def _configure_boot_from_volume(self, task): """Set information for booting from a remote volume to iRMC. :param task: a TaskManager instance containing the node to act on. :raises: IRMCOperationError if iRMC operation failed """ irmc_info = irmc_common.parse_driver_info(task.node) viom_conf = viom.VIOMConfiguration(irmc_info, identification=task.node.uuid) self._register_lan_ports(viom_conf, task) for vt in task.volume_targets: if vt.volume_type == 'iscsi': self._set_iscsi_target(task, viom_conf, vt) elif vt.volume_type == 'fibre_channel': self._set_fc_target(task, viom_conf, vt) try: LOG.debug('Set VIOM configuration for node %(node)s: %(table)s', { 'node': task.node.uuid, 'table': viom_conf.dump_json() }) viom_conf.apply() except scci.SCCIError as e: LOG.error( 'iRMC failed to set VIOM configuration for node ' '%(node)s: %(error)s', { 'node': task.node.uuid, 'error': e }) raise exception.IRMCOperationError(operation='Configure VIOM', error=e)
def _prepare_floppy_image(task, params): """Prepares the floppy image for passing the parameters. This method prepares a temporary vfat filesystem image, which contains the parameters to be passed to the ramdisk. Then it uploads the file NFS or CIFS server. :param task: a TaskManager instance containing the node to act on. :param params: a dictionary containing 'parameter name'->'value' mapping to be passed to the deploy ramdisk via the floppy image. :returns: floppy image filename :raises: ImageCreationFailed, if it failed while creating the floppy image. :raises: IRMCOperationError, if copying floppy image file failed. """ floppy_filename = _get_floppy_image_name(task.node) floppy_fullpathname = os.path.join(CONF.irmc.remote_image_share_root, floppy_filename) with tempfile.NamedTemporaryFile() as vfat_image_tmpfile_obj: images.create_vfat_image(vfat_image_tmpfile_obj.name, parameters=params) try: shutil.copyfile(vfat_image_tmpfile_obj.name, floppy_fullpathname) except IOError as e: operation = _("Copying floppy image file") raise exception.IRMCOperationError(operation=operation, error=e) return floppy_filename
def _attach_virtual_fd(node, floppy_image_filename): """Attaches virtual floppy on the node. :param node: an ironic node object. :raises: IRMCOperationError if insert virtual floppy failed. """ try: irmc_client = irmc_common.get_irmc_client(node) fd_set_params = scci.get_virtual_fd_set_params_cmd( CONF.irmc.remote_image_server, CONF.irmc.remote_image_user_domain, scci.get_share_type(CONF.irmc.remote_image_share_type), CONF.irmc.remote_image_share_name, floppy_image_filename, CONF.irmc.remote_image_user_name, CONF.irmc.remote_image_user_password) irmc_client(fd_set_params, async=False) irmc_client(scci.MOUNT_FD, async=False) except scci.SCCIClientError as irmc_exception: LOG.exception( "Error while inserting virtual floppy " "into node %(uuid)s. Error: %(error)s", { 'uuid': node.uuid, 'error': irmc_exception }) operation = _("Inserting virtual floppy") raise exception.IRMCOperationError(operation=operation, error=irmc_exception) LOG.info("Attached virtual floppy successfully" " for node %s", node.uuid)
def _attach_virtual_cd(node, bootable_iso_filename): """Attaches the given url as virtual media on the node. :param node: an ironic node object. :param bootable_iso_filename: a bootable ISO image to attach to. The iso file should be present in NFS/CIFS server. :raises: IRMCOperationError if attaching virtual media failed. """ try: irmc_client = irmc_common.get_irmc_client(node) cd_set_params = scci.get_virtual_cd_set_params_cmd( CONF.irmc.remote_image_server, CONF.irmc.remote_image_user_domain, scci.get_share_type(CONF.irmc.remote_image_share_type), CONF.irmc.remote_image_share_name, bootable_iso_filename, CONF.irmc.remote_image_user_name, CONF.irmc.remote_image_user_password) irmc_client(cd_set_params, async=False) irmc_client(scci.MOUNT_CD, async=False) except scci.SCCIClientError as irmc_exception: LOG.exception( "Error while inserting virtual cdrom " "into node %(uuid)s. Error: %(error)s", { 'uuid': node.uuid, 'error': irmc_exception }) operation = _("Inserting virtual cdrom") raise exception.IRMCOperationError(operation=operation, error=irmc_exception) LOG.info("Attached virtual cdrom successfully" " for node %s", node.uuid)
def _set_power_state(task, target_state): """Turns the server power on/off or do a reboot. :param task: a TaskManager instance containing the node to act on. :param target_state: target state of the node. :raises: InvalidParameterValue if an invalid power state was specified. :raises: MissingParameterValue if some mandatory information is missing on the node :raises: IRMCOperationError on an error from SCCI """ node = task.node irmc_client = irmc_common.get_irmc_client(node) try: irmc_client(STATES_MAP[target_state]) except KeyError: msg = _("_set_power_state called with invalid power state " "'%s'") % target_state raise exception.InvalidParameterValue(msg) except scci.SCCIClientError as irmc_exception: LOG.error( _LE("iRMC set_power_state failed to set state to %(tstate)s " " for node %(node_id)s with error: %(error)s"), { 'tstate': target_state, 'node_id': node.uuid, 'error': irmc_exception }) operation = _('iRMC set_power_state') raise exception.IRMCOperationError(operation=operation, error=irmc_exception)
def _restore_bios_config(task): """Restore BIOS config to a node. :param task: a TaskManager instance containing the node to act on. :raises: IRMCOperationError if the operation fails. """ node_uuid = task.node.uuid # Get bios config stored in the node object bios_config = task.node.driver_internal_info.get('irmc_bios_config') if not bios_config: LOG.info( 'Skipped operation "restore BIOS config" on node %s ' 'as the backup data not found.', node_uuid) return def _remove_bios_config(task, reboot_flag=False): """Remove backup bios config from the node.""" internal_info = task.node.driver_internal_info internal_info.pop('irmc_bios_config', None) # NOTE(tiendc): If reboot flag is raised, then the BM will # reboot and cause a bug if the next clean step is in-band. # See https://storyboard.openstack.org/#!/story/2002731 if reboot_flag: internal_info['cleaning_reboot'] = True task.node.driver_internal_info = internal_info task.node.save() irmc_info = irmc_common.parse_driver_info(task.node) try: # Restore bios config irmc.elcm.restore_bios_config(irmc_info, bios_config) except irmc.scci.SCCIError as e: # If the input bios config is not correct or corrupted, then # we should remove it from the node object. if isinstance(e, irmc.scci.SCCIInvalidInputError): _remove_bios_config(task) LOG.error( 'Failed to restore BIOS config on node %(node)s. ' 'Error: %(error)s', { 'node': node_uuid, 'error': e }) raise exception.IRMCOperationError(operation='restore BIOS config', error=e) # Remove the backup data after restoring _remove_bios_config(task, reboot_flag=True) LOG.info('BIOS config is restored successfully on node %s', node_uuid) # Change power state to ON as server is automatically # shutdown after the operation. manager_utils.node_power_action(task, states.POWER_ON)
def _get_raid_adapter(node): """Get the RAID adapter info on a RAID controller. :param node: an ironic node object. :returns: RAID adapter dictionary, None otherwise. :raises: IRMCOperationError on an error from python-scciclient. """ irmc_info = node.driver_info LOG.info('iRMC driver is gathering RAID adapter info for node %s', node.uuid) try: return client.elcm.get_raid_adapter(irmc_info) except client.elcm.ELCMProfileNotFound: reason = ('Cannot find any RAID profile in "%s"' % node.uuid) raise exception.IRMCOperationError(operation='RAID config', error=reason)
def _cleanup_boot_from_volume(self, task, reboot=False): """Clear remote boot configuration. :param task: a task from TaskManager. :param reboot: True if reboot node soon :raises: IRMCOperationError if iRMC operation failed """ irmc_info = irmc_common.parse_driver_info(task.node) try: viom_conf = viom.VIOMConfiguration(irmc_info, task.node.uuid) viom_conf.terminate(reboot=reboot) except scci.SCCIError as e: LOG.error('iRMC failed to terminate VIOM configuration from ' 'node %(node)s: %(error)s', {'node': task.node.uuid, 'error': e}) raise exception.IRMCOperationError(operation='Terminate VIOM', error=e)
def get_secure_boot_mode(node): """Get the current secure boot mode. :param node: An ironic node object. :raises: UnsupportedDriverExtension if secure boot is not present. :raises: IRMCOperationError if the operation fails. """ driver_info = parse_driver_info(node) try: return elcm.get_secure_boot_mode(driver_info) except elcm.SecureBootConfigNotFound: raise exception.UnsupportedDriverExtension( driver=node.driver, extension='get_secure_boot_state') except scci.SCCIError as irmc_exception: LOG.error("Failed to get secure boot for node %s", node.uuid) raise exception.IRMCOperationError( operation=_("getting secure boot mode"), error=irmc_exception)
def _delete_raid_adapter(node): """Delete the RAID adapter info on a RAID controller. :param node: an ironic node object. :raises: IRMCOperationError if SCCI failed from python-scciclient. """ irmc_info = node.driver_info try: client.elcm.delete_raid_configuration(irmc_info) except client.scci.SCCIClientError as exc: LOG.error( 'iRMC driver failed to delete RAID configuration ' 'for node %(node_uuid)s. Reason: %(error)s.', { 'node_uuid': node.uuid, 'error': exc }) raise exception.IRMCOperationError(operation='RAID config', error=exc)
def inject_nmi(self, task): """Inject NMI, Non Maskable Interrupt. Inject NMI (Non Maskable Interrupt) for a node immediately. :param task: A TaskManager instance containing the node to act on. :raises: IRMCOperationError on an error from SCCI :returns: None """ node = task.node irmc_client = irmc_common.get_irmc_client(node) try: irmc_client(irmc.scci.POWER_RAISE_NMI) except irmc.scci.SCCIClientError as err: LOG.error('iRMC Inject NMI failed for node %(node)s: %(err)s.', {'node': node.uuid, 'err': err}) raise exception.IRMCOperationError( operation=irmc.scci.POWER_RAISE_NMI, error=err)
def apply_configuration(self, task, settings): """Applies BIOS configuration on the given node. This method takes the BIOS settings from the settings param and applies BIOS configuration on the given node. After the BIOS configuration is done, self.cache_bios_settings() may be called to sync the node's BIOS-related information with the BIOS configuration applied on the node. It will also validate the given settings before applying any settings and manage failures when setting an invalid BIOS config. In the case of needing password to update the BIOS config, it will be taken from the driver_info properties. :param task: a TaskManager instance. :param settings: Dictionary containing the BIOS configuration. It may be an empty dictionary as well. :raises: IRMCOperationError,if apply bios settings failed. """ irmc_info = irmc_common.parse_driver_info(task.node) try: LOG.info( 'Apply BIOS configuration for node %(node_uuid)s: ' '%(settings)s', { 'settings': settings, 'node_uuid': task.node.uuid }) irmc.elcm.set_bios_configuration(irmc_info, settings) # NOTE(trungnv): Fix failed cleaning during rebooting node # when combine OOB and IB steps in manual clean. self._resume_cleaning(task) except irmc.scci.SCCIError as e: LOG.error( 'Failed to apply BIOS configuration on node ' '%(node_uuid)s. Error: %(error)s', { 'node_uuid': task.node.uuid, 'error': e }) raise exception.IRMCOperationError( operation='Apply BIOS configuration', error=e)
def _detach_virtual_fd(node): """Detaches virtual media floppy on the node. :param node: an ironic node object. :raises: IRMCOperationError if eject virtual media floppy failed. """ try: irmc_client = irmc_common.get_irmc_client(node) irmc_client(scci.UNMOUNT_FD) except scci.SCCIClientError as irmc_exception: LOG.exception(_LE("Error while ejecting virtual floppy " "from node %(uuid)s. Error: %(error)s"), {'uuid': node.uuid, 'error': irmc_exception}) operation = _("Ejecting virtual floppy") raise exception.IRMCOperationError(operation=operation, error=irmc_exception) LOG.info(_LI("Detached virtual floppy successfully" " for node %s"), node.uuid)
def set_secure_boot_mode(node, enable): """Enable or disable UEFI Secure Boot :param node: An ironic node object. :param enable: Boolean value. True if the secure boot to be enabled. :raises: IRMCOperationError if the operation fails. """ driver_info = parse_driver_info(node) try: elcm.set_secure_boot_mode(driver_info, enable) LOG.info("Set secure boot to %(flag)s for node %(node)s", { 'flag': enable, 'node': node.uuid }) except scci.SCCIError as irmc_exception: LOG.error("Failed to set secure boot to %(flag)s for node %(node)s", { 'flag': enable, 'node': node.uuid }) raise exception.IRMCOperationError( operation=_("setting secure boot mode"), error=irmc_exception)
def backup_bios_config(task): """Backup BIOS config from a node. :param task: a TaskManager instance containing the node to act on. :raises: IRMCOperationError on failure. """ node_uuid = task.node.uuid # Skip this operation if the clean step 'restore' is disabled if CONF.irmc.clean_priority_restore_irmc_bios_config == 0: LOG.debug( 'Skipped the operation backup_BIOS_config for node %s ' 'as the clean step restore_BIOS_config is disabled.', node_uuid) return irmc_info = irmc_common.parse_driver_info(task.node) try: # Backup bios config result = irmc.elcm.backup_bios_config(irmc_info) except irmc.scci.SCCIError as e: LOG.error( 'Failed to backup BIOS config for node %(node)s. ' 'Error: %(error)s', { 'node': node_uuid, 'error': e }) raise exception.IRMCOperationError(operation='backup BIOS config', error=e) # Save bios config into the driver_internal_info internal_info = task.node.driver_internal_info internal_info['irmc_bios_config'] = result['bios_config'] task.node.driver_internal_info = internal_info task.node.save() LOG.info('BIOS config is backed up successfully for node %s', node_uuid)
def _wait_power_state(task, target_state, timeout=None): """Wait for having changed to the target power state. :param task: A TaskManager instance containing the node to act on. :raises: IRMCOperationError if the target state acknowledge failed. :raises: SNMPFailure if SNMP request failed. """ node = task.node d_info = irmc_common.parse_driver_info(node) snmp_client = snmp.SNMPClient(d_info['irmc_address'], d_info['irmc_snmp_port'], d_info['irmc_snmp_version'], d_info['irmc_snmp_community'], d_info['irmc_snmp_security']) interval = CONF.irmc.snmp_polling_interval retry_timeout_soft = timeout or CONF.conductor.soft_power_off_timeout max_retry = int(retry_timeout_soft / interval) def _wait(mutable): mutable['boot_status_value'] = snmp_client.get(BOOT_STATUS_OID) LOG.debug( "iRMC SNMP agent of %(node_id)s returned " "boot status value %(bootstatus)s on attempt %(times)s.", { 'node_id': node.uuid, 'bootstatus': BOOT_STATUS[mutable['boot_status_value']], 'times': mutable['times'] }) if _is_expected_power_state(target_state, mutable['boot_status_value']): mutable['state'] = target_state raise loopingcall.LoopingCallDone() mutable['times'] += 1 if mutable['times'] > max_retry: mutable['state'] = states.ERROR raise loopingcall.LoopingCallDone() store = {'state': None, 'times': 0, 'boot_status_value': None} timer = loopingcall.FixedIntervalLoopingCall(_wait, store) timer.start(interval=interval).wait() if store['state'] == target_state: # iRMC acknowledged the target state node.last_error = None node.power_state = (states.POWER_OFF if target_state == states.SOFT_POWER_OFF else states.POWER_ON) node.target_power_state = states.NOSTATE node.save() LOG.info( 'iRMC successfully set node %(node_id)s ' 'power state to %(bootstatus)s.', { 'node_id': node.uuid, 'bootstatus': BOOT_STATUS[store['boot_status_value']] }) else: # iRMC failed to acknowledge the target state last_error = (_('iRMC returned unexpected boot status value %s') % BOOT_STATUS[store['boot_status_value']]) node.last_error = last_error node.power_state = states.ERROR node.target_power_state = states.NOSTATE node.save() LOG.error( 'iRMC failed to acknowledge the target state for node ' '%(node_id)s. Error: %(last_error)s', { 'node_id': node.uuid, 'last_error': last_error }) error = _('unexpected boot status value') raise exception.IRMCOperationError(operation=target_state, error=error)
def _validate_physical_disks(node, logical_disks): """Validate physical disks on a RAID configuration. :param node: an ironic node object. :param logical_disks: RAID info to set RAID configuration :raises: IRMCOperationError on an error. """ raid_adapter = _get_raid_adapter(node) physical_disk_dict = _get_physical_disk(node) if raid_adapter is None: reason = ('Cannot find any raid profile in "%s"' % node.uuid) raise exception.IRMCOperationError(operation='RAID config', error=reason) if physical_disk_dict is None: reason = ('Cannot find any physical disks in "%s"' % node.uuid) raise exception.IRMCOperationError(operation='RAID config', error=reason) valid_disks = raid_adapter['Server']['HWConfigurationIrmc']['Adapters'][ 'RAIDAdapter'][0]['PhysicalDisks'] if valid_disks is None: reason = ('Cannot find any HDD over in the node "%s"' % node.uuid) raise exception.IRMCOperationError(operation='RAID config', error=reason) valid_disk_slots = [slot['Slot'] for slot in valid_disks['PhysicalDisk']] remain_valid_disk_slots = list(valid_disk_slots) number_of_valid_disks = len(valid_disk_slots) used_valid_disk_slots = [] for disk in logical_disks: # Check raid_level value in the target_raid_config of node if disk.get('raid_level') not in RAID_LEVELS: reason = ('RAID level is not supported: "%s"' % disk.get('raid_level')) raise exception.IRMCOperationError(operation='RAID config', error=reason) min_disk_value = RAID_LEVELS[disk['raid_level']]['min_disks'] max_disk_value = RAID_LEVELS[disk['raid_level']]['max_disks'] remain_valid_disks = number_of_valid_disks - min_disk_value number_of_valid_disks = number_of_valid_disks - min_disk_value if remain_valid_disks < 0: reason = ('Physical disks do not enough slots for raid "%s"' % disk['raid_level']) raise exception.IRMCOperationError(operation='RAID config', error=reason) if 'physical_disks' in disk: type_of_disks = [] number_of_physical_disks = len(disk['physical_disks']) # Check number of physical disks along with raid level if number_of_physical_disks > max_disk_value: reason = ("Too many disks requested for RAID level %(level)s, " "maximum is %(max)s", { 'level': disk['raid_level'], 'max': max_disk_value }) raise exception.InvalidParameterValue(err=reason) if number_of_physical_disks < min_disk_value: reason = ("Not enough disks requested for RAID level " "%(level)s, minimum is %(min)s ", { 'level': disk['raid_level'], 'min': min_disk_value }) raise exception.IRMCOperationError(operation='RAID config', error=reason) # Check physical disks in valid disk slots for phys_disk in disk['physical_disks']: if int(phys_disk) not in valid_disk_slots: reason = ("Incorrect physical disk %(disk)s, correct are " "%(valid)s", { 'disk': phys_disk, 'valid': valid_disk_slots }) raise exception.IRMCOperationError(operation='RAID config', error=reason) type_of_disks.append(physical_disk_dict[int(phys_disk)]) if physical_disk_dict[int(phys_disk)] != type_of_disks[0]: reason = ('Cannot create RAID configuration with ' 'different hard drives type %s' % physical_disk_dict[int(phys_disk)]) raise exception.IRMCOperationError(operation='RAID config', error=reason) # Check physical disk values with used disk slots if int(phys_disk) in used_valid_disk_slots: reason = ( "Disk %s is already used in a RAID configuration" % disk['raid_level']) raise exception.IRMCOperationError(operation='RAID config', error=reason) used_valid_disk_slots.append(int(phys_disk)) remain_valid_disk_slots.remove(int(phys_disk)) if disk['size_gb'] != 'MAX': # Validate size_gb value input _validate_logical_drive_capacity(disk, valid_disks)