1
# Copyright 2014 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
"""Support for managing nodes via the Moonshot HP iLO Chassis Manager CLI.
6
This module provides support for interacting with HP Moonshot iLO Chassis
7
Management (MSCM) CLI via SSH, and for using that support to allow MAAS to
8
manage systems via iLO.
11
from __future__ import (
21
'probe_and_enlist_mscm',
26
from paramiko import (
30
import provisioningserver.custom_hardware.utils as utils
34
'ProLiant Moonshot Cartridge': 'amd64/generic',
35
'ProLiant m300 Server Cartridge': 'amd64/generic',
36
'ProLiant m350 Server Cartridge': 'amd64/generic',
37
'ProLiant m400 Server Cartridge': 'arm64/xgene-uboot',
38
'ProLiant m500 Server Cartridge': 'amd64/generic',
39
'ProLiant m710 Server Cartridge': 'amd64/generic',
40
'ProLiant m800 Server Cartridge': 'armhf/keystone',
41
'Default': 'arm64/generic',
45
class MSCM_CLI_API(object):
46
"""An API for interacting with the Moonshot iLO CM CLI."""
48
def __init__(self, host, username, password):
49
"""MSCM_CLI_API Constructor."""
51
self.username = username
52
self.password = password
53
self._ssh = SSHClient()
54
self._ssh.set_missing_host_key_policy(AutoAddPolicy())
56
def _run_cli_command(self, command):
57
"""Run a single command and return unparsed text from stdout."""
59
self.host, username=self.username, password=self.password)
61
_, stdout, _ = self._ssh.exec_command(command)
62
output = stdout.read()
68
def discover_nodes(self):
69
"""Discover all available nodes.
71
Example of stdout from running "show node list":
73
'show node list\r\r\nSlot ID Proc Manufacturer
74
Architecture Memory Power Health\r\n----
75
----- ---------------------- --------------------
76
------ ----- ------\r\n 01 c1n1 Intel Corporation
77
x86 Architecture 32 GB On OK \r\n 02 c2n1
78
N/A No Asset Information \r\n\r\n'
80
The regex 'c\d+n\d' is finding the node_id's c1-45n1-8
82
node_list = self._run_cli_command("show node list")
83
return re.findall(r'c\d+n\d', node_list)
85
def get_node_macaddr(self, node_id):
86
"""Get node MAC address(es).
88
Example of stdout from running "show node macaddr <node_id>":
90
'show node macaddr c1n1\r\r\nSlot ID NIC 1 (Switch A)
91
NIC 2 (Switch B) NIC 3 (Switch A) NIC 4 (Switch B)\r\n
92
---- ----- ----------------- ----------------- -----------------
93
-----------------\r\n 1 c1n1 a0:1d:48:b5:04:34 a0:1d:48:b5:04:35
94
a0:1d:48:b5:04:36 a0:1d:48:b5:04:37\r\n\r\n\r\n'
96
The regex '[\:]'.join(['[0-9A-F]{1,2}'] * 6) is finding
97
the MAC Addresses for the given node_id.
99
macs = self._run_cli_command("show node macaddr %s" % node_id)
100
return re.findall(r':'.join(['[0-9a-f]{2}'] * 6), macs)
102
def get_node_arch(self, node_id):
103
"""Get node architecture.
105
Example of stdout from running "show node info <node_id>":
107
'show node info c1n1\r\r\n\r\nCartridge #1 \r\n Type: Compute\r\n
108
Manufacturer: HP\r\n Product Name: ProLiant m500 Server Cartridge\r\n'
110
Parsing this retrieves 'ProLiant m500 Server Cartridge'
112
node_detail = self._run_cli_command("show node info %s" % node_id)
113
cartridge = node_detail.split('Product Name: ')[1].splitlines()[0]
114
if cartridge in cartridge_mapping:
115
return cartridge_mapping[cartridge]
117
return cartridge_mapping['Default']
119
def get_node_power_status(self, node_id):
120
"""Get power state of node (on/off).
122
Example of stdout from running "show node power <node_id>":
124
'show node power c1n1\r\r\n\r\nCartridge #1\r\n Node #1\r\n
127
Parsing this retrieves 'On'
129
power_state = self._run_cli_command("show node power %s" % node_id)
130
return power_state.split('Power State: ')[1].splitlines()[0]
132
def power_node_on(self, node_id):
134
return self._run_cli_command("set node power on %s" % node_id)
136
def power_node_off(self, node_id):
137
"""Power node off."""
138
return self._run_cli_command("set node power off force %s" % node_id)
140
def configure_node_boot_m2(self, node_id):
141
"""Configure HDD boot for node."""
142
return self._run_cli_command("set node boot M.2 %s" % node_id)
144
def configure_node_bootonce_pxe(self, node_id):
145
"""Configure PXE boot for node once."""
146
return self._run_cli_command("set node bootonce pxe %s" % node_id)
149
def power_control_mscm(host, username, password, node_id, power_change):
150
"""Handle calls from the power template for nodes with a power type
153
mscm = MSCM_CLI_API(host, username, password)
154
power_status = mscm.get_node_power_status(node_id)
156
if power_change == 'off':
157
mscm.power_node_off(node_id)
160
if power_change != 'on':
161
raise AssertionError('Unexpected maas power mode.')
163
if power_status == 'On':
164
mscm.power_node_off(node_id)
166
mscm.configure_node_bootonce_pxe(node_id)
167
mscm.power_node_on(node_id)
170
def probe_and_enlist_mscm(host, username, password):
171
""" Extracts all of nodes from mscm, sets all of them to boot via HDD by,
172
default, sets them to bootonce via PXE, and then enlists them into MAAS.
174
mscm = MSCM_CLI_API(host, username, password)
175
nodes = mscm.discover_nodes()
176
for node_id in nodes:
177
# Set default boot to HDD
178
mscm.configure_node_boot_m2(node_id)
180
'power_address': host,
181
'power_user': username,
182
'power_pass': password,
185
arch = mscm.get_node_arch(node_id)
186
macs = mscm.get_node_macaddr(node_id)
187
utils.create_node(macs, arch, 'mscm', params)