From 9880262defb96531245234abdbe24ae1b9236c73 Mon Sep 17 00:00:00 2001 From: Bill Dodd Date: Thu, 12 Jul 2018 12:34:08 -0500 Subject: [PATCH] Add BIOS interface to Redfish hardware type The Redfish hardware type does not currently implement the new BIOS hardware interface. This patch implements the Redfish BIOS interface, allowing operators to perform BIOS configuration actions on Ironic Redfish nodes. Change-Id: I44a2a465b08bc15465b7096b1e4838aebb460c1b Story: 2001791 Task: 12507 --- driver-requirements.txt | 2 +- ironic/drivers/modules/redfish/bios.py | 283 ++++++++++++++++++ ironic/drivers/redfish.py | 6 + .../unit/drivers/modules/redfish/test_bios.py | 226 ++++++++++++++ .../modules/redfish/test_management.py | 3 +- .../drivers/modules/redfish/test_power.py | 3 +- ironic/tests/unit/drivers/test_redfish.py | 3 +- ...dfish-bios-interface-a1acd8122c896a38.yaml | 3 + setup.cfg | 1 + 9 files changed, 526 insertions(+), 4 deletions(-) create mode 100644 ironic/drivers/modules/redfish/bios.py create mode 100644 ironic/tests/unit/drivers/modules/redfish/test_bios.py create mode 100644 releasenotes/notes/redfish-bios-interface-a1acd8122c896a38.yaml diff --git a/driver-requirements.txt b/driver-requirements.txt index e8f1c615e5..5ee2943f02 100644 --- a/driver-requirements.txt +++ b/driver-requirements.txt @@ -16,7 +16,7 @@ python-xclarityclient>=0.1.6 ImcSdk>=0.7.2 # The Redfish hardware type uses the Sushy library -sushy +sushy>=1.6.0 # Ansible-deploy interface ansible>=2.4 diff --git a/ironic/drivers/modules/redfish/bios.py b/ironic/drivers/modules/redfish/bios.py new file mode 100644 index 0000000000..53b5b35723 --- /dev/null +++ b/ironic/drivers/modules/redfish/bios.py @@ -0,0 +1,283 @@ +# Copyright 2018 DMTF. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from ironic_lib import metrics_utils +from oslo_log import log +from oslo_utils import importutils + +from ironic.common import exception +from ironic.common.i18n import _ +from ironic.common import states +from ironic.conductor import task_manager +from ironic.conductor import utils as manager_utils +from ironic.drivers import base +from ironic.drivers.modules.redfish import utils as redfish_utils +from ironic import objects + +LOG = log.getLogger(__name__) + +METRICS = metrics_utils.get_metrics_logger(__name__) + +sushy = importutils.try_import('sushy') + + +class RedfishBIOS(base.BIOSInterface): + + def __init__(self): + super(RedfishBIOS, self).__init__() + if sushy is None: + raise exception.DriverLoadError( + driver='redfish', + reason=_("Unable to import the sushy library")) + + 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) + + @base.clean_step(priority=0) + 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) + + @base.clean_step(priority=0, argsinfo={ + 'settings': { + 'description': ( + 'A list of BIOS settings to be applied' + ), + 'required': True + } + }) + 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 post_reset(self, task): + """Perform post reset action to apply the BIOS factory reset. + + Extension point to allow vendor implementations to extend this class + and override this method to perform a custom action to apply the BIOS + factory reset to the Redfish service. The default implementation + performs a reboot. + + :param task: a TaskManager instance containing the node to act on. + """ + self._reboot(task) + + def post_configuration(self, task, settings): + """Perform post configuration action to store the BIOS settings. + + Extension point to allow vendor implementations to extend this class + and override this method to perform a custom action to write the BIOS + settings to the Redfish service. The default implementation performs + a reboot. + + :param task: a TaskManager instance containing the node to act on. + :param settings: a list of BIOS settings to be updated. + """ + self._reboot(task) + + def get_properties(self): + """Return the properties of the interface. + + :returns: dictionary of : entries. + """ + return redfish_utils.COMMON_PROPERTIES.copy() + + def validate(self, task): + """Validates the driver information needed by the redfish driver. + + :param task: a TaskManager instance containing the node to act on. + :raises: InvalidParameterValue on malformed parameter(s) + :raises: MissingParameterValue on missing parameter(s) + """ + redfish_utils.parse_driver_info(task.node) + + def _check_bios_attrs(self, task, current_attrs, requested_attrs): + """Checks that the requested BIOS settings were applied to the service. + + :param task: a TaskManager instance containing the node to act on. + :param current_attrs: the current BIOS attributes from the system. + :param requested_attrs: the requested BIOS attributes to update. + """ + + attrs_not_updated = {} + for attr in requested_attrs: + if requested_attrs[attr] != current_attrs.get(attr): + attrs_not_updated[attr] = requested_attrs[attr] + + if attrs_not_updated: + LOG.debug('BIOS settings %(attrs)s for node %(node_uuid)s ' + 'not updated.', {'attrs': attrs_not_updated, + 'node_uuid': task.node.uuid}) + self._set_clean_failed(task, attrs_not_updated) + else: + LOG.debug('Verification of BIOS settings for node %(node_uuid)s ' + 'successful.', {'node_uuid': task.node.uuid}) + + @task_manager.require_exclusive_lock + def _reboot(self, task): + """Reboot the target Redfish service. + + :param task: a TaskManager instance containing the node to act on. + :raises: InvalidParameterValue when the wrong state is specified + or the wrong driver info is specified. + :raises: RedfishError on an error from the Sushy library + """ + manager_utils.node_power_action(task, states.REBOOT) + + def _set_cleaning_reboot(self, task): + """Set driver_internal_info flags for cleaning reboot. + + :param task: a TaskManager instance containing the node to act on. + """ + info = task.node.driver_internal_info + info['cleaning_reboot'] = True + task.node.driver_internal_info = info + task.node.save() + + def _set_reboot_requested(self, task, attributes): + """Set driver_internal_info flags for reboot requested. + + :param task: a TaskManager instance containing the node to act on. + :param attributes: the requested BIOS attributes to update. + """ + info = task.node.driver_internal_info + info['post_config_reboot_requested'] = True + info['cleaning_reboot'] = True + info['requested_bios_attrs'] = attributes + info['skip_current_clean_step'] = False + task.node.driver_internal_info = info + task.node.save() + + def _clear_reboot_requested(self, task): + """Clear driver_internal_info flags after reboot completed. + + :param task: a TaskManager instance containing the node to act on. + """ + info = task.node.driver_internal_info + if 'post_config_reboot_requested' in info: + del info['post_config_reboot_requested'] + if 'requested_bios_attrs' in info: + del info['requested_bios_attrs'] + task.node.driver_internal_info = info + task.node.save() + + def _set_clean_failed(self, task, attrs_not_updated): + """Fail the cleaning step and log the error. + + :param task: a TaskManager instance containing the node to act on. + :param attrs_not_updated: the BIOS attributes that were not updated. + """ + error_msg = (_('Redfish BIOS apply_configuration step failed for node ' + '%(node)s. Attributes %(attrs)s are not updated.') % + {'node': task.node.uuid, 'attrs': attrs_not_updated}) + last_error = (_('Redfish BIOS apply_configuration step failed. ' + 'Attributes %(attrs)s are not updated.') % + {'attrs': attrs_not_updated}) + LOG.error(error_msg) + task.node.last_error = last_error + if task.node.provision_state in [states.CLEANING, states.CLEANWAIT]: + task.process_event('fail') diff --git a/ironic/drivers/redfish.py b/ironic/drivers/redfish.py index 9e5c933311..1610a2613e 100644 --- a/ironic/drivers/redfish.py +++ b/ironic/drivers/redfish.py @@ -16,6 +16,7 @@ from ironic.drivers import generic from ironic.drivers.modules import inspector from ironic.drivers.modules import noop +from ironic.drivers.modules.redfish import bios as redfish_bios from ironic.drivers.modules.redfish import inspect as redfish_inspect from ironic.drivers.modules.redfish import management as redfish_mgmt from ironic.drivers.modules.redfish import power as redfish_power @@ -24,6 +25,11 @@ from ironic.drivers.modules.redfish import power as redfish_power class RedfishHardware(generic.GenericHardware): """Redfish hardware type.""" + @property + def supported_bios_interfaces(self): + """List of supported bios interfaces.""" + return [redfish_bios.RedfishBIOS, noop.NoBIOS] + @property def supported_management_interfaces(self): """List of supported management interfaces.""" diff --git a/ironic/tests/unit/drivers/modules/redfish/test_bios.py b/ironic/tests/unit/drivers/modules/redfish/test_bios.py new file mode 100644 index 0000000000..3edcbf1e1e --- /dev/null +++ b/ironic/tests/unit/drivers/modules/redfish/test_bios.py @@ -0,0 +1,226 @@ +# Copyright 2018 DMTF. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +from oslo_utils import importutils + +from ironic.common import exception +from ironic.common import states +from ironic.conductor import task_manager +from ironic.conductor import utils as manager_utils +from ironic.drivers.modules.redfish import bios as redfish_bios +from ironic.drivers.modules.redfish import utils as redfish_utils +from ironic import objects +from ironic.tests.unit.db import base as db_base +from ironic.tests.unit.db import utils as db_utils +from ironic.tests.unit.objects import utils as obj_utils + +sushy = importutils.try_import('sushy') + +INFO_DICT = db_utils.get_test_redfish_info() + + +class MockedSushyError(Exception): + pass + + +@mock.patch('eventlet.greenthread.sleep', lambda _t: None) +class RedfishBiosTestCase(db_base.DbTestCase): + + def setUp(self): + super(RedfishBiosTestCase, self).setUp() + self.config(enabled_bios_interfaces=['redfish'], + enabled_hardware_types=['redfish'], + enabled_power_interfaces=['redfish'], + enabled_management_interfaces=['redfish']) + self.node = obj_utils.create_test_node( + self.context, driver='redfish', driver_info=INFO_DICT) + + @mock.patch.object(redfish_bios, 'sushy', None) + def test_loading_error(self): + self.assertRaisesRegex( + exception.DriverLoadError, + 'Unable to import the sushy library', + redfish_bios.RedfishBIOS) + + def test_get_properties(self): + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + properties = task.driver.get_properties() + for prop in redfish_utils.COMMON_PROPERTIES: + self.assertIn(prop, properties) + + @mock.patch.object(redfish_utils, 'parse_driver_info', autospec=True) + def test_validate(self, mock_parse_driver_info): + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + task.driver.bios.validate(task) + mock_parse_driver_info.assert_called_once_with(task.node) + + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + @mock.patch.object(objects, 'BIOSSettingList', autospec=True) + def test_cache_bios_settings_noop(self, mock_setting_list, + mock_get_system): + create_list = [] + update_list = [] + delete_list = [] + nochange_list = [{'name': 'EmbeddedSata', 'value': 'Raid'}, + {'name': 'NicBoot1', 'value': 'NetworkBoot'}] + mock_setting_list.sync_node_setting.return_value = ( + create_list, update_list, delete_list, nochange_list + ) + + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + attributes = mock_get_system(task.node).bios.attributes + settings = [{'name': k, 'value': v} for k, v in attributes.items()] + mock_get_system.reset_mock() + + task.driver.bios.cache_bios_settings(task) + mock_get_system.assert_called_once_with(task.node) + mock_setting_list.sync_node_setting.assert_called_once_with( + task.context, task.node.id, settings) + mock_setting_list.create.assert_not_called() + mock_setting_list.save.assert_not_called() + mock_setting_list.delete.assert_not_called() + + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + @mock.patch.object(objects, 'BIOSSettingList', autospec=True) + def test_cache_bios_settings(self, mock_setting_list, mock_get_system): + create_list = [{'name': 'DebugMode', 'value': 'enabled'}] + update_list = [{'name': 'BootMode', 'value': 'Uefi'}, + {'name': 'NicBoot2', 'value': 'NetworkBoot'}] + delete_list = [{'name': 'AdminPhone', 'value': '555-867-5309'}] + nochange_list = [{'name': 'EmbeddedSata', 'value': 'Raid'}, + {'name': 'NicBoot1', 'value': 'NetworkBoot'}] + delete_names = [] + for setting in delete_list: + delete_names.append(setting.get('name')) + mock_setting_list.sync_node_setting.return_value = ( + create_list, update_list, delete_list, nochange_list + ) + + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + attributes = mock_get_system(task.node).bios.attributes + settings = [{'name': k, 'value': v} for k, v in attributes.items()] + mock_get_system.reset_mock() + + task.driver.bios.cache_bios_settings(task) + mock_get_system.assert_called_once_with(task.node) + mock_setting_list.sync_node_setting.assert_called_once_with( + task.context, task.node.id, settings) + mock_setting_list.create.assert_called_once_with( + task.context, task.node.id, create_list) + mock_setting_list.save.assert_called_once_with( + task.context, task.node.id, update_list) + mock_setting_list.delete.assert_called_once_with( + task.context, task.node.id, delete_names) + + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + @mock.patch.object(manager_utils, 'node_power_action', autospec=True) + def test_factory_reset(self, mock_power_action, mock_get_system): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.driver.bios.factory_reset(task) + mock_get_system.assert_called_with(task.node) + mock_power_action.assert_called_once_with(task, states.REBOOT) + bios = mock_get_system(task.node).bios + bios.reset_bios.assert_called_once() + + @mock.patch('ironic.drivers.modules.redfish.bios.sushy') + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + def test_factory_reset_fail(self, mock_get_system, mock_sushy): + mock_sushy.exceptions.SushyError = MockedSushyError + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + bios = mock_get_system(task.node).bios + bios.reset_bios.side_effect = MockedSushyError + self.assertRaisesRegex( + exception.RedfishError, 'BIOS factory reset failed', + task.driver.bios.factory_reset, task) + + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + @mock.patch.object(manager_utils, 'node_power_action', autospec=True) + def test_apply_configuration_step1(self, mock_power_action, + mock_get_system): + settings = [{'name': 'ProcTurboMode', 'value': 'Disabled'}, + {'name': 'NicBoot1', 'value': 'NetworkBoot'}] + attributes = {s['name']: s['value'] for s in settings} + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.driver.bios.apply_configuration(task, settings) + mock_get_system.assert_called_with(task.node) + mock_power_action.assert_called_once_with(task, states.REBOOT) + bios = mock_get_system(task.node).bios + bios.set_attributes.assert_called_once_with(attributes) + + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + def test_apply_configuration_step2(self, mock_get_system): + settings = [{'name': 'ProcTurboMode', 'value': 'Disabled'}, + {'name': 'NicBoot1', 'value': 'NetworkBoot'}] + requested_attrs = {'ProcTurboMode': 'Enabled'} + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.node.driver_internal_info[ + 'post_config_reboot_requested'] = True + task.node.driver_internal_info[ + 'requested_bios_attrs'] = requested_attrs + task.driver.bios._clear_reboot_requested = mock.MagicMock() + task.driver.bios.apply_configuration(task, settings) + mock_get_system.assert_called_with(task.node) + task.driver.bios._clear_reboot_requested\ + .assert_called_once_with(task) + + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + def test_check_bios_attrs(self, mock_get_system): + settings = [{'name': 'ProcTurboMode', 'value': 'Disabled'}, + {'name': 'NicBoot1', 'value': 'NetworkBoot'}] + requested_attrs = {'ProcTurboMode': 'Enabled'} + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + attributes = mock_get_system(task.node).bios.attributes + task.node.driver_internal_info[ + 'post_config_reboot_requested'] = True + task.node.driver_internal_info[ + 'requested_bios_attrs'] = requested_attrs + task.driver.bios._check_bios_attrs = mock.MagicMock() + task.driver.bios.apply_configuration(task, settings) + task.driver.bios._check_bios_attrs \ + .assert_called_once_with(task, attributes, requested_attrs) + + @mock.patch('ironic.drivers.modules.redfish.bios.sushy') + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + def test_apply_configuration_fail(self, mock_get_system, mock_sushy): + settings = [{'name': 'ProcTurboMode', 'value': 'Disabled'}, + {'name': 'NicBoot1', 'value': 'NetworkBoot'}] + mock_sushy.exceptions.SushyError = MockedSushyError + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + bios = mock_get_system(task.node).bios + bios.set_attributes.side_effect = MockedSushyError + self.assertRaisesRegex( + exception.RedfishError, 'BIOS apply configuration failed', + task.driver.bios.apply_configuration, task, settings) + + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + def test_post_configuration(self, mock_get_system): + settings = [{'name': 'ProcTurboMode', 'value': 'Disabled'}, + {'name': 'NicBoot1', 'value': 'NetworkBoot'}] + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + task.driver.bios.post_configuration = mock.MagicMock() + task.driver.bios.apply_configuration(task, settings) + task.driver.bios.post_configuration\ + .assert_called_once_with(task, settings) diff --git a/ironic/tests/unit/drivers/modules/redfish/test_management.py b/ironic/tests/unit/drivers/modules/redfish/test_management.py index cce6c829e9..7876b2087f 100644 --- a/ironic/tests/unit/drivers/modules/redfish/test_management.py +++ b/ironic/tests/unit/drivers/modules/redfish/test_management.py @@ -38,7 +38,8 @@ class RedfishManagementTestCase(db_base.DbTestCase): self.config(enabled_hardware_types=['redfish'], enabled_power_interfaces=['redfish'], enabled_management_interfaces=['redfish'], - enabled_inspect_interfaces=['redfish']) + enabled_inspect_interfaces=['redfish'], + enabled_bios_interfaces=['redfish']) self.node = obj_utils.create_test_node( self.context, driver='redfish', driver_info=INFO_DICT) diff --git a/ironic/tests/unit/drivers/modules/redfish/test_power.py b/ironic/tests/unit/drivers/modules/redfish/test_power.py index 4c3f61d6f5..96903328e8 100644 --- a/ironic/tests/unit/drivers/modules/redfish/test_power.py +++ b/ironic/tests/unit/drivers/modules/redfish/test_power.py @@ -38,7 +38,8 @@ class RedfishPowerTestCase(db_base.DbTestCase): self.config(enabled_hardware_types=['redfish'], enabled_power_interfaces=['redfish'], enabled_management_interfaces=['redfish'], - enabled_inspect_interfaces=['redfish']) + enabled_inspect_interfaces=['redfish'], + enabled_bios_interfaces=['redfish']) self.node = obj_utils.create_test_node( self.context, driver='redfish', driver_info=INFO_DICT) diff --git a/ironic/tests/unit/drivers/test_redfish.py b/ironic/tests/unit/drivers/test_redfish.py index 073df7d772..1b4e44585a 100644 --- a/ironic/tests/unit/drivers/test_redfish.py +++ b/ironic/tests/unit/drivers/test_redfish.py @@ -31,7 +31,8 @@ class RedfishHardwareTestCase(db_base.DbTestCase): self.config(enabled_hardware_types=['redfish'], enabled_power_interfaces=['redfish'], enabled_management_interfaces=['redfish'], - enabled_inspect_interfaces=['redfish']) + enabled_inspect_interfaces=['redfish'], + enabled_bios_interfaces=['redfish']) def test_default_interfaces(self): node = obj_utils.create_test_node(self.context, driver='redfish') diff --git a/releasenotes/notes/redfish-bios-interface-a1acd8122c896a38.yaml b/releasenotes/notes/redfish-bios-interface-a1acd8122c896a38.yaml new file mode 100644 index 0000000000..df916fe2fb --- /dev/null +++ b/releasenotes/notes/redfish-bios-interface-a1acd8122c896a38.yaml @@ -0,0 +1,3 @@ +--- +features: + - Adds ``bios`` interface to the ``redfish`` hardware type. diff --git a/setup.cfg b/setup.cfg index d7a6460eb4..128b75e5ab 100644 --- a/setup.cfg +++ b/setup.cfg @@ -58,6 +58,7 @@ ironic.hardware.interfaces.bios = ilo = ironic.drivers.modules.ilo.bios:IloBIOS irmc = ironic.drivers.modules.irmc.bios:IRMCBIOS no-bios = ironic.drivers.modules.noop:NoBIOS + redfish = ironic.drivers.modules.redfish.bios:RedfishBIOS ironic.hardware.interfaces.boot = fake = ironic.drivers.modules.fake:FakeBoot