def _raise_unsupported_error(method=None): if method: raise exception.UnsupportedDriverExtension( _("Unsupported method (%s) passed through to vendor extension.") % method) raise exception.MissingParameterValue( _("Method not specified when calling vendor extension."))
def vendor_passthru(self, context, node_id, driver_method, info): """RPC method to encapsulate vendor action. Synchronously validate driver specific info or get driver status, and if successful, start background worker to perform vendor action asynchronously. :param context: an admin context. :param node_id: the id or uuid of a node. :param driver_method: the name of the vendor method. :param info: vendor method args. :raises: InvalidParameterValue if supplied info is not valid. :raises: UnsupportedDriverExtension if current driver does not have vendor interface or method is unsupported. :raises: NoFreeConductorWorker when there is no free worker to start async task. """ LOG.debug("RPC vendor_passthru called for node %s." % node_id) # NOTE(max_lobur): Even though not all vendor_passthru calls may # require an exclusive lock, we need to do so to guarantee that the # state doesn't unexpectedly change between doing a vendor.validate # and vendor.vendor_passthru. with task_manager.acquire(context, node_id, shared=False) as task: if not getattr(task.driver, 'vendor', None): raise exception.UnsupportedDriverExtension( driver=task.node.driver, extension='vendor passthru') task.driver.vendor.validate(task, method=driver_method, **info) task.spawn_after(self._spawn_worker, task.driver.vendor.vendor_passthru, task, method=driver_method, **info)
def driver_vendor_passthru(self, context, driver_name, driver_method, info): """RPC method which synchronously handles driver-level vendor passthru calls. These calls don't require a node UUID and are executed on a random conductor with the specified driver. :param context: an admin context. :param driver_name: name of the driver on which to call the method. :param driver_method: name of the vendor method, for use by the driver. :param info: user-supplied data to pass through to the driver. :raises: InvalidParameterValue if supplied info is not valid. :raises: UnsupportedDriverExtension if current driver does not have vendor interface, if the vendor interface does not implement driver-level vendor passthru or if the passthru method is unsupported. :raises: DriverNotFound if the supplied driver is not loaded. """ # Any locking in a top-level vendor action will need to be done by the # implementation, as there is little we could reasonably lock on here. LOG.debug("RPC driver_vendor_passthru for driver %s." % driver_name) try: driver = self.driver_factory[driver_name].obj except KeyError: raise exception.DriverNotFound(driver_name=driver_name) if not getattr(driver, 'vendor', None): raise exception.UnsupportedDriverExtension( driver=driver_name, extension='vendor interface') return driver.vendor.driver_vendor_passthru(context, method=driver_method, **info)
def test_validate_require_managed_boot(self, mock_get_system, mock_create_ports_if_not_exist, mock_client): CONF.set_override('require_managed_boot', True, group='inspector') self.driver.boot.validate_inspection.side_effect = ( exception.UnsupportedDriverExtension('')) self.assertRaises(exception.UnsupportedDriverExtension, self.iface.validate, self.task)
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 factory_reset(self, task): """Reset BIOS configuration to factory default on the given node. :param task: a TaskManager instance. :raises: UnsupportedDriverExtension, if the node's driver doesn't support BIOS reset. """ raise exception.UnsupportedDriverExtension(driver=task.node.driver, extension='factory_reset')
def validate_rescue(self, task): """Validate that the node has required properties for rescue. :param task: A TaskManager instance with the node being checked :raises: MissingParameterValue if node is missing one or more required parameters :raises: UnsupportedDriverExtension """ raise exception.UnsupportedDriverExtension(driver=task.node.driver, extension='validate_rescue')
def test_get_console_information_not_supported(self): node = self.dbapi.create_node(dbutils.get_test_node()) with mock.patch.object(rpcapi.ConductorAPI, 'get_console_information') as mock_gci: mock_gci.side_effect = exception.UnsupportedDriverExtension( extension='console', driver='test-driver') ret = self.get_json('/nodes/%s/states/console' % node.uuid, expect_errors=True) self.assertEqual(400, ret.status_code) mock_gci.assert_called_once_with(mock.ANY, node.uuid, 'test-topic')
def test_set_console_mode_console_not_supported(self): with mock.patch.object(rpcapi.ConductorAPI, 'set_console_mode') \ as mock_scm: mock_scm.side_effect = exception.UnsupportedDriverExtension( extension='console', driver='test-driver') ret = self.put_json('/nodes/%s/states/console' % self.node.uuid, {'enabled': "true"}, expect_errors=True) self.assertEqual(400, ret.status_code) mock_scm.assert_called_once_with(mock.ANY, self.node.uuid, True, 'test-topic')
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: UnsupportedDriverExtension """ raise exception.UnsupportedDriverExtension(driver=task.node.driver, extension='inject_nmi')
def set_console_mode(self, context, node_id, enabled): """Enable/Disable the console. Validate driver specific information synchronously, and then spawn a background worker to set console mode asynchronously. :param context: request context. :param node_id: node id or uuid. :param enabled: Boolean value; whether the console is enabled or disabled. :raises: UnsupportedDriverExtension if the node's driver doesn't support console. :raises: InvalidParameterValue when the wrong driver info is specified. :raises: NoFreeConductorWorker when there is no free worker to start async task """ LOG.debug( _('RPC set_console_mode called for node %(node)s with ' 'enabled %(enabled)s') % { 'node': node_id, 'enabled': enabled }) task = task_manager.TaskManager(context, node_id, shared=False) node = task.node try: if not getattr(task.driver, 'console', None): exc = exception.UnsupportedDriverExtension(driver=node.driver, extension='console') node.last_error = exc.format_message() node.save(context) raise exc task.driver.console.validate(task, node) if enabled == node.console_enabled: op = _('enabled') if enabled else _('disabled') LOG.info( _("No console action was triggered because the " "console is already %s") % op) else: node.last_error = None node.save(context) # Start the requested action in the background. thread = self._spawn_worker(self._set_console_mode, task, enabled) # Release node lock at the end. thread.link(lambda t: task.release_resources()) except Exception: with excutils.save_and_reraise_exception(): # Release node lock if error occurred. task.release_resources()
def validate_inspection(self, task): """Validate that the node has required properties for inspection. :param task: A TaskManager instance with the node being checked :raises: UnsupportedDriverExtension """ try: self._validate_common(task) except exception.MissingParameterValue: # Fall back to non-managed in-band inspection raise exception.UnsupportedDriverExtension(driver=task.node.driver, extension='inspection')
def test_prepare_ramdisk_conflicting_boot_modes_set_unsupported( self, set_boot_mode_mock): self.node.provision_state = states.DEPLOYING properties = self.node.properties properties['capabilities'] = 'boot_mode:uefi' self.node.properties = properties self.node.save() set_boot_mode_mock.side_effect = exception.UnsupportedDriverExtension( extension='management', driver='test-driver') self.assertRaises(exception.UnsupportedDriverExtension, self._test_prepare_ramdisk, uefi=True, node_boot_mode=boot_modes.LEGACY_BIOS)
def test_unmanaged_ok(self, mock_client): self.driver.boot.validate_inspection.side_effect = ( exception.UnsupportedDriverExtension('')) mock_introspect = mock_client.return_value.start_introspection self.assertEqual(states.INSPECTWAIT, self.iface.inspect_hardware(self.task)) mock_introspect.assert_called_once_with(self.node.uuid) self.assertFalse(self.driver.boot.prepare_ramdisk.called) self.assertFalse(self.driver.network.add_inspection_network.called) self.assertFalse(self.driver.power.reboot.called) self.assertFalse(self.driver.network.remove_inspection_network.called) self.assertFalse(self.driver.boot.clean_up_ramdisk.called) self.assertFalse(self.driver.power.set_power_state.called)
def test_raid_logical_disk_properties_iface_not_supported( self, disk_prop_mock): driver._RAID_PROPERTIES = {} self.register_fake_conductors() disk_prop_mock.side_effect = exception.UnsupportedDriverExtension( extension='raid', driver='fake-hardware') path = '/drivers/%s/raid/logical_disk_properties' % self.hw1 ret = self.get_json(path, headers={api_base.Version.string: "1.12"}, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, ret.status_code) self.assertTrue(ret.json['error_message']) disk_prop_mock.assert_called_once_with(mock.ANY, mock.ANY, self.hw1, topic=mock.ANY)
def test_require_managed_boot(self, mock_client): CONF.set_override('require_managed_boot', True, group='inspector') self.driver.boot.validate_inspection.side_effect = ( exception.UnsupportedDriverExtension('')) mock_introspect = mock_client.return_value.start_introspection self.assertRaises(exception.UnsupportedDriverExtension, self.iface.inspect_hardware, self.task) self.assertFalse(mock_introspect.called) self.assertFalse(self.driver.boot.prepare_ramdisk.called) self.assertFalse(self.driver.network.add_inspection_network.called) self.assertFalse(self.driver.power.reboot.called) self.assertFalse(self.driver.network.remove_inspection_network.called) self.assertFalse(self.driver.boot.clean_up_ramdisk.called) self.assertFalse(self.driver.power.set_power_state.called)
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: IloOperationError on an error from IloClient library. :raises: UnsupportedDriverExtension if secure boot is not supported by the hardware """ try: ilo_common.set_secure_boot_mode(task, state) except ilo_error.IloOperationNotSupported: raise exception.UnsupportedDriverExtension( driver=task.node.driver, extension='set_secure_boot_state')
def test_unmanaged_error(self, mock_acquire, mock_client): mock_acquire.return_value.__enter__.return_value = self.task self.driver.boot.validate_inspection.side_effect = ( exception.UnsupportedDriverExtension('')) mock_introspect = mock_client.return_value.start_introspection mock_introspect.side_effect = RuntimeError('boom') self.iface.inspect_hardware(self.task) mock_introspect.assert_called_once_with(self.node.uuid) self.assertIn('boom', self.task.node.last_error) self.task.process_event.assert_called_once_with('fail') self.assertFalse(self.driver.boot.prepare_ramdisk.called) self.assertFalse(self.driver.network.add_inspection_network.called) self.assertFalse(self.driver.network.remove_inspection_network.called) self.assertFalse(self.driver.boot.clean_up_ramdisk.called) self.assertFalse(self.driver.power.set_power_state.called)
def driver_vendor_passthru(self, context, method, **kwargs): """Handle top-level (ie, no node is specified) vendor actions. These allow a vendor interface to expose additional cross-node API functionality. VendorInterface subclasses are explicitly not required to implement this in order to maintain backwards compatibility with existing drivers. :param context: a context for this action. :param method: an arbitrary string describing the action to be taken. :param kwargs: arbitrary parameters to the passthru method. """ raise exception.UnsupportedDriverExtension( _('Vendor interface does not support driver vendor_passthru ' 'method: %s') % method)
def vendor_passthru(self, context, node_id, driver_method, info): """RPC method to encapsulate vendor action. Synchronously validate driver specific info or get driver status, and if successful, start background worker to perform vendor action asynchronously. :param context: an admin context. :param node_id: the id or uuid of a node. :param driver_method: the name of the vendor method. :param info: vendor method args. :raises: InvalidParameterValue if supplied info is not valid. :raises: UnsupportedDriverExtension if current driver does not have vendor interface or method is unsupported. :raises: NoFreeConductorWorker when there is no free worker to start async task. """ LOG.debug(_("RPC vendor_passthru called for node %s.") % node_id) # NOTE(max_lobur): Even though not all vendor_passthru calls may # require an exclusive lock, we need to do so to guarantee that the # state doesn't unexpectedly change between doing a vendor.validate # and vendor.vendor_passthru. task = task_manager.TaskManager(context, node_id, shared=False) try: if not getattr(task.driver, 'vendor', None): raise exception.UnsupportedDriverExtension( driver=task.node.driver, extension='vendor passthru') task.driver.vendor.validate(task, task.node, method=driver_method, **info) # Start requested action in the background. thread = self._spawn_worker(task.driver.vendor.vendor_passthru, task, task.node, method=driver_method, **info) # Release node lock at the end of async action. thread.link(lambda t: task.release_resources()) except Exception: with excutils.save_and_reraise_exception(): # Release node lock if error occurred. task.release_resources()
def get_secure_boot_state(self, task): """Get the current secure boot state for the node. :param task: A task from TaskManager. :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. :returns: Boolean """ system = redfish_utils.get_system(task.node) try: return system.secure_boot.enabled except sushy.exceptions.MissingAttributeError: raise exception.UnsupportedDriverExtension( driver=task.node.driver, extension='get_secure_boot_state')
def test_vendor_passthru_no_such_method(self): ndict = dbutils.get_test_node() self.dbapi.create_node(ndict) uuid = ndict['uuid'] info = {'foo': 'bar'} with mock.patch.object( rpcapi.ConductorAPI, 'vendor_passthru') as mock_vendor: mock_vendor.side_effect = exception.UnsupportedDriverExtension( {'driver': ndict['driver'], 'node': uuid, 'extension': 'test'}) response = self.post_json('/nodes/%s/vendor_passthru/test' % uuid, info, expect_errors=True) mock_vendor.assert_called_once_with( mock.ANY, uuid, 'test', info, 'test-topic') self.assertEqual(400, response.status_code)
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 :raises: UnsupportedDriverExtension if the system does not support BIOS settings """ node_id = task.node.id system = redfish_utils.get_system(task.node) try: attributes = system.bios.attributes except sushy.exceptions.MissingAttributeError: error_msg = _('Cannot fetch BIOS attributes for node %s, ' 'BIOS settings are not supported.') % task.node.uuid LOG.error(error_msg) raise exception.UnsupportedDriverExtension(error_msg) 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 _set_boot_mode_on_bm(task, ironic_boot_mode, fail_if_unsupported=False): try: manager_utils.node_set_boot_mode(task, ironic_boot_mode) except exception.UnsupportedDriverExtension as ex: if fail_if_unsupported: msg = (_("Baremetal node %(uuid)s boot mode is not set " "to boot mode %(boot_mode)s: %(error)s") % { 'uuid': task.node.uuid, 'boot_mode': ironic_boot_mode, 'error': ex }) LOG.error(msg) raise exception.UnsupportedDriverExtension(msg) msg_tmpl = _("Baremetal node %(uuid)s boot mode is not set " "to boot mode %(boot_mode)s. Assuming " "baremetal node is already in %(boot_mode)s or " "driver set boot mode via some other " "mechanism: %(error)s") LOG.debug(msg_tmpl, { 'uuid': task.node.uuid, 'boot_mode': ironic_boot_mode, 'error': ex }) except exception.InvalidParameterValue as ex: msg = (_("Node %(uuid)s boot mode is not set. " "Attempt to set %(ironic_boot_mode)s boot mode " "on the baremetal node failed with error %(error)s") % { 'uuid': task.node.uuid, 'ironic_boot_mode': ironic_boot_mode, 'error': ex }) LOG.error(msg) raise exception.InvalidParameterValue(msg) else: LOG.info( "Baremetal node boot mode is set to boot " "mode %(boot_mode)s", { 'uuid': task.node.uuid, 'boot_mode': ironic_boot_mode })
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 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) # NOTE(sbaker): Some systems such as HPE Gen9 do not support # getting or setting the boot mode. When setting failed and the # mode attribute is missing from the boot field, raising # UnsupportedDriverExtension will allow the deploy to continue. if system.boot.get('mode') is None: LOG.info( _('Attempt to set boot mode on node %(node)s ' 'failed to set boot mode as the node does not ' 'appear to support overriding the boot mode. ' 'Possibly partial Redfish implementation?'), {'node': task.node.uuid}) raise exception.UnsupportedDriverExtension( driver=task.node.driver, extension='set_boot_mode') raise exception.RedfishError(error=error_msg)
def validate_vendor_action(self, context, node_id, driver_method, info): """Validate driver specific info or get driver status.""" LOG.debug( _("RPC validate_vendor_action called for node %s.") % node_id) with task_manager.acquire(context, node_id, shared=True) as task: try: if getattr(task.driver, 'vendor', None): return task.driver.vendor.validate(task, task.node, method=driver_method, **info) else: raise exception.UnsupportedDriverExtension( driver=task.node.driver, extension='vendor passthru') except Exception as e: with excutils.save_and_reraise_exception(): task.node.last_error = \ _("Failed to validate vendor info. Error: %s") % e task.node.save(context)
def _reset_keys(self, task, reset_type): system = redfish_utils.get_system(task.node) try: sb = system.secure_boot except sushy.exceptions.MissingAttributeError: LOG.error( 'Resetting secure boot keys 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='reset_keys') try: sb.reset_keys(reset_type) except sushy.exceptions.SushyError as exc: msg = (_('Failed to reset secure boot keys on node %(node)s: ' '%(exc)s') % { 'node': task.node.uuid, 'exc': exc }) LOG.error(msg) raise exception.RedfishError(error=msg)
def set_console_mode(self, context, node_id, enabled): """Enable/Disable the console. Validate driver specific information synchronously, and then spawn a background worker to set console mode asynchronously. :param context: request context. :param node_id: node id or uuid. :param enabled: Boolean value; whether the console is enabled or disabled. :raises: UnsupportedDriverExtension if the node's driver doesn't support console. :raises: InvalidParameterValue when the wrong driver info is specified. :raises: NoFreeConductorWorker when there is no free worker to start async task """ LOG.debug('RPC set_console_mode called for node %(node)s with ' 'enabled %(enabled)s' % {'node': node_id, 'enabled': enabled}) with task_manager.acquire(context, node_id, shared=False) as task: node = task.node if not getattr(task.driver, 'console', None): raise exception.UnsupportedDriverExtension(driver=node.driver, extension='console') task.driver.console.validate(task) if enabled == node.console_enabled: op = _('enabled') if enabled else _('disabled') LOG.info(_("No console action was triggered because the " "console is already %s") % op) task.release_resources() else: node.last_error = None node.save(context) task.spawn_after(self._spawn_worker, self._set_console_mode, task, enabled)
def get_console_information(self, context, node_id): """Get connection information about the console. :param context: request context. :param node_id: node id or uuid. :raises: UnsupportedDriverExtension if the node's driver doesn't support console. :raises: NodeConsoleNotEnabled if the console is not enabled. :raises: InvalidParameterValue when the wrong driver info is specified. """ LOG.debug('RPC get_console_information called for node %s' % node_id) with task_manager.acquire(context, node_id, shared=True) as task: node = task.node if not getattr(task.driver, 'console', None): raise exception.UnsupportedDriverExtension(driver=node.driver, extension='console') if not node.console_enabled: raise exception.NodeConsoleNotEnabled(node=node_id) task.driver.console.validate(task) return task.driver.console.get_console(task)