Example #1
0
    def get_ipmi_session(self):
        """
        Initialize a Pyghmi IPMI session to this runner's self.node

        :return: An instance of pyghmi.ipmi.command.Command initialized to nodes' IPMI interface
        """

        node = self.node

        if node.oob_type != 'ipmi':
            raise errors.DriverError("Node OOB type is not IPMI")

        ipmi_network = self.node.oob_parameters['network']
        ipmi_address = self.node.get_network_address(ipmi_network)

        if ipmi_address is None:
            raise errors.DriverError("Node %s has no IPMI address" %
                                     (node.name))

        ipmi_account = self.node.oob_parameters['account']
        ipmi_credential = self.node.oob_parameters['credential']

        self.logger.debug("Starting IPMI session to %s with %s/%s" %
                          (ipmi_address, ipmi_account, ipmi_credential[:1]))
        ipmi_session = Command(bmc=ipmi_address,
                               userid=ipmi_account,
                               password=ipmi_credential)

        return ipmi_session
Example #2
0
    def get_redfish_session(self, node):
        """Initialize a Redfish session to the node.

        :param node: instance of objects.BaremetalNode
        :return: An instance of client.RedfishSession initialized to node's Redfish interface
        """
        if node.oob_type != 'redfish':
            raise errors.DriverError("Node OOB type is not Redfish")

        oob_network = node.oob_parameters['network']
        oob_address = node.get_network_address(oob_network)
        if oob_address is None:
            raise errors.DriverError("Node %s has no OOB Redfish address" %
                                     (node.name))

        oob_account = node.oob_parameters['account']
        oob_credential = node.oob_parameters['credential']

        self.logger.debug("Starting Redfish session to %s with %s" %
                          (oob_address, oob_account))
        try:
            redfish_obj = RedfishSession(
                host=oob_address,
                account=oob_account,
                password=oob_credential,
                use_ssl=cfg.CONF.redfish_driver.use_ssl,
                connection_retries=cfg.CONF.redfish_driver.max_retries)
        except (RedfishException, errors.DriverError) as iex:
            self.logger.error(
                "Error initializing Redfish session for node %s" % node.name)
            self.logger.error("Redfish Exception: %s" % str(iex))
            redfish_obj = None

        return redfish_obj
Example #3
0
    def execute_task(self, task_id):
        # actions that should be threaded for execution
        threaded_actions = [hd_fields.OrchestratorAction.DeployNode]

        action_timeouts = {
            hd_fields.OrchestratorAction.DeployNode:
            config.config_mgr.conf.timeouts.deploy_node,
        }

        task = self.state_manager.get_task(task_id)

        if task is None:
            raise errors.DriverError("Invalid task %s" % (task_id))

        if task.action not in self.supported_actions:
            raise errors.DriverError(
                "Driver %s doesn't support task action %s" %
                (self.driver_desc, task.action))

        task.set_status(hd_fields.TaskStatus.Running)
        task.save()

        if task.action in threaded_actions:
            if task.retry > 0:
                msg = "Retrying task %s on previous failed entities." % str(
                    task.get_id())
                task.add_status_msg(msg=msg,
                                    error=False,
                                    ctx=str(task.get_id()),
                                    ctx_type='task')

            with concurrent.futures.ThreadPoolExecutor(max_workers=16) as e:
                subtask_futures = dict()
                subtask = self.orchestrator.create_task(
                    design_ref=task.design_ref,
                    action=task.action,
                    retry=task.retry)
                task.register_subtask(subtask)

                action = self.action_class_map.get(task.action,
                                                   None)(subtask,
                                                         self.orchestrator,
                                                         self.state_manager)
                subtask_futures[subtask.get_id().bytes] = e.submit(
                    action.start)

        task.set_status(hd_fields.TaskStatus.Complete)
        task.save()

        return
Example #4
0
    def apply_to_node(self, system_id):
        """
        Apply this tag to a MaaS node

        :param system_id: MaaS system_id of the node
        """

        if system_id in self.get_applied_nodes():
            self.logger.debug("Tag %s already applied to node %s" %
                              (self.name, system_id))
        else:
            url = self.interpolate_url()

            resp = self.api_client.post(url,
                                        op='update_nodes',
                                        files={'add': system_id})

            if not resp.ok:
                self.logger.error(
                    "Error applying tag to node, received HTTP %s from MaaS" %
                    resp.status_code)
                self.logger.debug("MaaS response: %s" % resp.text)
                raise errors.DriverError(
                    "Error applying tag to node, received HTTP %s from MaaS" %
                    resp.status_code)
