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
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
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
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)
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)
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)
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))
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)
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__))
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)
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)
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)
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))
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)
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()
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))
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)
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))
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
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
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
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))
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))
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)
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
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))
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))
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)
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)
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))