This file is indexed.

/usr/lib/python3/dist-packages/provisioningserver/drivers/power/mscm.py is in python3-maas-provisioningserver 2.0.0~beta3+bzr4941-0ubuntu1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# Copyright 2015-2016 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""Moonshot HP iLO Chassis Power Driver.

Support for managing nodes via the Moonshot HP iLO Chassis Manager CLI.

This module provides support for interacting with HP Moonshot iLO Chassis
Management (MSCM) CLI via SSH, and for using that support to allow MAAS to
manage systems via iLO.
"""

__all__ = [
    'probe_and_enlist_mscm',
]

import re
from socket import error as SOCKETError

from paramiko import (
    AutoAddPolicy,
    SSHClient,
    SSHException,
)
from provisioningserver.drivers.power import (
    PowerActionError,
    PowerConnError,
    PowerDriver,
    PowerFatalError,
)
from provisioningserver.rpc.utils import (
    commission_node,
    create_node,
)
from provisioningserver.utils.twisted import synchronous


cartridge_mapping = {
    'ProLiant Moonshot Cartridge': 'amd64/generic',
    'ProLiant m300 Server Cartridge': 'amd64/generic',
    'ProLiant m350 Server Cartridge': 'amd64/generic',
    'ProLiant m400 Server Cartridge': 'arm64/xgene-uboot',
    'ProLiant m500 Server Cartridge': 'amd64/generic',
    'ProLiant m700 Server Cartridge': 'amd64/generic',
    'ProLiant m710 Server Cartridge': 'amd64/generic',
    'ProLiant m720 Server Cartridge': 'amd64/generic',
    'ProLiant m800 Server Cartridge': 'armhf/keystone',
    'Default': 'amd64/generic',
}


class MSCMState:
    OFF = ("Off", "Unavailable")
    ON = "On"


class MSCMPowerDriver(PowerDriver):

    name = 'mscm'
    description = "Moonshot HP iLO Chassis Manager Power Driver."
    settings = []

    def detect_missing_packages(self):
        # uses pure-python paramiko ssh client - nothing to look for!
        return []

    def run_mscm_command(
            self, command, power_address=None, power_user=None,
            power_pass=None, **extra):
        """Run a single command on MSCM via SSH and return output."""
        try:
            ssh_client = SSHClient()
            ssh_client.set_missing_host_key_policy(AutoAddPolicy())
            ssh_client.connect(
                power_address, username=power_user, password=power_pass)
            _, stdout, _ = ssh_client.exec_command(command)
            output = stdout.read().decode('utf-8')
        except (SSHException, EOFError, SOCKETError) as e:
            raise PowerConnError(
                "Could not make SSH connection to MSCM for "
                "%s on %s - %s" % (power_user, power_address, e))
        finally:
            ssh_client.close()

        return output

    def power_on(self, system_id, context):
        """Power on MSCM node."""
        node_id = context['node_id']
        # If node is on, power off first
        if self.power_query(system_id, context) == 'on':
            self.power_off(system_id, context)
        try:
            # Configure node to boot once from PXE
            self.run_mscm_command(
                "set node bootonce pxe %s" % node_id, **context)
            # Power node on
            self.run_mscm_command(
                "set node power on %s" % node_id, **context)
        except PowerConnError as e:
            raise PowerActionError(
                "MSCM Power Driver unable to power on node %s: %s"
                % (context['node_id'], e))

    def power_off(self, system_id, context):
        """Power off MSCM node."""
        try:
            # Power node off
            self.run_mscm_command(
                "set node power off force %s" % context['node_id'], **context)
        except PowerConnError as e:
            raise PowerActionError(
                "MSCM Power Driver unable to power off node %s: %s"
                % (context['node_id'], e))

    def power_query(self, system_id, context):
        """Power query MSCM node."""
        try:
            # Retreive node power state
            #
            # Example of output from running "show node power <node_id>":
            # "show node power c1n1\r\r\n\r\nCartridge #1\r\n  Node #1\r\n
            # Power State: On\r\n"
            output = self.run_mscm_command(
                "show node power %s" % context['node_id'], **context)
        except PowerConnError as e:
            raise PowerActionError(
                "MSCM Power Driver unable to power query node %s: %s"
                % (context['node_id'], e))
        match = re.search("Power State:\s*((O[\w]+|U[\w]+))", output)
        if match is None:
            raise PowerFatalError(
                "MSCM Power Driver unable to extract node power state from: %s"
                % output)
        else:
            power_state = match.group(1)
            if power_state in MSCMState.OFF:
                return 'off'
            elif power_state == MSCMState.ON:
                return 'on'


@synchronous
def probe_and_enlist_mscm(
        user, host, username, password, accept_all=False, domain=None):
    """ Extracts all of nodes from the MSCM, sets all of them to boot via M.2
    by, default, sets them to bootonce via PXE, and then enlists them into
    MAAS.  If accept_all is True, it will also commission them.
    """
    mscm_driver = MSCMPowerDriver()
    # Discover all available nodes
    #
    # Example of output from running "show node list":
    # "show node list\r\r\nSlot ID    Proc Manufacturer
    # Architecture         Memory Power Health\r\n----
    # ----- ---------------------- --------------------
    # ------ ----- ------\r\n 01  c1n1  Intel Corporation
    # x86 Architecture     32 GB  On    OK \r\n 02  c2n1
    # N/A                    No Asset Information \r\n\r\n'"
    node_list = mscm_driver.run_mscm_command(
        "show node list", power_address=host,
        power_user=username, power_pass=password)
    nodes = re.findall(r'c\d+n\d', node_list)
    for node_id in nodes:
        params = {
            'power_address': host,
            'power_user': username,
            'power_pass': password,
            'node_id': node_id,
        }
        # Set default boot to M.2
        mscm_driver.run_mscm_command(
            "set node boot M.2 %s" % node_id, **params)
        # Retrieve node architecture
        #
        # Example of output from running "show node info <node_id>":
        # "show node info c1n1\r\r\n\r\nCartridge #1 \r\n
        # Type: Compute\r\n Manufacturer: HP\r\n
        # Product Name: ProLiant m500 Server Cartridge\r\n"
        node_info = mscm_driver.run_mscm_command(
            "show node info %s" % node_id, **params)
        match = re.search("Product Name:\s*([A-Za-z0-9 ]+)", node_info)
        if match is None:
            raise PowerFatalError(
                "MSCM Power Driver unable to extract node architecture"
                " from: %s" % node_info)
        else:
            cartridge = match.group(1)
        if cartridge in cartridge_mapping:
            arch = cartridge_mapping[cartridge]
        else:
            arch = cartridge_mapping['Default']
        # Retrieve node MACs
        #
        # Example of output from running "show node macaddr <node_id>":
        # "show node macaddr c1n1\r\r\nSlot ID    NIC 1 (Switch A)
        # NIC 2 (Switch B)  NIC 3 (Switch A)  NIC 4 (Switch B)\r\n
        # ---- ----- ----------------- ----------------- -----------------
        # -----------------\r\n  1  c1n1  a0:1d:48:b5:04:34 a0:1d:48:b5:04:35
        # a0:1d:48:b5:04:36 a0:1d:48:b5:04:37\r\n\r\n\r\n"
        node_macaddr = mscm_driver.run_mscm_command(
            "show node macaddr %s" % node_id, **params)
        macs = re.findall(r':'.join(['[0-9a-f]{2}'] * 6), node_macaddr)
        # Create node
        system_id = create_node(macs, arch, 'mscm', params, domain).wait(30)

        if accept_all:
            commission_node(system_id, user).wait(30)