Example #5
0
    def deploy(self, user_data=None, platform=None, kernel=None):
        """Start the MaaS deployment process.

        :param user_data: ``str`` of cloud-init user data
        :param platform: Which image to install
        :param kernel: Which kernel to enable
        """
        deploy_options = {}

        if user_data is not None:
            deploy_options['user_data'] = base64.b64encode(
                user_data.encode('utf-8')).decode('utf-8')

        if platform is not None:
            deploy_options['distro_series'] = platform

        if kernel is not None:
            deploy_options['hwe_kernel'] = kernel

        url = self.interpolate_url()
        resp = self.api_client.post(
            url,
            op='deploy',
            files=deploy_options if len(deploy_options) > 0 else None)

        if not resp.ok:
            self.logger.error(
                "Error deploying node, received HTTP %s from MaaS" %
                resp.status_code)
            self.logger.debug("MaaS response: %s" % resp.text)
            raise errors.DriverError(
                "Error deploying node, received HTTP %s from MaaS" %
                resp.status_code)
Example #6
0
    def get_applied_nodes(self):
        """
        Query the list of nodes this tag is currently applied to

        :return: List of MaaS system_ids of nodes
        """

        url = self.interpolate_url()

        resp = self.api_client.get(url, op='nodes')

        if resp.status_code == 200:
            resp_json = resp.json()
            system_id_list = []

            for n in resp_json:
                system_id = n.get('system_id', None)
                if system_id is not None:
                    system_id_list.append(system_id)

            return system_id_list
        else:
            self.logger.error(
                "Error retrieving node/tag pairs, received HTTP %s from MaaS" %
                resp.status_code)
            self.logger.debug("MaaS response: %s" % resp.text)
            raise errors.DriverError(
                "Error retrieving node/tag pairs, received HTTP %s from MaaS" %
                resp.status_code)
Example #7
0
    def add(self, res):
        """
        Custom add to include a subnet id and type which can't be
        updated in a PUT
        """
        data_dict = res.to_dict()

        subnet = getattr(res, 'subnet', None)

        if subnet is not None:
            data_dict['subnet'] = subnet

        range_type = getattr(res, 'type', None)

        if range_type is not None:
            data_dict['type'] = range_type

        url = self.interpolate_url()

        resp = self.api_client.post(url, files=data_dict)

        if resp.status_code == 200:
            resp_json = resp.json()
            res.set_resource_id(resp_json.get('id'))
            return res

        raise errors.DriverError(
            "Failed updating MAAS url %s - return code %s" %
            (url, resp.status_code))
Example #8
0
    def unmount(self):
        """Unmount this block device."""
        try:
            self.refresh()
            if self.filesystem is None or self.filesystem.get(
                    'mount_point', None) is None:
                self.logger.debug(
                    "Device %s not currently mounted, skipping unmount." %
                    (self.name))

            url = self.interpolate_url()

            self.logger.debug("Unmounting device %s on node %s" %
                              (self.name, self.system_id))
            resp = self.api_client.post(url, op='unmount')

            if not resp.ok:
                raise Exception("MAAS error: %s - %s" %
                                (resp.status_code, resp.text))

            self.refresh()
        except Exception as ex:
            msg = "Error: unmount of device %s on node %s failed: %s" \
                  % (self.name, self.system_id, str(ex))
            self.logger.error(msg)
            raise errors.DriverError(msg)
Example #9
0
    def from_json(cls, api_client, json_string):
        parsed = json.loads(json_string)

        if isinstance(parsed, dict):
            return cls.from_dict(api_client, parsed)

        raise errors.DriverError("Invalid JSON for class %s" % (cls.__name__))
