From e7664a161d3a99d5ddd351489910d535bdec3752 Mon Sep 17 00:00:00 2001 From: Dao Cong Tien Date: Wed, 4 Jan 2017 09:22:30 +0700 Subject: [PATCH] Adds clean step 'restore_irmc_bios_config' to iRMC drivers - Adds new boot interface 'irmc-pxe'. Deprecates 'pxe' boot interface from using with hardware type 'irmc'. - Adds functions backup_bios_config and restore_bios_config to iRMC management interface for implementing the BIOS BACKUP/RESTORE mechanism supporting iRMC S4 hardware. The function backup_bios_config() will be called automatically before deploying. - Adds clean step restore_irmc_bios_config to restore BIOS config for a node during automatic cleaning. Change-Id: I04aa5bc2f5e287e048d0b52fee123e53ae2eaa99 Partial-Bug: #1639688 --- etc/ironic/ironic.conf.sample | 4 + ironic/conf/irmc.py | 3 + ironic/drivers/irmc.py | 5 +- ironic/drivers/modules/irmc/boot.py | 38 +++++ ironic/drivers/modules/irmc/management.py | 126 +++++++++++++++- ironic/drivers/pxe.py | 3 +- .../unit/drivers/modules/irmc/test_boot.py | 50 ++++++- .../drivers/modules/irmc/test_management.py | 136 +++++++++++++++++- ironic/tests/unit/drivers/test_pxe.py | 3 +- .../drivers/third_party_driver_mock_specs.py | 5 + .../unit/drivers/third_party_driver_mocks.py | 2 + ...ep-reset-bios-config-a8bed625670b7fdf.yaml | 16 +++ setup.cfg | 1 + 13 files changed, 373 insertions(+), 19 deletions(-) create mode 100644 releasenotes/notes/irmc-add-clean-step-reset-bios-config-a8bed625670b7fdf.yaml diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample index f692758514..2436e84c5f 100644 --- a/etc/ironic/ironic.conf.sample +++ b/etc/ironic/ironic.conf.sample @@ -1965,6 +1965,10 @@ # SNMP polling interval in seconds (integer value) #snmp_polling_interval = 10 +# Priority for restore_irmc_bios_config clean step. (integer +# value) +#clean_priority_restore_irmc_bios_config = 0 + [ironic_lib] diff --git a/ironic/conf/irmc.py b/ironic/conf/irmc.py index ac590339b8..bc558d03c5 100644 --- a/ironic/conf/irmc.py +++ b/ironic/conf/irmc.py @@ -69,6 +69,9 @@ opts = [ cfg.IntOpt('snmp_polling_interval', default=10, help='SNMP polling interval in seconds'), + cfg.IntOpt('clean_priority_restore_irmc_bios_config', + default=0, + help=_('Priority for restore_irmc_bios_config clean step.')), ] diff --git a/ironic/drivers/irmc.py b/ironic/drivers/irmc.py index f721c31098..10fe664a62 100644 --- a/ironic/drivers/irmc.py +++ b/ironic/drivers/irmc.py @@ -91,7 +91,10 @@ class IRMCHardware(generic.GenericHardware): @property def supported_boot_interfaces(self): """List of supported boot interfaces.""" - return [boot.IRMCVirtualMediaBoot, pxe.PXEBoot] + # NOTE: Support for pxe boot is deprecated, and will be + # removed from the list in the future. + return [boot.IRMCVirtualMediaBoot, boot.IRMCPXEBoot, + pxe.PXEBoot] @property def supported_console_interfaces(self): diff --git a/ironic/drivers/modules/irmc/boot.py b/ironic/drivers/modules/irmc/boot.py index 1a85809259..78f9a3253e 100644 --- a/ironic/drivers/modules/irmc/boot.py +++ b/ironic/drivers/modules/irmc/boot.py @@ -36,6 +36,8 @@ from ironic.conf import CONF from ironic.drivers import base from ironic.drivers.modules import deploy_utils from ironic.drivers.modules.irmc import common as irmc_common +from ironic.drivers.modules.irmc import management as irmc_management +from ironic.drivers.modules import pxe scci = importutils.try_import('scciclient.irmc.scci') @@ -587,6 +589,11 @@ class IRMCVirtualMediaBoot(base.BootInterface): task.node.provision_state != states.CLEANING): return + # NOTE(tiendc): Before deploying, we need to backup BIOS config + # as the data will be used later when cleaning. + if task.node.provision_state == states.DEPLOYING: + irmc_management.backup_bios_config(task) + deploy_nic_mac = deploy_utils.get_single_nic_with_vif_port_id(task) ramdisk_params['BOOTIF'] = deploy_nic_mac @@ -654,3 +661,34 @@ class IRMCVirtualMediaBoot(base.BootInterface): task, node.driver_internal_info['irmc_boot_iso']) manager_utils.node_set_boot_device(task, boot_devices.CDROM, persistent=True) + + +class IRMCPXEBoot(pxe.PXEBoot): + """iRMC PXE boot.""" + + @METRICS.timer('IRMCPXEBoot.prepare_ramdisk') + def prepare_ramdisk(self, task, ramdisk_params): + """Prepares the boot of Ironic ramdisk using PXE. + + This method prepares the boot of the deploy kernel/ramdisk after + reading relevant information from the node's driver_info and + instance_info. + + :param task: a task from TaskManager. + :param ramdisk_params: the parameters to be passed to the ramdisk. + pxe driver passes these parameters as kernel command-line + arguments. + :returns: None + :raises: MissingParameterValue, if some information is missing in + node's driver_info or instance_info. + :raises: InvalidParameterValue, if some information provided is + invalid. + :raises: IronicException, if some power or set boot device + operation failed on the node. + """ + # NOTE(tiendc): Before deploying, we need to backup BIOS config + # as the data will be used later when cleaning. + if task.node.provision_state == states.DEPLOYING: + irmc_management.backup_bios_config(task) + + super(IRMCPXEBoot, self).prepare_ramdisk(task, ramdisk_params) diff --git a/ironic/drivers/modules/irmc/management.py b/ironic/drivers/modules/irmc/management.py index 6f774b849d..bddb970901 100644 --- a/ironic/drivers/modules/irmc/management.py +++ b/ironic/drivers/modules/irmc/management.py @@ -14,6 +14,7 @@ """ iRMC Management Driver """ + from ironic_lib import metrics_utils from oslo_log import log as logging from oslo_utils import importutils @@ -21,14 +22,19 @@ from oslo_utils import importutils from ironic.common import boot_devices 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 import conf +from ironic.drivers import base from ironic.drivers.modules import ipmitool from ironic.drivers.modules.irmc import common as irmc_common from ironic.drivers import utils as driver_utils -scci = importutils.try_import('scciclient.irmc.scci') +irmc = importutils.try_import('scciclient.irmc') LOG = logging.getLogger(__name__) +CONF = conf.CONF METRICS = metrics_utils.get_metrics_logger(__name__) @@ -61,12 +67,12 @@ def _get_sensors_data(task): try: report = irmc_common.get_irmc_report(task.node) - sensor = scci.get_sensor_data(report) + sensor = irmc.scci.get_sensor_data(report) except (exception.InvalidParameterValue, exception.MissingParameterValue, - scci.SCCIInvalidInputError, - scci.SCCIClientError) as e: + irmc.scci.SCCIInvalidInputError, + irmc.scci.SCCIClientError) as e: LOG.error("SCCI get sensor data failed for node %(node_id)s " "with the following error: %(error)s", {'node_id': task.node.uuid, 'error': e}) @@ -106,6 +112,96 @@ def _get_sensors_data(task): return sensors_data +def backup_bios_config(task): + """Backup BIOS config from a node. + + :param task: a TaskManager instance containing the node to act on. + :raises: IRMCOperationError on failure. + """ + node_uuid = task.node.uuid + + # Skip this operation if the clean step 'restore' is disabled + if CONF.irmc.clean_priority_restore_irmc_bios_config == 0: + LOG.debug('Skipped the operation backup_BIOS_config for node %s ' + 'as the clean step restore_BIOS_config is disabled.', + node_uuid) + return + + irmc_info = irmc_common.parse_driver_info(task.node) + + try: + # Backup bios config + result = irmc.elcm.backup_bios_config(irmc_info) + except irmc.scci.SCCIError as e: + LOG.error('Failed to backup BIOS config for node %(node)s. ' + 'Error: %(error)s', {'node': node_uuid, 'error': e}) + raise exception.IRMCOperationError(operation='backup BIOS config', + error=e) + + # Save bios config into the driver_internal_info + internal_info = task.node.driver_internal_info + internal_info['irmc_bios_config'] = result['bios_config'] + task.node.driver_internal_info = internal_info + task.node.save() + + LOG.info('BIOS config is backed up successfully for node %s', + node_uuid) + + # NOTE(tiendc): When the backup operation done, server is automatically + # shutdown. However, this function is called right before the method + # task.driver.deploy() that will trigger a reboot. So, we don't need + # to power on the server at this point. + + +def _restore_bios_config(task): + """Restore BIOS config to a node. + + :param task: a TaskManager instance containing the node to act on. + :raises: IRMCOperationError if the operation fails. + """ + node_uuid = task.node.uuid + + # Get bios config stored in the node object + bios_config = task.node.driver_internal_info.get('irmc_bios_config') + if not bios_config: + LOG.info('Skipped operation "restore BIOS config" on node %s ' + 'as the backup data not found.', node_uuid) + return + + def _remove_bios_config(task): + """Remove backup bios config from the node.""" + internal_info = task.node.driver_internal_info + internal_info.pop('irmc_bios_config', None) + task.node.driver_internal_info = internal_info + task.node.save() + + irmc_info = irmc_common.parse_driver_info(task.node) + + try: + # Restore bios config + irmc.elcm.restore_bios_config(irmc_info, bios_config) + except irmc.scci.SCCIError as e: + # If the input bios config is not correct or corrupted, then + # we should remove it from the node object. + if isinstance(e, irmc.scci.SCCIInvalidInputError): + _remove_bios_config(task) + + LOG.error('Failed to restore BIOS config on node %(node)s. ' + 'Error: %(error)s', {'node': node_uuid, 'error': e}) + raise exception.IRMCOperationError(operation='restore BIOS config', + error=e) + + # Remove the backup data after restoring + _remove_bios_config(task) + + LOG.info('BIOS config is restored successfully on node %s', + node_uuid) + + # Change power state to ON as server is automatically + # shutdown after the operation. + manager_utils.node_power_action(task, states.POWER_ON) + + class IRMCManagement(ipmitool.IPMIManagement): def get_properties(self): @@ -249,9 +345,25 @@ class IRMCManagement(ipmitool.IPMIManagement): node = task.node irmc_client = irmc_common.get_irmc_client(node) try: - irmc_client(scci.POWER_RAISE_NMI) - except scci.SCCIClientError as err: + irmc_client(irmc.scci.POWER_RAISE_NMI) + except irmc.scci.SCCIClientError as err: LOG.error('iRMC Inject NMI failed for node %(node)s: %(err)s.', {'node': node.uuid, 'err': err}) raise exception.IRMCOperationError( - operation=scci.POWER_RAISE_NMI, error=err) + operation=irmc.scci.POWER_RAISE_NMI, error=err) + + @METRICS.timer('IRMCManagement.restore_irmc_bios_config') + @base.clean_step( + priority=CONF.irmc.clean_priority_restore_irmc_bios_config) + def restore_irmc_bios_config(self, task): + """Restore BIOS config for a node. + + :param task: a task from TaskManager. + :raises: NodeCleaningFailure, on failure to execute step. + :returns: None. + """ + try: + _restore_bios_config(task) + except exception.IRMCOperationError as e: + raise exception.NodeCleaningFailure(node=task.node.uuid, + reason=e) diff --git a/ironic/drivers/pxe.py b/ironic/drivers/pxe.py index 596de3ffd2..f21db501e4 100644 --- a/ironic/drivers/pxe.py +++ b/ironic/drivers/pxe.py @@ -34,6 +34,7 @@ from ironic.drivers.modules.ilo import power as ilo_power from ironic.drivers.modules.ilo import vendor as ilo_vendor from ironic.drivers.modules import inspector from ironic.drivers.modules import ipmitool +from ironic.drivers.modules.irmc import boot as irmc_boot from ironic.drivers.modules.irmc import inspect as irmc_inspect from ironic.drivers.modules.irmc import management as irmc_management from ironic.drivers.modules.irmc import power as irmc_power @@ -142,7 +143,7 @@ class PXEAndIRMCDriver(base.BaseDriver): reason=_("Unable to import python-scciclient library")) self.power = irmc_power.IRMCPower() self.console = ipmitool.IPMIShellinaboxConsole() - self.boot = pxe.PXEBoot() + self.boot = irmc_boot.IRMCPXEBoot() self.deploy = iscsi_deploy.ISCSIDeploy() self.management = irmc_management.IRMCManagement() self.inspect = irmc_inspect.IRMCInspect() diff --git a/ironic/tests/unit/drivers/modules/irmc/test_boot.py b/ironic/tests/unit/drivers/modules/irmc/test_boot.py index d515baa4dc..1cbbf1d892 100644 --- a/ironic/tests/unit/drivers/modules/irmc/test_boot.py +++ b/ironic/tests/unit/drivers/modules/irmc/test_boot.py @@ -36,12 +36,13 @@ from ironic.conductor import utils as manager_utils from ironic.drivers.modules import deploy_utils from ironic.drivers.modules.irmc import boot as irmc_boot from ironic.drivers.modules.irmc import common as irmc_common +from ironic.drivers.modules.irmc import management as irmc_management +from ironic.drivers.modules import pxe from ironic.tests.unit.conductor import mgr_utils 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 - if six.PY3: import io file = io.BytesIO @@ -896,13 +897,16 @@ class IRMCVirtualMediaBootTestCase(db_base.DbTestCase): validate_prop_mock.assert_called_once_with( task.context, d_info, ['kernel', 'ramdisk']) + @mock.patch.object(irmc_management, 'backup_bios_config', spec_set=True, + autospec=True) @mock.patch.object(irmc_boot, '_setup_deploy_iso', spec_set=True, autospec=True) @mock.patch.object(deploy_utils, 'get_single_nic_with_vif_port_id', spec_set=True, autospec=True) def _test_prepare_ramdisk(self, get_single_nic_with_vif_port_id_mock, - _setup_deploy_iso_mock): + _setup_deploy_iso_mock, + mock_backup_bios): instance_info = self.node.instance_info instance_info['irmc_boot_iso'] = 'glance://abcdef' instance_info['image_source'] = '6b2f0c0c-79e8-4db6-842e-43c9764204af' @@ -922,6 +926,9 @@ class IRMCVirtualMediaBootTestCase(db_base.DbTestCase): task, expected_ramdisk_opts) self.assertEqual('glance://abcdef', self.node.instance_info['irmc_boot_iso']) + provision_state = task.node.provision_state + self.assertEqual(1 if provision_state == states.DEPLOYING else 0, + mock_backup_bios.call_count) def test_prepare_ramdisk_glance_image_deploying(self): self.node.provision_state = states.DEPLOYING @@ -1051,3 +1058,42 @@ class IRMCVirtualMediaBootTestCase(db_base.DbTestCase): cfg.CONF.set_override('remote_image_share_type', 'nfs', 'irmc') self.assertRaises(ValueError, cfg.CONF.set_override, 'remote_image_share_type', 'fake', 'irmc') + + +class IRMCPXEBootTestCase(db_base.DbTestCase): + + def setUp(self): + super(IRMCPXEBootTestCase, self).setUp() + mgr_utils.mock_the_extension_manager(driver="pxe_irmc") + self.node = obj_utils.create_test_node( + self.context, driver='pxe_irmc', driver_info=INFO_DICT) + + @mock.patch.object(irmc_management, 'backup_bios_config', spec_set=True, + autospec=True) + @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', spec_set=True, + autospec=True) + def test_prepare_ramdisk_with_backup_bios(self, mock_parent_prepare, + mock_backup_bios): + self.node.provision_state = states.DEPLOYING + self.node.save() + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.driver.boot.prepare_ramdisk(task, {}) + mock_backup_bios.assert_called_once_with(task) + mock_parent_prepare.assert_called_once_with( + task.driver.boot, task, {}) + + @mock.patch.object(irmc_management, 'backup_bios_config', spec_set=True, + autospec=True) + @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', spec_set=True, + autospec=True) + def test_prepare_ramdisk_without_backup_bios(self, mock_parent_prepare, + mock_backup_bios): + self.node.provision_state = states.CLEANING + self.node.save() + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.driver.boot.prepare_ramdisk(task, {}) + self.assertFalse(mock_backup_bios.called) + mock_parent_prepare.assert_called_once_with( + task.driver.boot, task, {}) diff --git a/ironic/tests/unit/drivers/modules/irmc/test_management.py b/ironic/tests/unit/drivers/modules/irmc/test_management.py index 48da7ee9e3..3a63adc5aa 100644 --- a/ironic/tests/unit/drivers/modules/irmc/test_management.py +++ b/ironic/tests/unit/drivers/modules/irmc/test_management.py @@ -24,10 +24,13 @@ import mock from ironic.common import boot_devices from ironic.common import driver_factory 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 import ipmitool from ironic.drivers.modules.irmc import common as irmc_common from ironic.drivers.modules.irmc import management as irmc_management +from ironic.drivers.modules.irmc import power as irmc_power from ironic.drivers import utils as driver_utils from ironic.tests.unit.conductor import mgr_utils from ironic.tests.unit.db import base as db_base @@ -39,6 +42,116 @@ from ironic.tests.unit.objects import utils as obj_utils INFO_DICT = db_utils.get_test_irmc_info() +@mock.patch.object(irmc_management.irmc, 'elcm', + spec_set=mock_specs.SCCICLIENT_IRMC_ELCM_SPEC) +@mock.patch.object(manager_utils, 'node_power_action', + specset=True, autospec=True) +@mock.patch.object(irmc_power.IRMCPower, 'get_power_state', + return_value=states.POWER_ON, + specset=True, autospec=True) +class IRMCManagementFunctionsTestCase(db_base.DbTestCase): + def setUp(self): + super(IRMCManagementFunctionsTestCase, self).setUp() + driver_info = INFO_DICT + + mgr_utils.mock_the_extension_manager(driver="fake_irmc") + self.driver = driver_factory.get_driver("fake_irmc") + self.node = obj_utils.create_test_node(self.context, + driver='fake_irmc', + driver_info=driver_info) + self.info = irmc_common.parse_driver_info(self.node) + + irmc_management.irmc.scci.SCCIError = Exception + irmc_management.irmc.scci.SCCIInvalidInputError = ValueError + + def test_backup_bios_config(self, mock_get_power, mock_power_action, + mock_elcm): + self.config(clean_priority_restore_irmc_bios_config=10, group='irmc') + bios_config = {'Server': {'System': {'BiosConfig': {'key1': 'val1'}}}} + mock_elcm.backup_bios_config.return_value = { + 'bios_config': bios_config} + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + irmc_management.backup_bios_config(task) + + self.assertEqual(bios_config, task.node.driver_internal_info[ + 'irmc_bios_config']) + self.assertEqual(1, mock_elcm.backup_bios_config.call_count) + + def test_backup_bios_config_skipped(self, mock_get_power, + mock_power_action, mock_elcm): + self.config(clean_priority_restore_irmc_bios_config=0, group='irmc') + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + irmc_management.backup_bios_config(task) + + self.assertNotIn('irmc_bios_config', + task.node.driver_internal_info) + self.assertFalse(mock_elcm.backup_bios_config.called) + + def test_backup_bios_config_failed(self, mock_get_power, + mock_power_action, mock_elcm): + self.config(clean_priority_restore_irmc_bios_config=10, group='irmc') + mock_elcm.backup_bios_config.side_effect = Exception + + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + self.assertRaises(exception.IRMCOperationError, + irmc_management.backup_bios_config, + task) + self.assertNotIn('irmc_bios_config', + task.node.driver_internal_info) + self.assertEqual(1, mock_elcm.backup_bios_config.call_count) + + def test__restore_bios_config(self, mock_get_power, mock_power_action, + mock_elcm): + + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + # Set bios data for the node info + task.node.driver_internal_info['irmc_bios_config'] = 'data' + irmc_management._restore_bios_config(task) + + self.assertEqual(1, mock_elcm.restore_bios_config.call_count) + + def test__restore_bios_config_failed(self, mock_get_power, + mock_power_action, + mock_elcm): + mock_elcm.restore_bios_config.side_effect = Exception + + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + # Set bios data for the node info + task.node.driver_internal_info['irmc_bios_config'] = 'data' + + self.assertRaises(exception.IRMCOperationError, + irmc_management._restore_bios_config, + task) + # Backed up BIOS config is still in the node object + self.assertEqual('data', task.node.driver_internal_info[ + 'irmc_bios_config']) + self.assertTrue(mock_elcm.restore_bios_config.called) + + def test__restore_bios_config_corrupted(self, mock_get_power, + mock_power_action, + mock_elcm): + mock_elcm.restore_bios_config.side_effect = \ + irmc_management.irmc.scci.SCCIInvalidInputError + + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + # Set bios data for the node info + task.node.driver_internal_info['irmc_bios_config'] = 'data' + + self.assertRaises(exception.IRMCOperationError, + irmc_management._restore_bios_config, + task) + # Backed up BIOS config is removed from the node object + self.assertNotIn('irmc_bios_config', + task.node.driver_internal_info) + self.assertTrue(mock_elcm.restore_bios_config.called) + + class IRMCManagementTestCase(db_base.DbTestCase): def setUp(self): super(IRMCManagementTestCase, self).setUp() @@ -259,7 +372,7 @@ class IRMCManagementTestCase(db_base.DbTestCase): task, "unknown") - @mock.patch.object(irmc_management, 'scci', + @mock.patch.object(irmc_management.irmc, 'scci', spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) @mock.patch.object(irmc_common, 'get_irmc_report', spec_set=True, autospec=True) @@ -307,7 +420,7 @@ class IRMCManagementTestCase(db_base.DbTestCase): } self.assertEqual(expected, sensor_dict) - @mock.patch.object(irmc_management, 'scci', + @mock.patch.object(irmc_management.irmc, 'scci', spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) @mock.patch.object(irmc_common, 'get_irmc_report', spec_set=True, autospec=True) @@ -350,8 +463,8 @@ class IRMCManagementTestCase(db_base.DbTestCase): get_irmc_report_mock.side_effect = exception.InvalidParameterValue( "Fake Error") - irmc_management.scci.SCCIInvalidInputError = Exception - irmc_management.scci.SCCIClientError = Exception + irmc_management.irmc.scci.SCCIInvalidInputError = Exception + irmc_management.irmc.scci.SCCIClientError = Exception with task_manager.acquire(self.context, self.node.uuid) as task: task.node.driver_info['irmc_sensor_method'] = 'scci' @@ -373,7 +486,7 @@ class IRMCManagementTestCase(db_base.DbTestCase): self.driver.management.inject_nmi(task) irmc_client.assert_called_once_with( - irmc_management.scci.POWER_RAISE_NMI) + irmc_management.irmc.scci.POWER_RAISE_NMI) self.assertFalse(mock_log.called) @mock.patch.object(irmc_management.LOG, 'error', spec_set=True, @@ -384,7 +497,7 @@ class IRMCManagementTestCase(db_base.DbTestCase): mock_log): irmc_client = mock_get_irmc_client.return_value irmc_client.side_effect = Exception() - irmc_management.scci.SCCIClientError = Exception + irmc_management.irmc.scci.SCCIClientError = Exception with task_manager.acquire(self.context, self.node.uuid) as task: self.assertRaises(exception.IRMCOperationError, @@ -392,5 +505,14 @@ class IRMCManagementTestCase(db_base.DbTestCase): task) irmc_client.assert_called_once_with( - irmc_management.scci.POWER_RAISE_NMI) + irmc_management.irmc.scci.POWER_RAISE_NMI) self.assertTrue(mock_log.called) + + @mock.patch.object(irmc_management, '_restore_bios_config', + spec_set=True, autospec=True) + def test_management_interface_restore_irmc_bios_config(self, + mock_restore_bios): + with task_manager.acquire(self.context, self.node.uuid) as task: + result = task.driver.management.restore_irmc_bios_config(task) + self.assertIsNone(result) + mock_restore_bios.assert_called_once_with(task) diff --git a/ironic/tests/unit/drivers/test_pxe.py b/ironic/tests/unit/drivers/test_pxe.py index 4378ab38f8..4471cf0028 100644 --- a/ironic/tests/unit/drivers/test_pxe.py +++ b/ironic/tests/unit/drivers/test_pxe.py @@ -29,6 +29,7 @@ from ironic.drivers.modules.ilo import management as ilo_management from ironic.drivers.modules.ilo import power as ilo_power from ironic.drivers.modules.ilo import vendor as ilo_vendor from ironic.drivers.modules import ipmitool +from ironic.drivers.modules.irmc import boot as irmc_boot from ironic.drivers.modules.irmc import management as irmc_management from ironic.drivers.modules.irmc import power as irmc_power from ironic.drivers.modules import iscsi_deploy @@ -107,7 +108,7 @@ class PXEDriversTestCase(testtools.TestCase): self.assertIsInstance(driver.power, irmc_power.IRMCPower) self.assertIsInstance(driver.console, ipmitool.IPMIShellinaboxConsole) - self.assertIsInstance(driver.boot, pxe_module.PXEBoot) + self.assertIsInstance(driver.boot, irmc_boot.IRMCPXEBoot) self.assertIsInstance(driver.deploy, iscsi_deploy.ISCSIDeploy) self.assertIsInstance(driver.management, irmc_management.IRMCManagement) diff --git a/ironic/tests/unit/drivers/third_party_driver_mock_specs.py b/ironic/tests/unit/drivers/third_party_driver_mock_specs.py index 8a51c89faf..a3e9b0183b 100644 --- a/ironic/tests/unit/drivers/third_party_driver_mock_specs.py +++ b/ironic/tests/unit/drivers/third_party_driver_mock_specs.py @@ -91,6 +91,7 @@ SCCICLIENT_IRMC_SCCI_SPEC = ( 'UNMOUNT_CD', 'MOUNT_FD', 'UNMOUNT_FD', + 'SCCIError', 'SCCIClientError', 'SCCIInvalidInputError', 'get_share_type', @@ -101,6 +102,10 @@ SCCICLIENT_IRMC_SCCI_SPEC = ( 'get_virtual_fd_set_params_cmd', 'get_essential_properties', ) +SCCICLIENT_IRMC_ELCM_SPEC = ( + 'backup_bios_config', + 'restore_bios_config', +) ONEVIEWCLIENT_SPEC = ( 'client', diff --git a/ironic/tests/unit/drivers/third_party_driver_mocks.py b/ironic/tests/unit/drivers/third_party_driver_mocks.py index 0cb3ebcbbf..31fec78b75 100644 --- a/ironic/tests/unit/drivers/third_party_driver_mocks.py +++ b/ironic/tests/unit/drivers/third_party_driver_mocks.py @@ -164,6 +164,8 @@ if not scciclient: UNMOUNT_CD=mock.sentinel.UNMOUNT_CD, MOUNT_FD=mock.sentinel.MOUNT_FD, UNMOUNT_FD=mock.sentinel.UNMOUNT_FD) + sys.modules['scciclient.irmc.elcm'] = mock.MagicMock( + spec_set=mock_specs.SCCICLIENT_IRMC_ELCM_SPEC) # if anything has loaded the iRMC driver yet, reload it now that the diff --git a/releasenotes/notes/irmc-add-clean-step-reset-bios-config-a8bed625670b7fdf.yaml b/releasenotes/notes/irmc-add-clean-step-reset-bios-config-a8bed625670b7fdf.yaml new file mode 100644 index 0000000000..36325b66fd --- /dev/null +++ b/releasenotes/notes/irmc-add-clean-step-reset-bios-config-a8bed625670b7fdf.yaml @@ -0,0 +1,16 @@ +--- +features: + - Adds new boot interface named ``irmc-pxe``. + - Adds clean step ``restore_irmc_bios_config`` to restore BIOS config + for a node during automatic cleaning. +upgrade: + - Adds new configuration option + ``[irmc]clean_priority_restore_irmc_bios_config``, which + enables setting priority for the clean step. Default value for + this option is 0, which means the clean step is disabled. +deprecations: + - Deprecates the boot interface ``pxe`` from using with hardware type + ``irmc``. It is recommended for operator to switch to use the boot + iterface ``irmc-pxe`` as soon as possible. Using the interface + ``pxe`` with ``irmc`` drivers will cause the reset bios feature + not to work even the priority option is set to enable. diff --git a/setup.cfg b/setup.cfg index 4022041ac4..a55e8e2878 100644 --- a/setup.cfg +++ b/setup.cfg @@ -86,6 +86,7 @@ ironic.hardware.interfaces.boot = fake = ironic.drivers.modules.fake:FakeBoot ilo-pxe = ironic.drivers.modules.ilo.boot:IloPXEBoot ilo-virtual-media = ironic.drivers.modules.ilo.boot:IloVirtualMediaBoot + irmc-pxe = ironic.drivers.modules.irmc.boot:IRMCPXEBoot irmc-virtual-media = ironic.drivers.modules.irmc.boot:IRMCVirtualMediaBoot pxe = ironic.drivers.modules.pxe:PXEBoot