diff --git a/docs/source/topology.rst b/docs/source/topology.rst index f2c1b421..e1934d37 100644 --- a/docs/source/topology.rst +++ b/docs/source/topology.rst @@ -357,6 +357,55 @@ additional values. ``BaremetalNode`` *compute01* then adopts all values from the adopted from *defaults*) and can then again override or append any configuration that is specific to that node. +Defining Node Out-Of-Band Management +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Drydock supports plugin-based OOB management. At a minimum a +OOB driver supports configuring a node to PXE boot during the next +boot cycle and power cycling the node to initiate the provisioning +process. Richer features might also be supported such as BIOS +configuration or BMC log analysis. The value of ``oob.type`` in the +host profile or baremetal node definition will define what additional +parameters are required for that type and what capabilities are available +via OOB driver tasks. + +IPMI +**** + +The ``ipmi`` OOB type requires additional configuration to allow OOB +management: + + 1. The ``oob`` parameters ``account`` and ``credential`` must be populated with a valid + account and password that can access the BMC via IPMI over LAN. + 2. The ``oob`` parameter ``network`` must reference which node network is used for OOB + access. + 3. The ``addressing`` section of the node definition must contain an IP address assignment + for the network referenced in ``oob.network``. + +Currently the IPMI driver supports only basic management by setting nodes to PXE boot and +power-cycling the node. + +Libvirt +******* + +The ``libvirt`` OOB type requires additional configuration within the site definition +as well as particular configuration in the deployment of Drydock (and likely the node +provisioning driver.): + + 1. A SSH public/private key-pair should be generated with the public key being added + to the authorized_keys file on all hypervisors hosting libvirt-based VMs being + deployed. The account for this must be in the ``libvirt`` group. + 2. The private key should be provided in the Drydock and MAAS charts as an override to + ``conf.ssh.private_key`` + 3. The Drydock and MAAS chart should override ``manifests.secret_ssh_key: true``. + 4. In the site definition, each libvirt-based node must define ``oob`` parameter + ``libvirt_uri`` of the form ``qemu+ssh://account@hostname/system`` where ``account`` + is an account in the libvirt group on the hypervisor with an authorized_key and + ``hostname`` is an IP address or FQDN for the hypervisor hosting the VM. + +Currently the Libvirt driver supports only basic management by setting nodes to PXE boot and +power-cycling the node. + Defining Node Interfaces and Network Addressing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/drydock_provisioner/drivers/node/maasdriver/models/machine.py b/drydock_provisioner/drivers/node/maasdriver/models/machine.py index d8177ecc..dad0c6c0 100644 --- a/drydock_provisioner/drivers/node/maasdriver/models/machine.py +++ b/drydock_provisioner/drivers/node/maasdriver/models/machine.py @@ -317,6 +317,42 @@ class Machine(model_base.ResourceBase): "Error setting node metadata, received HTTP %s from MaaS" % resp.status_code) + def set_power_parameters(self, power_type, **kwargs): + """Set power parameters for this node. + + Only available after the node has been added to MAAS. + + :param power_type: The type of power management for the node + :param kwargs: Each kwargs key will be prepended with 'power_parameters_' and + added to the list of updates for the node. + """ + if not power_type: + raise errors.DriverError( + "Cannot set power parameters. Must specify a power type.") + + url = self.interpolate_url() + + if kwargs: + power_params = dict() + + self.logger.debug("Setting node power type to %s." % power_type) + self.power_type = power_type + power_params['power_type'] = power_type + + for k, v in kwargs.items(): + power_params['power_parameters_' + k] = v + + self.logger.debug("Updating node %s power parameters: %s" % + (self.hostname, str(power_params))) + resp = self.api_client.put(url, files=power_params) + + if resp.status_code == 200: + return True + + raise errors.DriverError( + "Failed updating power parameters MAAS url %s - return code %s\n%s" + % (url, resp.status_code.resp.text)) + def to_dict(self): """Serialize this resource instance into a dict. @@ -460,10 +496,22 @@ class Machines(model_base.ResourceCollectionBase): (maas_node.resource_id, node_model.get_id())) if maas_node.hostname != node_model.name and update_name: - maas_node.hostname = node_model.name - maas_node.update() - self.logger.debug("Updated MaaS resource %s hostname to %s" % - (maas_node.resource_id, node_model.name)) + try: + maas_node.hostname = node_model.name + maas_node.update() + if node_model.oob_type == 'libvirt': + self.logger.debug( + "Updating node %s MaaS power parameters for libvirt." % + (node_model.name)) + oob_params = node_model.oob_parameters + maas_node.set_power_parameters( + 'virsh', + power_address=oob_params.get('libvirt_uri'), + power_id=node_model.name) + self.logger.debug("Updated MaaS resource %s hostname to %s" % + (maas_node.resource_id, node_model.name)) + except Exception as ex: + self.logger.debug("Error updating MAAS node: %s" % str(ex)) return maas_node diff --git a/drydock_provisioner/orchestrator/validations/oob_valid_ipmi.py b/drydock_provisioner/orchestrator/validations/oob_valid_ipmi.py new file mode 100644 index 00000000..9974b46e --- /dev/null +++ b/drydock_provisioner/orchestrator/validations/oob_valid_ipmi.py @@ -0,0 +1,51 @@ +# Copyright 2018 AT&T Intellectual Property. All other rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from drydock_provisioner.orchestrator.validations.validators import Validators + + +class IpmiValidity(Validators): + def __init__(self): + super().__init__('Valid IPMI Configuration', 'DD4001') + + def run_validation(self, site_design, orchestrator=None): + """For all IPMI-based nodes, check for a valid configuration. + + 1. Check that the node has an IP address assigned on the oob network + 2. Check that credentials are defined + """ + required_params = ['account', 'credential', 'network'] + + baremetal_node_list = site_design.baremetal_nodes or [] + + for baremetal_node in baremetal_node_list: + if baremetal_node.oob_type == 'ipmi': + for p in required_params: + if not baremetal_node.oob_parameters.get(p, None): + msg = ( + 'OOB parameter %s for IPMI node %s missing.' % p, + baremetal_node.name) + self.report_error(msg, [baremetal_node.doc_ref], + "Define OOB parameter %s" % p) + oob_addr = None + if baremetal_node.oob_parameters.get('network', None): + oob_net = baremetal_node.oob_parameters.get('network') + for a in baremetal_node.addressing: + if a.network == oob_net: + oob_addr = a.address + if not oob_addr: + msg = ('OOB address missing for IPMI node %s.' % + baremetal_node.name) + self.report_error(msg, [baremetal_node.doc_ref], + "Provide address to node OOB interface.") + return diff --git a/drydock_provisioner/orchestrator/validations/oob_valid_libvirt.py b/drydock_provisioner/orchestrator/validations/oob_valid_libvirt.py new file mode 100644 index 00000000..4c21621e --- /dev/null +++ b/drydock_provisioner/orchestrator/validations/oob_valid_libvirt.py @@ -0,0 +1,51 @@ +# Copyright 2018 AT&T Intellectual Property. All other rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from drydock_provisioner.orchestrator.validations.validators import Validators + + +class LibvirtValidity(Validators): + def __init__(self): + super().__init__('Valid Libvirt Configuration', 'DD4002') + + def run_validation(self, site_design, orchestrator=None): + """For all libvirt-based nodes, check for a valid configuration. + + 1. Check that the node has a valid libvirt_uri + 2. Check that boot MAC address is defined + """ + baremetal_node_list = site_design.baremetal_nodes or [] + + for baremetal_node in baremetal_node_list: + if baremetal_node.oob_type == 'libvirt': + libvirt_uri = baremetal_node.oob_parameters.get( + 'libvirt_uri', None) + if not libvirt_uri: + msg = ('OOB parameter libvirt_uri missing for node %s.' % + baremetal_node.name) + self.report_error( + msg, [baremetal_node.doc_ref], + "Provide libvirt URI to node hypervisor.") + else: + if not libvirt_uri.startswith("qemu+ssh"): + msg = 'OOB parameter libvirt_uri has invalid scheme.' + self.report_error( + msg, [baremetal_node.doc_ref], + "Only scheme 'qemu+ssh' is supported.") + if not baremetal_node.boot_mac: + msg = 'libvirt-based node requries defined boot MAC address.' + self.report_error(msg, [ + baremetal_node.doc_ref + ], "Specify the node's PXE MAC address in metadata.boot_mac" + ) + return diff --git a/drydock_provisioner/orchestrator/validations/validator.py b/drydock_provisioner/orchestrator/validations/validator.py index e5bd51d7..ee5ae3b3 100644 --- a/drydock_provisioner/orchestrator/validations/validator.py +++ b/drydock_provisioner/orchestrator/validations/validator.py @@ -28,6 +28,8 @@ from drydock_provisioner.orchestrator.validations.storage_partititioning import from drydock_provisioner.orchestrator.validations.storage_sizing import StorageSizing from drydock_provisioner.orchestrator.validations.unique_network_check import UniqueNetworkCheck from drydock_provisioner.orchestrator.validations.hostname_validity import HostnameValidity +from drydock_provisioner.orchestrator.validations.oob_valid_ipmi import IpmiValidity +from drydock_provisioner.orchestrator.validations.oob_valid_libvirt import LibvirtValidity class Validator(): @@ -88,4 +90,6 @@ rule_set = [ StorageSizing(), UniqueNetworkCheck(), HostnameValidity(), + IpmiValidity(), + LibvirtValidity() ]