Example #10
0
    def format(self, fstype='ext4', uuid_str=None, label=None):
        """Format this block device with a filesystem.

        :param fstype: String of the filesystem format to use, defaults to ext4
        :param uuid: String of the UUID to assign to the filesystem. One will be
                     generated if this is left as None
        """
        try:
            data = {'fstype': fstype}

            if uuid_str:
                data['uuid'] = str(uuid_str)
            else:
                data['uuid'] = str(uuid.uuid4())

            url = self.interpolate_url()

            self.logger.debug(
                "Formatting device %s on node %s as filesystem: fstype=%s, uuid=%s"
                % (self.name, self.system_id, fstype, uuid))
            resp = self.api_client.post(url, op='format', files=data)

            if not resp.ok:
                raise Exception("MAAS error: %s - %s" %
                                (resp.status_code, resp.text))

            self.refresh()
        except Exception as ex:
            msg = "Error: format of device %s on node %s failed: %s" \
                  % (self.name, self.system_id, str(ex))
            self.logger.error(msg)
            raise errors.DriverError(msg)
Example #11
0
    def delete_lv(self, lv_id=None, lv_name=None):
        """Delete a logical volume from this volume group.

        :param lv_id: Resource ID of the logical volume
        :param lv_name: Name of the logical volume, only referenced if no lv_id is specified
        """
        try:
            self.refresh()
            if self.logical_volumes is not None:
                if lv_id and lv_id in self.logical_volumes.values():
                    target_lv = lv_id
                elif lv_name and lv_name in self.logical_volumes:
                    target_lv = self.logical_volumes[lv_name]
                else:
                    raise Exception(
                        "lv_id %s and lv_name %s not found in VG %s" %
                        (lv_id, lv_name, self.name))

                url = self.interpolate_url()

                resp = self.api_client.post(
                    url, op='delete_logical_volume', files={
                        'id': target_lv
                    })

                if not resp.ok:
                    raise Exception("MAAS error - %s - %s" % (resp.status_code,
                                                              resp.text))
            else:
                raise Exception("VG %s has no logical volumes" % self.name)
        except Exception as ex:
            msg = "Error: Could not delete logical volume: %s" % str(ex)
            self.logger.error(msg)
            raise errors.DriverError(msg)
Example #12
0
    def commission(self, debug=False, skip_bmc_config=False):
        """Start the MaaS commissioning process.

        :param debug: If true, enable ssh on the node and leave it power up after commission
        :param skip_bmc_config: If true, skip re-configuration of the BMC for IPMI based machines
        """
        url = self.interpolate_url()

        # If we want to debug this node commissioning, enable SSH
        # after commissioning and leave the node powered up

        options = {
            'enable_ssh': '1' if debug else '0',
            'skip_bmc_config': '1' if skip_bmc_config else '0',
        }

        resp = self.api_client.post(url, op='commission', files=options)

        # Need to sort out how to handle exceptions
        if not resp.ok:
            self.logger.error(
                "Error commissioning node, received HTTP %s from MaaS" %
                resp.status_code)
            self.logger.debug("MaaS response: %s" % resp.text)
            raise errors.DriverError(
                "Error commissioning node, received HTTP %s from MaaS" %
                resp.status_code)
