~lutostag/ubuntu/trusty/maas/1.5.4+keystone

« back to all changes in this revision

Viewing changes to src/provisioningserver/drivers/hardware/mscm.py

  • Committer: Package Import Robot
  • Author(s): Greg Lutostanski, Greg Lutostanski, Gavin Panella, Diogo Matsubara, Raphaël Badin
  • Date: 2014-08-29 13:27:34 UTC
  • mfrom: (1.2.34)
  • Revision ID: package-import@ubuntu.com-20140829132734-1yaitdu9h4zlgci0
Tags: 1.5.4+bzr2294-0ubuntu1
* New upstream bug fix release:
  - Change supported releases for install to Precise, Saucy, Trusty, Utopic
    (Add Utopic; Remove Quantal/Raring) -- will still only be able to install
    releases with streams available to maas (LP: #1337437)
  - Package fails to install when the default route is through an
    aliased/tagged interface (LP: #1350235)
  - ERROR Nonce already used (LP: #1190986)
  - Add MAAS arm64/xgene support (LP: #1338851)
  - API documentation for nodegroup op=details missing parameter
    (LP: #1331982)
  - Reduce number of celery tasks emitted when updating a cluster controller
    (LP: #1324944)
  - Fix VirshSSH template which was referencing invalid attributes
    (LP: #1324966)
  - Fix a start up problems where a database lock was being taken outside of
    a transaction (LP: #1325640, LP: #1325759)
  - Reformat badly formatted Architecture error message (LP: #1301465)
  - Final changes to support ppc64el (now known as PowerNV) (LP: #1315154)
  - UI tweak to make navigation elements visible for documentation

[ Greg Lutostanski ]
 * debian/control:
  - maas-provisioningserver not maas-cluster-controller depends on
    python-pexpect (LP: #1352273)

[ Gavin Panella ]
 * debian/maas-cluster-controller.postinst
  - Allow maas-pserv to bind to all IPv6 addresses too. (LP: #1342302) 

[ Diogo Matsubara ]
 * debian/control:
  - python-maas-provisioningserver depends on python-paramiko (LP: #1334401)

[ Raphaël Badin ]
 * debian/extras/99-maas-sudoers:
  - Add rule 'maas-dhcp-server stop' job.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2014 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
"""Support for managing nodes via the Moonshot HP iLO Chassis Manager CLI.
 
5
 
 
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.
 
9
"""
 
10
 
 
11
from __future__ import (
 
12
    absolute_import,
 
13
    print_function,
 
14
    unicode_literals,
 
15
    )
 
16
str = None
 
17
 
 
18
__metaclass__ = type
 
19
__all__ = [
 
20
    'power_control_mscm',
 
21
    'probe_and_enlist_mscm',
 
22
]
 
23
 
 
24
import re
 
25
 
 
26
from paramiko import (
 
27
    AutoAddPolicy,
 
28
    SSHClient,
 
29
    )
 
30
import provisioningserver.custom_hardware.utils as utils
 
31
 
 
32
 
 
33
cartridge_mapping = {
 
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',
 
42
}
 
43
 
 
44
 
 
45
class MSCM_CLI_API(object):
 
46
    """An API for interacting with the Moonshot iLO CM CLI."""
 
47
 
 
48
    def __init__(self, host, username, password):
 
49
        """MSCM_CLI_API Constructor."""
 
50
        self.host = host
 
51
        self.username = username
 
52
        self.password = password
 
53
        self._ssh = SSHClient()
 
54
        self._ssh.set_missing_host_key_policy(AutoAddPolicy())
 
55
 
 
56
    def _run_cli_command(self, command):
 
57
        """Run a single command and return unparsed text from stdout."""
 
58
        self._ssh.connect(
 
59
            self.host, username=self.username, password=self.password)
 
60
        try:
 
61
            _, stdout, _ = self._ssh.exec_command(command)
 
62
            output = stdout.read()
 
63
        finally:
 
64
            self._ssh.close()
 
65
 
 
66
        return output
 
67
 
 
68
    def discover_nodes(self):
 
69
        """Discover all available nodes.
 
70
 
 
71
        Example of stdout from running "show node list":
 
72
 
 
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'
 
79
 
 
80
        The regex 'c\d+n\d' is finding the node_id's c1-45n1-8
 
81
        """
 
82
        node_list = self._run_cli_command("show node list")
 
83
        return re.findall(r'c\d+n\d', node_list)
 
84
 
 
85
    def get_node_macaddr(self, node_id):
 
86
        """Get node MAC address(es).
 
87
 
 
88
        Example of stdout from running "show node macaddr <node_id>":
 
89
 
 
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'
 
95
 
 
96
        The regex '[\:]'.join(['[0-9A-F]{1,2}'] * 6) is finding
 
97
        the MAC Addresses for the given node_id.
 
98
        """
 
99
        macs = self._run_cli_command("show node macaddr %s" % node_id)
 
100
        return re.findall(r':'.join(['[0-9a-f]{2}'] * 6), macs)
 
101
 
 
102
    def get_node_arch(self, node_id):
 
103
        """Get node architecture.
 
104
 
 
105
        Example of stdout from running "show node info <node_id>":
 
106
 
 
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'
 
109
 
 
110
        Parsing this retrieves 'ProLiant m500 Server Cartridge'
 
111
        """
 
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]
 
116
        else:
 
117
            return cartridge_mapping['Default']
 
118
 
 
119
    def get_node_power_status(self, node_id):
 
120
        """Get power state of node (on/off).
 
121
 
 
122
        Example of stdout from running "show node power <node_id>":
 
123
 
 
124
        'show node power c1n1\r\r\n\r\nCartridge #1\r\n  Node #1\r\n
 
125
        Power State: On\r\n'
 
126
 
 
127
        Parsing this retrieves 'On'
 
128
        """
 
129
        power_state = self._run_cli_command("show node power %s" % node_id)
 
130
        return power_state.split('Power State: ')[1].splitlines()[0]
 
131
 
 
132
    def power_node_on(self, node_id):
 
133
        """Power node on."""
 
134
        return self._run_cli_command("set node power on %s" % node_id)
 
135
 
 
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)
 
139
 
 
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)
 
143
 
 
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)
 
147
 
 
148
 
 
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
 
151
    of 'mscm'.
 
152
    """
 
153
    mscm = MSCM_CLI_API(host, username, password)
 
154
    power_status = mscm.get_node_power_status(node_id)
 
155
 
 
156
    if power_change == 'off':
 
157
        mscm.power_node_off(node_id)
 
158
        return
 
159
 
 
160
    if power_change != 'on':
 
161
        raise AssertionError('Unexpected maas power mode.')
 
162
 
 
163
    if power_status == 'On':
 
164
        mscm.power_node_off(node_id)
 
165
 
 
166
    mscm.configure_node_bootonce_pxe(node_id)
 
167
    mscm.power_node_on(node_id)
 
168
 
 
169
 
 
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.
 
173
    """
 
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)
 
179
        params = {
 
180
            'power_address': host,
 
181
            'power_user': username,
 
182
            'power_pass': password,
 
183
            'node_id': node_id,
 
184
        }
 
185
        arch = mscm.get_node_arch(node_id)
 
186
        macs = mscm.get_node_macaddr(node_id)
 
187
        utils.create_node(macs, arch, 'mscm', params)