def test_expire_old_sessions(self, mock_sushy): for num in range(20): self.node.driver_info['redfish_username'] = '******' % num redfish_utils.get_system(self.node) self.assertEqual(mock_sushy.call_count, 20) self.assertEqual(len(redfish_utils.SessionCache.sessions), 10)
def test_auth_auto(self, mock_sushy): redfish_utils.get_system(self.node) mock_sushy.assert_called_with( self.parsed_driver_info['address'], username=self.parsed_driver_info['username'], password=self.parsed_driver_info['password'], verify=True)
def test_disabled_sessions_cache(self, mock_sushy): cfg.CONF.set_override('connection_cache_size', 0, 'redfish') for num in range(2): self.node.driver_info['redfish_username'] = '******' % num redfish_utils.get_system(self.node) self.assertEqual(mock_sushy.call_count, 2) self.assertEqual(len(redfish_utils.SessionCache._sessions), 0)
def test_ensure_basic_session_caching(self, mock_auth, mock_sushy): self.node.driver_info['redfish_auth_type'] = 'basic' mock_session_or_basic_auth = mock_auth['auto'] redfish_utils.get_system(self.node) mock_sushy.assert_called_with( mock.ANY, verify=mock.ANY, auth=mock_session_or_basic_auth.return_value, ) self.assertEqual(len(redfish_utils.SessionCache._sessions), 1)
def test_auth_auto(self, mock_auth, mock_sushy): redfish_utils.get_system(self.node) mock_session_or_basic_auth = mock_auth['auto'] mock_session_or_basic_auth.assert_called_with( username=self.parsed_driver_info['username'], password=self.parsed_driver_info['password']) mock_sushy.assert_called_with( self.parsed_driver_info['address'], auth=mock_session_or_basic_auth.return_value, verify=True)
def test_auth_basic(self, mock_auth, mock_sushy): self.node.driver_info['redfish_auth_type'] = 'basic' mock_basic_auth = mock_auth['basic'] redfish_utils.get_system(self.node) mock_basic_auth.assert_called_with( username=self.parsed_driver_info['username'], password=self.parsed_driver_info['password']) sushy.Sushy.assert_called_with(mock.ANY, verify=mock.ANY, auth=mock_basic_auth.return_value)
def test_auth_auto(self, mock_auth, mock_sushy): redfish_utils.get_system(self.node) mock_session_or_basic_auth = mock_auth['auto'] mock_session_or_basic_auth.assert_called_with( username=self.parsed_driver_info['username'], password=self.parsed_driver_info['password'] ) mock_sushy.assert_called_with( self.parsed_driver_info['address'], auth=mock_session_or_basic_auth.return_value, verify=True)
def test_auth_basic(self, mock_auth, mock_sushy): self.node.driver_info['redfish_auth_type'] = 'basic' mock_basic_auth = mock_auth['basic'] redfish_utils.get_system(self.node) mock_basic_auth.assert_called_with( username=self.parsed_driver_info['username'], password=self.parsed_driver_info['password'] ) sushy.Sushy.assert_called_with( mock.ANY, verify=mock.ANY, auth=mock_basic_auth.return_value )
def clean_up_ramdisk(self, task): """Cleans up the boot of ironic ramdisk. This method cleans up the environment that was setup for booting the deploy ramdisk. :param task: A task from TaskManager. :returns: None """ d_info = _parse_driver_info(task.node) config_via_floppy = d_info.get('config_via_floppy') LOG.debug("Cleaning up deploy boot for " "%(node)s", {'node': task.node.uuid}) managers = redfish_utils.get_system(task.node).managers _eject_vmedia(task, managers, sushy.VIRTUAL_MEDIA_CD) image_utils.cleanup_iso_image(task) if (config_via_floppy and _has_vmedia_device(managers, sushy.VIRTUAL_MEDIA_FLOPPY)): _eject_vmedia(task, managers, sushy.VIRTUAL_MEDIA_FLOPPY) image_utils.cleanup_floppy_image(task)
def get_boot_device(self, task): """Get the current boot device for a node. :param task: a task from TaskManager. :raises: InvalidParameterValue on malformed parameter(s) :raises: MissingParameterValue on missing parameter(s) :raises: RedfishConnectionError when it fails to connect to Redfish :raises: RedfishError on an error from the Sushy library :returns: a dictionary containing: :boot_device: the boot device, one of :mod:`ironic.common.boot_devices` or None if it is unknown. :persistent: Boolean value or None, True if the boot device persists, False otherwise. None if it's unknown. """ system = redfish_utils.get_system(task.node) return { 'boot_device': BOOT_DEVICE_MAP.get(system.boot.get('target')), 'persistent': BOOT_DEVICE_PERSISTENT_MAP.get(system.boot.get('enabled')) }
def set_boot_mode(self, task, mode): """Set the boot mode for a node. Set the boot mode to use on next reboot of the node. :param task: A task from TaskManager. :param mode: The boot mode, one of :mod:`ironic.common.boot_modes`. :raises: InvalidParameterValue if an invalid boot mode is specified. :raises: MissingParameterValue if a required parameter is missing :raises: RedfishConnectionError when it fails to connect to Redfish :raises: RedfishError on an error from the Sushy library """ system = redfish_utils.get_system(task.node) try: system.set_system_boot_options(mode=BOOT_MODE_MAP_REV[mode]) except sushy.exceptions.SushyError as e: error_msg = (_('Setting boot mode to %(mode)s ' 'failed for node %(node)s. ' 'Error: %(error)s') % { 'node': task.node.uuid, 'mode': mode, 'error': e }) LOG.error(error_msg) raise exception.RedfishError(error=error_msg)
def set_power_state(self, task, power_state, timeout=None): """Set the power state of the task's node. :param task: a TaskManager instance containing the node to act on. :param power_state: Any power state from :mod:`ironic.common.states`. :param timeout: Time to wait for the node to reach the requested state. :raises: MissingParameterValue if a required parameter is missing. :raises: RedfishConnectionError when it fails to connect to Redfish :raises: RedfishError on an error from the Sushy library """ system = redfish_utils.get_system(task.node) if (power_state in (states.POWER_ON, states.SOFT_REBOOT, states.REBOOT) and isinstance(task.driver.management, redfish_mgmt.RedfishManagement)): task.driver.management.restore_boot_device(task, system) try: _set_power_state(task, system, power_state, timeout=timeout) except sushy.exceptions.SushyError as e: error_msg = (_('Setting power state to %(state)s failed for node ' '%(node)s. Error: %(error)s') % { 'node': task.node.uuid, 'state': power_state, 'error': e }) LOG.error(error_msg) raise exception.RedfishError(error=error_msg)
def clean_up_instance(self, task): """Cleans up the boot of instance. This method cleans up the environment that was setup for booting the instance. :param task: A task from TaskManager. :returns: None """ LOG.debug("Cleaning up instance boot for " "%(node)s", {'node': task.node.uuid}) managers = redfish_utils.get_system(task.node).managers _eject_vmedia(task, managers, sushy.VIRTUAL_MEDIA_CD) d_info = task.node.driver_info config_via_floppy = d_info.get('config_via_floppy') if config_via_floppy: _eject_vmedia(task, managers, sushy.VIRTUAL_MEDIA_FLOPPY) boot_option = deploy_utils.get_boot_option(task.node) if (boot_option == 'ramdisk' and task.node.instance_info.get('configdrive')): _eject_vmedia(task, managers, sushy.VIRTUAL_MEDIA_USBSTICK) image_utils.cleanup_disk_image(task, prefix='configdrive') image_utils.cleanup_iso_image(task)
def set_power_state(self, task, power_state, timeout=None): """Set the power state of the task's node. :param task: a TaskManager instance containing the node to act on. :param power_state: Any power state from :mod:`ironic.common.states`. :param timeout: Time to wait for the node to reach the requested state. :raises: MissingParameterValue if a required parameter is missing. :raises: RedfishConnectionError when it fails to connect to Redfish :raises: RedfishError on an error from the Sushy library """ system = redfish_utils.get_system(task.node) try: system.reset_system(SET_POWER_STATE_MAP.get(power_state)) except sushy.exceptions.SushyError as e: error_msg = (_('Redfish set power state failed for node ' '%(node)s. Error: %(error)s') % { 'node': task.node.uuid, 'error': e }) LOG.error(error_msg) raise exception.RedfishError(error=error_msg) target_state = TARGET_STATE_MAP.get(power_state, power_state) cond_utils.node_wait_for_power_state(task, target_state, timeout=timeout)
def _eject_vmedia(task, boot_device=None): """Eject virtual CDs and DVDs :param task: A task from TaskManager. :param boot_device: sushy boot device e.g. `VIRTUAL_MEDIA_CD`, `VIRTUAL_MEDIA_DVD` or `VIRTUAL_MEDIA_FLOPPY` or `None` to eject everything (default). :raises: InvalidParameterValue, if no suitable virtual CD or DVD is found on the node. """ system = redfish_utils.get_system(task.node) for manager in system.managers: for v_media in manager.virtual_media.get_members(): if boot_device and boot_device not in v_media.media_types: continue inserted = v_media.inserted if inserted: v_media.eject_media() LOG.info( "Boot media is%(already)s ejected from " "%(boot_device)s for node %(node)s" "", { 'node': task.node.uuid, 'already': '' if inserted else ' already', 'boot_device': v_media.name })
def test_get_system(self, mock_sushy): fake_conn = mock_sushy.return_value fake_system = fake_conn.get_system.return_value response = redfish_utils.get_system(self.node) self.assertEqual(fake_system, response) fake_conn.get_system.assert_called_once_with( '/redfish/v1/Systems/FAKESYSTEM')
def cache_bios_settings(self, task): """Store or update the current BIOS settings for the node. Get the current BIOS settings and store them in the bios_settings database table. :param task: a TaskManager instance containing the node to act on. :raises: RedfishConnectionError when it fails to connect to Redfish :raises: RedfishError on an error from the Sushy library """ node_id = task.node.id system = redfish_utils.get_system(task.node) attributes = system.bios.attributes settings = [] # Convert Redfish BIOS attributes to Ironic BIOS settings if attributes: settings = [{'name': k, 'value': v} for k, v in attributes.items()] LOG.debug('Cache BIOS settings for node %(node_uuid)s', {'node_uuid': task.node.uuid}) create_list, update_list, delete_list, nochange_list = ( objects.BIOSSettingList.sync_node_setting(task.context, node_id, settings)) if create_list: objects.BIOSSettingList.create(task.context, node_id, create_list) if update_list: objects.BIOSSettingList.save(task.context, node_id, update_list) if delete_list: delete_names = [d['name'] for d in delete_list] objects.BIOSSettingList.delete(task.context, node_id, delete_names)
def reboot(self, task, timeout=None): """Perform a hard reboot of the task's node. :param task: a TaskManager instance containing the node to act on. :param timeout: Time to wait for the node to become powered on. :raises: MissingParameterValue if a required parameter is missing. :raises: RedfishConnectionError when it fails to connect to Redfish :raises: RedfishError on an error from the Sushy library """ system = redfish_utils.get_system(task.node) current_power_state = GET_POWER_STATE_MAP.get(system.power_state) try: if current_power_state == states.POWER_ON: system.reset_system(SET_POWER_STATE_MAP.get(states.REBOOT)) else: system.reset_system(SET_POWER_STATE_MAP.get(states.POWER_ON)) except sushy.exceptions.SushyError as e: error_msg = (_('Redfish reboot failed for node %(node)s. ' 'Error: %(error)s') % {'node': task.node.uuid, 'error': e}) LOG.error(error_msg) raise exception.RedfishError(error=error_msg) cond_utils.node_wait_for_power_state(task, states.POWER_ON, timeout=timeout)
def reboot(self, task, timeout=None): """Perform a hard reboot of the task's node. :param task: a TaskManager instance containing the node to act on. :param timeout: Time to wait for the node to become powered on. :raises: MissingParameterValue if a required parameter is missing. :raises: RedfishConnectionError when it fails to connect to Redfish :raises: RedfishError on an error from the Sushy library """ system = redfish_utils.get_system(task.node) current_power_state = GET_POWER_STATE_MAP.get(system.power_state) try: if current_power_state == states.POWER_ON: next_state = states.POWER_OFF _set_power_state(task, system, next_state, timeout=timeout) next_state = states.POWER_ON _set_power_state(task, system, next_state, timeout=timeout) except sushy.exceptions.SushyError as e: error_msg = (_('Reboot failed for node %(node)s when setting ' 'power state to %(state)s. Error: %(error)s') % { 'node': task.node.uuid, 'state': next_state, 'error': e }) LOG.error(error_msg) raise exception.RedfishError(error=error_msg)
def get_physical_disks(node): """Get the physical drives of the node. :param node: an ironic node object. :returns: a list of Drive objects from sushy :raises: RedfishConnectionError when it fails to connect to Redfish :raises: RedfishError if there is an error getting the drives via Redfish """ system = redfish_utils.get_system(node) disks = [] disk_to_controller = {} try: collection = system.storage for storage in collection.get_members(): disks.extend(storage.drives) controller = (storage.storage_controllers[0] if storage.storage_controllers else None) for drive in storage.drives: disk_to_controller[drive] = controller except sushy.exceptions.SushyError as exc: error_msg = _('Cannot get the list of physical disks for node ' '%(node_uuid)s. Reason: %(error)s.' % {'node_uuid': node.uuid, 'error': exc}) LOG.error(error_msg) raise exception.RedfishError(error=exc) return disks, disk_to_controller
def set_boot_device(self, task, device, persistent=False): """Set the boot device for a node. Set the boot device to use on next reboot of the node. :param task: a task from TaskManager. :param device: the boot device, one of :mod:`ironic.common.boot_devices`. :param persistent: Boolean value. True if the boot device will persist to all future boots, False if not. Default: False. :raises: InvalidParameterValue on malformed parameter(s) :raises: MissingParameterValue on missing parameter(s) :raises: RedfishConnectionError when it fails to connect to Redfish :raises: RedfishError on an error from the Sushy library """ system = redfish_utils.get_system(task.node) try: system.set_system_boot_source( BOOT_DEVICE_MAP_REV[device], enabled=BOOT_DEVICE_PERSISTENT_MAP_REV[persistent]) except sushy.exceptions.SushyError as e: error_msg = (_('Redfish set boot device failed for node ' '%(node)s. Error: %(error)s') % {'node': task.node.uuid, 'error': e}) LOG.error(error_msg) raise exception.RedfishError(error=error_msg)
def set_boot_device(self, task, device, persistent=False): """Set the boot device for a node. Set the boot device to use on next reboot of the node. :param task: a task from TaskManager. :param device: the boot device, one of :mod:`ironic.common.boot_devices`. :param persistent: Boolean value. True if the boot device will persist to all future boots, False if not. Default: False. :raises: InvalidParameterValue on malformed parameter(s) :raises: MissingParameterValue on missing parameter(s) :raises: RedfishConnectionError when it fails to connect to Redfish :raises: RedfishError on an error from the Sushy library """ system = redfish_utils.get_system(task.node) try: system.set_system_boot_source( BOOT_DEVICE_MAP_REV[device], enabled=BOOT_DEVICE_PERSISTENT_MAP_REV[persistent]) except sushy.exceptions.SushyError as e: error_msg = (_('Redfish set boot device failed for node ' '%(node)s. Error: %(error)s') % { 'node': task.node.uuid, 'error': e }) LOG.error(error_msg) raise exception.RedfishError(error=error_msg)
def apply_configuration(self, task, settings): """Apply the BIOS settings to the node. :param task: a TaskManager instance containing the node to act on. :param settings: a list of BIOS settings to be updated. :raises: RedfishConnectionError when it fails to connect to Redfish :raises: RedfishError on an error from the Sushy library """ system = redfish_utils.get_system(task.node) try: bios = system.bios except sushy.exceptions.MissingAttributeError: error_msg = (_('Redfish BIOS factory reset failed for node ' '%s, because BIOS settings are not supported.') % task.node.uuid) LOG.error(error_msg) raise exception.RedfishError(error=error_msg) # Convert Ironic BIOS settings to Redfish BIOS attributes attributes = {s['name']: s['value'] for s in settings} info = task.node.driver_internal_info reboot_requested = info.get('post_config_reboot_requested') if not reboot_requested: # Step 1: Apply settings and issue a reboot LOG.debug('Apply BIOS configuration for node %(node_uuid)s: ' '%(settings)r', {'node_uuid': task.node.uuid, 'settings': settings}) if bios.supported_apply_times and ( sushy.APPLY_TIME_ON_RESET in bios.supported_apply_times): apply_time = sushy.APPLY_TIME_ON_RESET else: apply_time = None try: bios.set_attributes(attributes, apply_time=apply_time) except sushy.exceptions.SushyError as e: error_msg = (_('Redfish BIOS apply configuration failed for ' 'node %(node)s. Error: %(error)s') % {'node': task.node.uuid, 'error': e}) LOG.error(error_msg) raise exception.RedfishError(error=error_msg) self.post_configuration(task, settings) self._set_reboot_requested(task, attributes) return deploy_utils.get_async_step_return_state(task.node) else: # Step 2: Verify requested BIOS settings applied requested_attrs = info.get('requested_bios_attrs') current_attrs = bios.attributes LOG.debug('Verify BIOS configuration for node %(node_uuid)s: ' '%(attrs)r', {'node_uuid': task.node.uuid, 'attrs': requested_attrs}) self._clear_reboot_requested(task) self._check_bios_attrs(task, current_attrs, requested_attrs)
def set_secure_boot_state(self, task, state): """Set the current secure boot state for the node. :param task: A task from TaskManager. :param state: A new state as a boolean. :raises: MissingParameterValue if a required parameter is missing :raises: RedfishError or its derivative in case of a driver runtime error. :raises: UnsupportedDriverExtension if secure boot is not supported by the hardware. """ system = redfish_utils.get_system(task.node) try: sb = system.secure_boot except sushy.exceptions.MissingAttributeError: LOG.error( 'Secure boot has been requested for node %s but its ' 'Redfish BMC does not have a SecureBoot object', task.node.uuid) raise exception.UnsupportedDriverExtension( driver=task.node.driver, extension='set_secure_boot_state') if sb.enabled == state: LOG.info( 'Secure boot state for node %(node)s is already ' '%(value)s', { 'node': task.node.uuid, 'value': state }) return boot_mode = system.boot.get('mode') if boot_mode == sushy.BOOT_SOURCE_MODE_BIOS: # NOTE(dtantsur): the case of disabling secure boot when boot mode # is legacy should be covered by the check above. msg = (_("Configuring secure boot requires UEFI for node %s") % task.node.uuid) LOG.error(msg) raise exception.RedfishError(error=msg) try: sb.set_enabled(state) except sushy.exceptions.SushyError as exc: msg = (_('Failed to set secure boot state on node %(node)s to ' '%(value)s: %(exc)s') % { 'node': task.node.uuid, 'value': state, 'exc': exc }) LOG.error(msg) raise exception.RedfishError(error=msg) else: LOG.info( 'Secure boot state for node %(node)s has been set to ' '%(value)s', { 'node': task.node.uuid, 'value': state })
def set_indicator_state(self, task, component, indicator, state): """Set indicator on the hardware component to the desired state. :param task: A task from TaskManager. :param component: The hardware component, one of :mod:`ironic.common.components`. :param indicator: Indicator ID (as reported by `get_supported_indicators`). :param state: Desired state of the indicator, one of :mod:`ironic.common.indicator_states`. :raises: InvalidParameterValue if an invalid component, indicator or state is specified. :raises: MissingParameterValue if a required parameter is missing :raises: RedfishError on an error from the Sushy library """ system = redfish_utils.get_system(task.node) try: if (component == components.SYSTEM and indicator == system.uuid): system.set_indicator_led(INDICATOR_MAP_REV[state]) return elif (component == components.CHASSIS and system.chassis): for chassis in system.chassis: if chassis.uuid == indicator: chassis.set_indicator_led( INDICATOR_MAP_REV[state]) return elif (component == components.DISK and system.simple_storage and system.simple_storage.drives): for drive in system.simple_storage.drives: if drive.uuid == indicator: drive.set_indicator_led( INDICATOR_MAP_REV[state]) return except sushy.exceptions.SushyError as e: error_msg = (_('Redfish set %(component)s indicator %(indicator)s ' 'state %(state)s failed for node %(node)s. Error: ' '%(error)s') % {'component': component, 'indicator': indicator, 'state': state, 'node': task.node.uuid, 'error': e}) LOG.error(error_msg) raise exception.RedfishError(error=error_msg) raise exception.MissingParameterValue(_( "Unknown indicator %(indicator)s for component %(component)s of " "node %(uuid)s") % {'indicator': indicator, 'component': component, 'uuid': task.node.uuid})
def get_sensors_data(self, task): """Get sensors data. :param task: a TaskManager instance. :raises: FailedToGetSensorData when getting the sensor data fails. :raises: FailedToParseSensorData when parsing sensor data fails. :raises: InvalidParameterValue if required parameters are missing. :raises: MissingParameterValue if a required parameter is missing. :returns: returns a dict of sensor data grouped by sensor type. """ node = task.node sensors = collections.defaultdict(dict) system = redfish_utils.get_system(node) for chassis in system.chassis: try: sensors['Fan'].update(self._get_sensors_fan(chassis)) except sushy.exceptions.SushyError as exc: LOG.debug("Failed reading fan information for node " "%(node)s: %(error)s", {'node': node.uuid, 'error': exc}) try: sensors['Temperature'].update( self._get_sensors_temperatures(chassis)) except sushy.exceptions.SushyError as exc: LOG.debug("Failed reading temperature information for node " "%(node)s: %(error)s", {'node': node.uuid, 'error': exc}) try: sensors['Power'].update(self._get_sensors_power(chassis)) except sushy.exceptions.SushyError as exc: LOG.debug("Failed reading power information for node " "%(node)s: %(error)s", {'node': node.uuid, 'error': exc}) try: sensors['Drive'].update(self._get_sensors_drive(system)) except sushy.exceptions.SushyError as exc: LOG.debug("Failed reading drive information for node " "%(node)s: %(error)s", {'node': node.uuid, 'error': exc}) LOG.debug("Gathered sensor data: %(sensors)s", {'sensors': sensors}) return sensors
def get_power_state(self, task): """Get the current power state of the task's node. :param task: a TaskManager instance containing the node to act on. :returns: a power state. One of :mod:`ironic.common.states`. :raises: InvalidParameterValue on malformed parameter(s) :raises: MissingParameterValue on missing parameter(s) :raises: RedfishConnectionError when it fails to connect to Redfish :raises: RedfishError on an error from the Sushy library """ system = redfish_utils.get_system(task.node) return GET_POWER_STATE_MAP.get(system.power_state)
def eject_vmedia(task, boot_device=None): """Eject virtual CDs and DVDs :param task: A task from TaskManager. :param boot_device: sushy boot device e.g. `VIRTUAL_MEDIA_CD`, `VIRTUAL_MEDIA_DVD` or `VIRTUAL_MEDIA_FLOPPY` or `None` to eject everything (default). :raises: InvalidParameterValue, if no suitable virtual CD or DVD is found on the node. """ system = redfish_utils.get_system(task.node) _eject_vmedia(task, system.managers, boot_device=boot_device)
def create_virtual_disk(task, raid_controller, physical_disks, raid_level, size_bytes, disk_name=None, span_length=None, span_depth=None, error_handler=None): """Create a single virtual disk on a RAID controller. :param task: TaskManager object containing the node. :param raid_controller: id of the RAID controller. :param physical_disks: ids of the physical disks. :param raid_level: RAID level of the virtual disk. :param size_bytes: size of the virtual disk. :param disk_name: name of the virtual disk. (optional) :param span_depth: Number of spans in virtual disk. (optional) :param span_length: Number of disks per span. (optional) :param error_handler: function to call if volume create fails. (optional) :returns: Newly created Volume resource or TaskMonitor if async task. :raises: RedfishConnectionError when it fails to connect to Redfish. :raises: RedfishError if there is an error creating the virtual disk. """ node = task.node system = redfish_utils.get_system(node) storage = _get_storage_controller(node, system, physical_disks) if not storage: reason = _('No storage controller found for node %(node_uuid)s' % {'node_uuid': node.uuid}) raise exception.RedfishError(error=reason) volume_collection = storage.volumes apply_time = None apply_time_support = volume_collection.operation_apply_time_support if apply_time_support and apply_time_support.mapped_supported_values: supported_values = apply_time_support.mapped_supported_values if sushy.APPLY_TIME_IMMEDIATE in supported_values: apply_time = sushy.APPLY_TIME_IMMEDIATE elif sushy.APPLY_TIME_ON_RESET in supported_values: apply_time = sushy.APPLY_TIME_ON_RESET payload = _construct_volume_payload( node, storage, raid_controller, physical_disks, raid_level, size_bytes, disk_name=disk_name, span_length=span_length, span_depth=span_depth) try: return volume_collection.create(payload, apply_time=apply_time) except sushy.exceptions.SushyError as exc: msg = ('Redfish driver failed to create virtual disk for node ' '%(node_uuid)s. Reason: %(error)s.') if error_handler: try: return error_handler(task, exc, volume_collection, payload) except sushy.exceptions.SushyError as exc: LOG.error(msg, {'node_uuid': node.uuid, 'error': exc}) raise exception.RedfishError(error=exc) LOG.error(msg, {'node_uuid': node.uuid, 'error': exc}) raise exception.RedfishError(error=exc)
def clear_job_queue(self, task): """Clear iDRAC job queue. :param task: a TaskManager instance containing the node to act on. :raises: RedfishError on an error. """ system = redfish_utils.get_system(task.node) for manager in system.managers: try: oem_manager = manager.get_oem_extension('Dell') except sushy.exceptions.OEMExtensionNotFoundError as e: error_msg = (_("Search for Sushy OEM extension Python package " "'sushy-oem-idrac' failed for node %(node)s. " "Ensure it is installed. Error: %(error)s") % { 'node': task.node.uuid, 'error': e }) LOG.error(error_msg) raise exception.RedfishError(error=error_msg) try: oem_manager.job_service.delete_jobs(job_ids=['JID_CLEARALL']) except sushy.exceptions.SushyError as e: error_msg = ( 'Failed to clear iDRAC job queue with system ' '%(system)s manager %(manager)s for node ' '%(node)s. Will try next manager, if available. ' 'Error: %(error)s' % { 'system': system.uuid if system.uuid else system.identity, 'manager': manager.uuid if manager.uuid else manager.identity, 'node': task.node.uuid, 'error': e }) LOG.debug(error_msg) continue LOG.info('Cleared iDRAC job queue for node %(node)s', {'node': task.node.uuid}) break else: error_msg = ( _('iDRAC Redfish clear job queue failed for node ' '%(node)s, because system %(system)s has no ' 'manager%(no_manager)s.') % { 'node': task.node.uuid, 'system': system.uuid if system.uuid else system.identity, 'no_manager': '' if not system.managers else ' which could' }) LOG.error(error_msg) raise exception.RedfishError(error=error_msg)
def _has_vmedia_device(task, boot_device): """Indicate if device exists at any of the managers :param task: A task from TaskManager. :param boot_device: sushy boot device e.g. `VIRTUAL_MEDIA_CD`, `VIRTUAL_MEDIA_DVD` or `VIRTUAL_MEDIA_FLOPPY`. """ system = redfish_utils.get_system(task.node) for manager in system.managers: for v_media in manager.virtual_media.get_members(): if boot_device in v_media.media_types: return True
def reset_idrac(self, task): """Reset the iDRAC. :param task: a TaskManager instance containing the node to act on. :raises: RedfishError on an error. """ system = redfish_utils.get_system(task.node) for manager in system.managers: try: oem_manager = manager.get_oem_extension('Dell') except sushy.exceptions.OEMExtensionNotFoundError as e: error_msg = (_("Search for Sushy OEM extension Python package " "'sushy-oem-idrac' failed for node %(node)s. " "Ensure it is installed. Error: %(error)s") % { 'node': task.node.uuid, 'error': e }) LOG.error(error_msg) raise exception.RedfishError(error=error_msg) try: oem_manager.reset_idrac() except sushy.exceptions.SushyError as e: error_msg = ( 'Failed to reset iDRAC with system %(system)s ' 'manager %(manager)s for node %(node)s. Will try ' 'next manager, if available. Error: %(error)s' % { 'system': system.uuid if system.uuid else system.identity, 'manager': manager.uuid if manager.uuid else manager.identity, 'node': task.node.uuid, 'error': e }) LOG.debug(error_msg) continue redfish_utils.wait_until_get_system_ready(task.node) LOG.info('Reset iDRAC for node %(node)s', {'node': task.node.uuid}) break else: error_msg = ( _('iDRAC Redfish reset iDRAC failed for node ' '%(node)s, because system %(system)s has no ' 'manager%(no_manager)s.') % { 'node': task.node.uuid, 'system': system.uuid if system.uuid else system.identity, 'no_manager': '' if not system.managers else ' which could' }) LOG.error(error_msg) raise exception.RedfishError(error=error_msg)
def detect_vendor(self, task): """Detects and returns the hardware vendor. Uses the System's Manufacturer field. :param task: A task from TaskManager. :raises: InvalidParameterValue if an invalid component, indicator or state is specified. :raises: MissingParameterValue if a required parameter is missing :raises: RedfishError on driver-specific problems. :returns: String representing the BMC reported Vendor or Manufacturer, otherwise returns None. """ return redfish_utils.get_system(task.node).manufacturer
def get_indicator_state(self, task, component, indicator): """Get current state of the indicator of the hardware component. :param task: A task from TaskManager. :param component: The hardware component, one of :mod:`ironic.common.components`. :param indicator: Indicator ID (as reported by `get_supported_indicators`). :raises: MissingParameterValue if a required parameter is missing :raises: RedfishError on an error from the Sushy library :returns: Current state of the indicator, one of :mod:`ironic.common.indicator_states`. """ system = redfish_utils.get_system(task.node) try: if (component == components.SYSTEM and indicator == system.uuid): return INDICATOR_MAP[system.indicator_led] if (component == components.CHASSIS and system.chassis): for chassis in system.chassis: if chassis.uuid == indicator: return INDICATOR_MAP[chassis.indicator_led] if (component == components.DISK and system.simple_storage and system.simple_storage.drives): for drive in system.simple_storage.drives: if drive.uuid == indicator: return INDICATOR_MAP[drive.indicator_led] except sushy.exceptions.SushyError as e: error_msg = (_('Redfish get %(component)s indicator %(indicator)s ' 'state failed for node %(node)s. Error: ' '%(error)s') % { 'component': component, 'indicator': indicator, 'node': task.node.uuid, 'error': e }) LOG.error(error_msg) raise exception.RedfishError(error=error_msg) raise exception.MissingParameterValue( _("Unknown indicator %(indicator)s for component %(component)s of " "node %(uuid)s") % { 'indicator': indicator, 'component': component, 'uuid': task.node.uuid })
def get_boot_mode(self, task): """Get the current boot mode for a node. Provides the current boot mode of the node. :param task: A task from TaskManager. :raises: MissingParameterValue if a required parameter is missing :raises: DriverOperationError or its derivative in case of driver runtime error. :returns: The boot mode, one of :mod:`ironic.common.boot_mode` or None if it is unknown. """ system = redfish_utils.get_system(task.node) return BOOT_MODE_MAP.get(system.boot.get('mode'))
def set_boot_mode(self, task, mode): """Set the boot mode for a node. Set the boot mode to use on next reboot of the node. :param task: A task from TaskManager. :param mode: The boot mode, one of :mod:`ironic.common.boot_modes`. :raises: InvalidParameterValue if an invalid boot mode is specified. :raises: MissingParameterValue if a required parameter is missing :raises: RedfishConnectionError when it fails to connect to Redfish :raises: RedfishError on an error from the Sushy library """ system = redfish_utils.get_system(task.node) boot_device = system.boot.get('target') if not boot_device: error_msg = (_('Cannot change boot mode on node %(node)s ' 'because its boot device is not set.') % {'node': task.node.uuid}) LOG.error(error_msg) raise exception.RedfishError(error_msg) boot_override = system.boot.get('enabled') if not boot_override: error_msg = (_('Cannot change boot mode on node %(node)s ' 'because its boot source override is not set.') % {'node': task.node.uuid}) LOG.error(error_msg) raise exception.RedfishError(error_msg) try: system.set_system_boot_source( boot_device, enabled=boot_override, mode=BOOT_MODE_MAP_REV[mode]) except sushy.exceptions.SushyError as e: error_msg = (_('Setting boot mode to %(mode)s ' 'failed for node %(node)s. ' 'Error: %(error)s') % {'node': task.node.uuid, 'mode': mode, 'error': e}) LOG.error(error_msg) raise exception.RedfishError(error=error_msg)
def apply_configuration(self, task, settings): """Apply the BIOS settings to the node. :param task: a TaskManager instance containing the node to act on. :param settings: a list of BIOS settings to be updated. :raises: RedfishConnectionError when it fails to connect to Redfish :raises: RedfishError on an error from the Sushy library """ system = redfish_utils.get_system(task.node) bios = system.bios # Convert Ironic BIOS settings to Redfish BIOS attributes attributes = {s['name']: s['value'] for s in settings} info = task.node.driver_internal_info reboot_requested = info.get('post_config_reboot_requested') if not reboot_requested: # Step 1: Apply settings and issue a reboot LOG.debug('Apply BIOS configuration for node %(node_uuid)s: ' '%(settings)r', {'node_uuid': task.node.uuid, 'settings': settings}) try: bios.set_attributes(attributes) except sushy.exceptions.SushyError as e: error_msg = (_('Redfish BIOS apply configuration failed for ' 'node %(node)s. Error: %(error)s') % {'node': task.node.uuid, 'error': e}) LOG.error(error_msg) raise exception.RedfishError(error=error_msg) self.post_configuration(task, settings) self._set_reboot_requested(task, attributes) return states.CLEANWAIT else: # Step 2: Verify requested BIOS settings applied requested_attrs = info.get('requested_bios_attrs') current_attrs = bios.attributes LOG.debug('Verify BIOS configuration for node %(node_uuid)s: ' '%(attrs)r', {'node_uuid': task.node.uuid, 'attrs': requested_attrs}) self._clear_reboot_requested(task) self._check_bios_attrs(task, current_attrs, requested_attrs)
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: InvalidParameterValue on malformed parameter(s) :raises: MissingParameterValue on missing parameter(s) :raises: RedfishConnectionError when it fails to connect to Redfish :raises: RedfishError on an error from the Sushy library """ system = redfish_utils.get_system(task.node) try: system.reset_system(sushy.RESET_NMI) except sushy.exceptions.SushyError as e: error_msg = (_('Redfish inject NMI failed for node %(node)s. ' 'Error: %(error)s') % {'node': task.node.uuid, 'error': e}) LOG.error(error_msg) raise exception.RedfishError(error=error_msg)
def factory_reset(self, task): """Reset the BIOS settings of the node to the factory default. :param task: a TaskManager instance containing the node to act on. :raises: RedfishConnectionError when it fails to connect to Redfish :raises: RedfishError on an error from the Sushy library """ system = redfish_utils.get_system(task.node) bios = system.bios LOG.debug('Factory reset BIOS settings for node %(node_uuid)s', {'node_uuid': task.node.uuid}) try: bios.reset_bios() except sushy.exceptions.SushyError as e: error_msg = (_('Redfish BIOS factory reset failed for node ' '%(node)s. Error: %(error)s') % {'node': task.node.uuid, 'error': e}) LOG.error(error_msg) raise exception.RedfishError(error=error_msg) self.post_reset(task) self._set_cleaning_reboot(task)
def set_power_state(self, task, power_state, timeout=None): """Set the power state of the task's node. :param task: a TaskManager instance containing the node to act on. :param power_state: Any power state from :mod:`ironic.common.states`. :param timeout: Time to wait for the node to reach the requested state. :raises: MissingParameterValue if a required parameter is missing. :raises: RedfishConnectionError when it fails to connect to Redfish :raises: RedfishError on an error from the Sushy library """ system = redfish_utils.get_system(task.node) try: system.reset_system(SET_POWER_STATE_MAP.get(power_state)) except sushy.exceptions.SushyError as e: error_msg = (_('Redfish set power state failed for node ' '%(node)s. Error: %(error)s') % {'node': task.node.uuid, 'error': e}) LOG.error(error_msg) raise exception.RedfishError(error=error_msg) target_state = TARGET_STATE_MAP.get(power_state, power_state) cond_utils.node_wait_for_power_state(task, target_state, timeout=timeout)
def get_boot_device(self, task): """Get the current boot device for a node. :param task: a task from TaskManager. :raises: InvalidParameterValue on malformed parameter(s) :raises: MissingParameterValue on missing parameter(s) :raises: RedfishConnectionError when it fails to connect to Redfish :raises: RedfishError on an error from the Sushy library :returns: a dictionary containing: :boot_device: the boot device, one of :mod:`ironic.common.boot_devices` or None if it is unknown. :persistent: Boolean value or None, True if the boot device persists, False otherwise. None if it's unknown. """ system = redfish_utils.get_system(task.node) return {'boot_device': BOOT_DEVICE_MAP.get(system.boot.get('target')), 'persistent': BOOT_DEVICE_PERSISTENT_MAP.get( system.boot.get('enabled'))}
def cache_bios_settings(self, task): """Store or update the current BIOS settings for the node. Get the current BIOS settings and store them in the bios_settings database table. :param task: a TaskManager instance containing the node to act on. :raises: RedfishConnectionError when it fails to connect to Redfish :raises: RedfishError on an error from the Sushy library """ node_id = task.node.id system = redfish_utils.get_system(task.node) attributes = system.bios.attributes settings = [] # Convert Redfish BIOS attributes to Ironic BIOS settings if attributes: settings = [{'name': k, 'value': v} for k, v in attributes.items()] LOG.debug('Cache BIOS settings for node %(node_uuid)s', {'node_uuid': task.node.uuid}) create_list, update_list, delete_list, nochange_list = ( objects.BIOSSettingList.sync_node_setting( task.context, node_id, settings)) if create_list: objects.BIOSSettingList.create( task.context, node_id, create_list) if update_list: objects.BIOSSettingList.save( task.context, node_id, update_list) if delete_list: delete_names = [d['name'] for d in delete_list] objects.BIOSSettingList.delete( task.context, node_id, delete_names)
def inspect_hardware(self, task): """Inspect hardware to get the hardware properties. Inspects hardware to get the essential properties. It fails if any of the essential properties are not received from the node. :param task: a TaskManager instance. :raises: HardwareInspectionFailure if essential properties could not be retrieved successfully. :returns: The resulting state of inspection. """ system = redfish_utils.get_system(task.node) # get the essential properties and update the node properties # with it. inspected_properties = task.node.properties if system.memory_summary and system.memory_summary.size_gib: inspected_properties['memory_mb'] = str( system.memory_summary.size_gib * units.Ki) if system.processors and system.processors.summary: cpus, arch = system.processors.summary if cpus: inspected_properties['cpus'] = cpus if arch: try: inspected_properties['cpu_arch'] = CPU_ARCH_MAP[arch] except KeyError: LOG.warning("Unknown CPU arch %(arch)s discovered " "for node %(node)s", {'node': task.node.uuid, 'arch': arch}) simple_storage_size = 0 try: LOG.debug("Attempting to discover system simple storage size for " "node %(node)s", {'node': task.node.uuid}) if (system.simple_storage and system.simple_storage.disks_sizes_bytes): simple_storage_size = [ size for size in system.simple_storage.disks_sizes_bytes if size >= 4 * units.Gi ] or [0] simple_storage_size = simple_storage_size[0] except sushy.exceptions.SushyError as ex: LOG.debug("No simple storage information discovered " "for node %(node)s: %(err)s", {'node': task.node.uuid, 'err': ex}) storage_size = 0 try: LOG.debug("Attempting to discover system storage volume size for " "node %(node)s", {'node': task.node.uuid}) if system.storage and system.storage.volumes_sizes_bytes: storage_size = [ size for size in system.storage.volumes_sizes_bytes if size >= 4 * units.Gi ] or [0] storage_size = storage_size[0] except sushy.exceptions.SushyError as ex: LOG.debug("No storage volume information discovered " "for node %(node)s: %(err)s", {'node': task.node.uuid, 'err': ex}) try: if not storage_size: LOG.debug("Attempting to discover system storage drive size " "for node %(node)s", {'node': task.node.uuid}) if system.storage and system.storage.drives_sizes_bytes: storage_size = [ size for size in system.storage.drives_sizes_bytes if size >= 4 * units.Gi ] or [0] storage_size = storage_size[0] except sushy.exceptions.SushyError as ex: LOG.debug("No storage drive information discovered " "for node %(node)s: %(err)s", {'node': task.node.uuid, 'err': ex}) # NOTE(etingof): pick the smallest disk larger than 4G among available if simple_storage_size and storage_size: local_gb = min(simple_storage_size, storage_size) else: local_gb = max(simple_storage_size, storage_size) # Note(deray): Convert the received size to GiB and reduce the # value by 1 GB as consumers like Ironic requires the ``local_gb`` # to be returned 1 less than actual size. local_gb = max(0, int(local_gb / units.Gi - 1)) # TODO(etingof): should we respect root device hints here? if local_gb: inspected_properties['local_gb'] = str(local_gb) else: LOG.warning("Could not provide a valid storage size configured " "for node %(node)s. Assuming this is a disk-less node", {'node': task.node.uuid}) inspected_properties['local_gb'] = '0' if system.boot.mode: if not drivers_utils.get_node_capability(task.node, 'boot_mode'): capabilities = utils.get_updated_capabilities( inspected_properties.get('capabilities', ''), {'boot_mode': BOOT_MODE_MAP[system.boot.mode]}) inspected_properties['capabilities'] = capabilities valid_keys = self.ESSENTIAL_PROPERTIES missing_keys = valid_keys - set(inspected_properties) if missing_keys: error = (_('Failed to discover the following properties: ' '%(missing_keys)s on node %(node)s'), {'missing_keys': ', '.join(missing_keys), 'node': task.node.uuid}) raise exception.HardwareInspectionFailure(error=error) task.node.properties = inspected_properties task.node.save() LOG.debug("Node properties for %(node)s are updated as " "%(properties)s", {'properties': inspected_properties, 'node': task.node.uuid}) if (system.ethernet_interfaces and system.ethernet_interfaces.summary): macs = system.ethernet_interfaces.summary # Create ports for the discovered NICs being in 'enabled' state enabled_macs = {nic_mac: nic_state for nic_mac, nic_state in macs.items() if nic_state == sushy.STATE_ENABLED} if enabled_macs: inspect_utils.create_ports_if_not_exist( task, enabled_macs, get_mac_address=lambda x: x[0]) else: LOG.warning("Not attempting to create any port as no NICs " "were discovered in 'enabled' state for node " "%(node)s: %(mac_data)s", {'mac_data': macs, 'node': task.node.uuid}) else: LOG.warning("No NIC information discovered " "for node %(node)s", {'node': task.node.uuid}) return states.MANAGEABLE
def test_ensure_session_reuse(self, mock_sushy): redfish_utils.get_system(self.node) redfish_utils.get_system(self.node) self.assertEqual(1, mock_sushy.call_count)
def test_ensure_new_session_address(self, mock_sushy): self.node.driver_info['redfish_address'] = 'http://bmc.foo' redfish_utils.get_system(self.node) self.node.driver_info['redfish_address'] = 'http://bmc.bar' redfish_utils.get_system(self.node) self.assertEqual(2, mock_sushy.call_count)
def test_ensure_new_session_username(self, mock_sushy): self.node.driver_info['redfish_username'] = '******' redfish_utils.get_system(self.node) self.node.driver_info['redfish_username'] = '******' redfish_utils.get_system(self.node) self.assertEqual(2, mock_sushy.call_count)