Example #13
0
    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.
        """
        with power_cv:
            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,
                        **{
                            k: "<redacted>"
                            for k in power_params if k in [
                                "power_parameters_power_pass"
                            ]
                        },
                    }),
                ))
                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"
                    % (url, resp.status_code.resp.text))
Example #14
0
    def unlink_subnet(self, subnet_id):
        for l in self.links:
            if l.get('subnet_id', None) == subnet_id:
                url = self.interpolate_url()

                resp = self.api_client.post(url,
                                            op='unlink_subnet',
                                            files={'id': l.get('resource_id')})

                if not resp.ok:
                    raise errors.DriverError("Error unlinking subnet")
                else:
                    return

        raise errors.DriverError(
            "Error unlinking interface, Link to subnet_id %s not found." %
            subnet_id)
Example #15
0
    def attach_fabric(self, fabric_id=None, fabric_name=None):
        """Attach this interface to a MaaS fabric.

        Only one of fabric_id or fabric_name should be specified. If both
        are, fabric_id rules

        :param fabric_id: The MaaS resource ID of a network Fabric to connect to
        :param fabric_name: The name of a MaaS fabric to connect to
        """
        fabric = None

        fabrics = maas_fabric.Fabrics(self.api_client)
        fabrics.refresh()

        if fabric_id is not None:
            fabric = fabrics.select(fabric_id)
        elif fabric_name is not None:
            fabric = fabrics.singleton({'name': fabric_name})
        else:
            self.logger.warning("Must specify fabric_id or fabric_name")
            raise ValueError("Must specify fabric_id or fabric_name")

        if fabric is None:
            self.logger.warning(
                "Fabric not found in MaaS for fabric_id %s, fabric_name %s" %
                (fabric_id, fabric_name))
            raise errors.DriverError(
                "Fabric not found in MaaS for fabric_id %s, fabric_name %s" %
                (fabric_id, fabric_name))

        # Locate the untagged VLAN for this fabric.
        fabric_vlan = fabric.vlans.singleton({'vid': 0})

        if fabric_vlan is None:
            self.logger.warning("Cannot locate untagged VLAN on fabric %s" %
                                (fabric_id))
            raise errors.DriverError(
                "Cannot locate untagged VLAN on fabric %s" % (fabric_id))

        self.vlan = fabric_vlan.resource_id
        self.logger.info(
            "Attaching interface %s on system %s to VLAN %s on fabric %s" %
            (self.resource_id, self.system_id, fabric_vlan.resource_id,
             fabric.resource_id))
        self.update()
Example #16
0
    def execute_task(self, task_id):
        task = self.state_manager.get_task(task_id)
        task_action = task.action

        if task_action in self.supported_actions:
            return
        else:
            raise errors.DriverError("Unsupported action %s for driver %s" %
                                     (task_action, self.driver_desc))
Example #17
0
    def create_partition(self, partition):
        """Create a partition on this block device.

        :param partition: Instance of models.partition.Partition to be carved out of this block device
        """
        if self.type == 'physical':
            if self.partitions is not None:
                partition = self.partitions.add(partition)
                self.partitions.refresh()
                return self.partitions.select(partition.resource_id)
            else:
                msg = "Error: could not access device %s partition list" % self.name
                self.logger.error(msg)
                raise errors.DriverError(msg)
        else:
            msg = "Error: cannot partition non-physical device %s." % (
                self.name)
            self.logger.error(msg)
            raise errors.DriverError(msg)
Example #18
0
    def update(self):
        data_dict = self.to_dict()
        url = self.interpolate_url()

        resp = self.api_client.put(url, files=data_dict)

        if resp.status_code == 200:
            return True
        
        raise errors.DriverError("Failed updating MAAS url %s - return code %s\n%s"
                % (url, resp.status_code, resp.text))
Example #19
0
    def __init__(self, node=None, **kwargs):
        super(PyghmiTaskRunner, self).__init__(**kwargs)

        self.logger = logging.getLogger('drydock.oobdriver.pyghmi')
        # We cheat here by providing the Node model instead
        # of making the runner source it from statemgmt
        if node is None:
            self.logger.error("Did not specify target node")
            raise errors.DriverError("Did not specify target node")

        self.node = node
Example #20
0
    def __init__(self, action=None, state_manager=None, orchestrator=None):
        super().__init__()

        self.orchestrator = orchestrator

        if isinstance(state_manager, statemgmt.DesignState):
            self.state_manager = state_manager
        else:
            raise errors.DriverError("Invalid state manager specified")

        self.action = action
Example #21
0
    def _get_ks_session(self):
        # Get keystone session object

        try:
            ks_session = KeystoneUtils.get_session()
        except exc.AuthorizationFailure as aferr:
            self.logger.error('Could not authorize against Keystone: %s',
                              str(aferr))
            raise errors.DriverError(
                'Could not authorize against Keystone: %s', str(aferr))

        return ks_session
Example #22
0
    def execute_task(self, task_id):
        task = self.state_manager.get_task(task_id)
        task_action = task.action

        if task_action in self.supported_actions:
            task.success()
            task.set_status(hd_fields.TaskStatus.Complete)
            task.save()
            return
        else:
            raise errors.DriverError("Unsupported action %s for driver %s" %
                                     (task_action, self.driver_desc))
Example #23
0
    def add(self, res):
        data_dict = res.to_dict()
        url = self.interpolate_url()

        resp = self.api_client.post(url, files=data_dict)

        if resp.status_code == 200:
            resp_json = resp.json()
            res.set_resource_id(resp_json.get('id'))
            return res
        
        raise errors.DriverError("Failed updating MAAS url %s - return code %s"
                % (url, resp.status_code))
Example #24
0
    def reset_network_config(self):
        """Reset the node networking configuration."""
        self.logger.info("Resetting networking configuration on node %s" %
                         (self.resource_id))

        url = self.interpolate_url()

        resp = self.api_client.post(url, op='restore_networking_configuration')

        if not resp.ok:
            msg = "Error resetting network on node %s: %s - %s" \
                  % (self.resource_id, resp.status_code, resp.text)
            self.logger.error(msg)
            raise errors.DriverError(msg)
Example #25
0
    def acquire_node(self, node_name):
        """Acquire a commissioned node fro deployment.

        :param node_name: The hostname of a node to acquire
        """
        self.refresh()

        node = self.singleton({'hostname': node_name})

        if node is None:
            self.logger.info("Node %s not found" % (node_name))
            raise errors.DriverError("Node %s not found" % (node_name))

        if node.status_name != 'Ready':
            self.logger.info(
                "Node %s status '%s' does not allow deployment, should be 'Ready'."
                % (node_name, node.status_name))
            raise errors.DriverError(
                "Node %s status '%s' does not allow deployment, should be 'Ready'."
                % (node_name, node.status_name))

        url = self.interpolate_url()

        resp = self.api_client.post(
            url, op='allocate', files={
                'system_id': node.resource_id
            })

        if not resp.ok:
            self.logger.error(
                "Error acquiring node, MaaS returned %s" % resp.status_code)
            self.logger.debug("MaaS response: %s" % resp.text)
            raise errors.DriverError(
                "Error acquiring node, MaaS returned %s" % resp.status_code)

        return node
Example #26
0
    def add(self, res):
        """
        Create a new resource in this collection in MaaS

        Customize as Tag resources use 'name' instead of 'id'

        :param res: Instance of cls.collection_resource
        """
        data_dict = res.to_dict()
        url = self.interpolate_url()

        resp = self.api_client.post(url, files=data_dict)

        if resp.status_code == 200:
            resp_json = resp.json()
            res.set_resource_id(resp_json.get('name'))
            return res
        elif resp.status_code == 400 and resp.text.find(
                'Tag with this Name already exists.') != -1:
            raise errors.DriverError("Tag %s already exists" % res.name)
        else:
            raise errors.DriverError(
                "Failed updating MAAS url %s - return code %s" %
                (url, resp.status_code))
Example #27
0
    def disconnect(self):
        """Disconnect this interface from subnets and VLANs."""
        url = self.interpolate_url()

        self.logger.debug("Disconnecting interface %s from networks." %
                          (self.name))
        resp = self.api_client.post(url, op='disconnect')

        if not resp.ok:
            self.logger.warning(
                "Could not disconnect interface, MaaS error: %s - %s" %
                (resp.status_code, resp.text))
            raise errors.DriverError(
                "Could not disconnect interface, MaaS error: %s - %s" %
                (resp.status_code, resp.text))
Example #28
0
    def is_importing(self):
        """Check if boot resources are importing."""
        url = self.interpolate_url()

        self.logger.debug("Checking if boot resources are importing.")
        resp = self.api_client.get(url, op='is_importing')

        if resp.status_code == 200:
            resp_json = resp.json()
            self.logger.debug("Boot resource importing status: %s" % resp_json)
            return resp_json
        else:
            msg = "Error checking import status of boot resources: %s - %s" % (
                resp.status_code, resp.text)
            self.logger.error(msg)
            raise errors.DriverError(msg)
Example #29
0
 def set_bootable(self):
     """Set this disk as the system bootdisk."""
     try:
         url = self.interpolate_url()
         self.logger.debug("Setting device %s on node %s as bootable." %
                           (self.resource_id, self.system_id))
         resp = self.api_client.post(url, op='set_boot_disk')
         if not resp.ok:
             raise Exception("MAAS error: %s - %s" %
                             (resp.status_code, resp.text))
         self.refresh()
     except Exception as ex:
         msg = "Error: setting device %s on node %s to boot failed: %s" \
               % (self.name, self.system_id, str(ex))
         self.logger.error(msg)
         raise errors.DriverError(msg)
Example #30
0
    def execute_task(self, task_id):
        task = self.state_manager.get_task(task_id)
        task_action = task.action

        if task_action in self.supported_actions:
            task_runner = DriverTaskRunner(task_id, self.state_manager,
                                           self.orchestrator)
            task_runner.start()

            while task_runner.is_alive():
                time.sleep(1)

            return
        else:
            raise errors.DriverError("Unsupported action %s for driver %s" %
                                     (task_action, self.driver_desc))