From 7c5f6557946ce71d6e6dc93445424c7795740ad2 Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Mon, 5 Feb 2018 13:09:29 +0100 Subject: [PATCH] Added Redfish boot mode management This change adds node boot mode management to the Redfish driver. Story: 1731013 Task: 9271 Change-Id: I3aa11cc0ce9da9cf6c801300370bd7ce420f434a --- ironic/drivers/modules/redfish/management.py | 87 ++++++++++++++++++- .../modules/redfish/test_management.py | 63 ++++++++++++++ .../drivers/third_party_driver_mock_specs.py | 2 + .../unit/drivers/third_party_driver_mocks.py | 4 +- ...sh-boot-mode-support-2f1a2568e71c65d0.yaml | 4 + 5 files changed, 156 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/add-redfish-boot-mode-support-2f1a2568e71c65d0.yaml diff --git a/ironic/drivers/modules/redfish/management.py b/ironic/drivers/modules/redfish/management.py index 7562b99106..3c86fde571 100644 --- a/ironic/drivers/modules/redfish/management.py +++ b/ironic/drivers/modules/redfish/management.py @@ -17,6 +17,7 @@ from oslo_log import log from oslo_utils import importutils from ironic.common import boot_devices +from ironic.common import boot_modes from ironic.common import exception from ironic.common.i18n import _ from ironic.conductor import task_manager @@ -37,6 +38,13 @@ if sushy: BOOT_DEVICE_MAP_REV = {v: k for k, v in BOOT_DEVICE_MAP.items()} + BOOT_MODE_MAP = { + sushy.BOOT_SOURCE_MODE_UEFI: boot_modes.UEFI, + sushy.BOOT_SOURCE_MODE_BIOS: boot_modes.LEGACY_BIOS + } + + BOOT_MODE_MAP_REV = {v: k for k, v in BOOT_MODE_MAP.items()} + BOOT_DEVICE_PERSISTENT_MAP = { sushy.BOOT_SOURCE_ENABLED_CONTINUOUS: True, sushy.BOOT_SOURCE_ENABLED_ONCE: False @@ -103,9 +111,7 @@ class RedfishManagement(base.ManagementInterface): :raises: RedfishError on an error from the Sushy library """ system = redfish_utils.get_system(task.node) - # TODO(lucasagomes): set_system_boot_source() also supports mode - # for UEFI and BIOS we should get it from instance_info and pass - # it along this call + try: system.set_system_boot_source( BOOT_DEVICE_MAP_REV[device], @@ -141,6 +147,81 @@ class RedfishManagement(base.ManagementInterface): 'persistent': BOOT_DEVICE_PERSISTENT_MAP.get( system.boot.get('enabled'))} + def get_supported_boot_modes(self, task): + """Get a list of the supported boot modes. + + :param task: A task from TaskManager. + :returns: A list with the supported boot modes defined + in :mod:`ironic.common.boot_modes`. If boot + mode support can't be determined, empty list + is returned. + """ + return list(BOOT_MODE_MAP_REV) + + @task_manager.require_exclusive_lock + 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 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 get_sensors_data(self, task): """Get sensors data. diff --git a/ironic/tests/unit/drivers/modules/redfish/test_management.py b/ironic/tests/unit/drivers/modules/redfish/test_management.py index de8fad54c6..d42e4a4e76 100644 --- a/ironic/tests/unit/drivers/modules/redfish/test_management.py +++ b/ironic/tests/unit/drivers/modules/redfish/test_management.py @@ -17,6 +17,7 @@ import mock from oslo_utils import importutils from ironic.common import boot_devices +from ironic.common import boot_modes from ironic.common import exception from ironic.conductor import task_manager from ironic.drivers.modules.redfish import management as redfish_mgmt @@ -153,6 +154,68 @@ class RedfishManagementTestCase(db_base.DbTestCase): 'persistent': True} self.assertEqual(expected, response) + def test_get_supported_boot_modes(self): + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + supported_boot_modes = ( + task.driver.management.get_supported_boot_modes(task)) + self.assertEqual(list(redfish_mgmt.BOOT_MODE_MAP_REV), + supported_boot_modes) + + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + def test_set_boot_mode(self, mock_get_system): + fake_system = mock.Mock() + mock_get_system.return_value = fake_system + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + expected_values = [ + (boot_modes.LEGACY_BIOS, sushy.BOOT_SOURCE_MODE_BIOS), + (boot_modes.UEFI, sushy.BOOT_SOURCE_MODE_UEFI) + ] + + for mode, expected in expected_values: + task.driver.management.set_boot_mode(task, mode=mode) + + # Asserts + fake_system.set_system_boot_source.assert_called_once_with( + mock.ANY, enabled=mock.ANY, mode=mode) + mock_get_system.assert_called_once_with(task.node) + + # Reset mocks + fake_system.set_system_boot_source.reset_mock() + mock_get_system.reset_mock() + + @mock.patch('ironic.drivers.modules.redfish.management.sushy') + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + def test_set_boot_mode_fail(self, mock_get_system, mock_sushy): + fake_system = mock.Mock() + mock_sushy.exceptions.SushyError = MockedSushyError + fake_system.set_system_boot_source.side_effect = MockedSushyError + mock_get_system.return_value = fake_system + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + self.assertRaisesRegex( + exception.RedfishError, 'Setting boot mode', + task.driver.management.set_boot_mode, task, boot_modes.UEFI) + fake_system.set_system_boot_source.assert_called_once_with( + mock.ANY, enabled=mock.ANY, mode=boot_modes.UEFI) + mock_get_system.assert_called_once_with(task.node) + + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + def test_get_boot_mode(self, mock_get_system): + boot_attribute = { + 'target': sushy.BOOT_SOURCE_TARGET_PXE, + 'enabled': sushy.BOOT_SOURCE_ENABLED_CONTINUOUS, + 'mode': sushy.BOOT_SOURCE_MODE_BIOS, + } + fake_system = mock.Mock(boot=boot_attribute) + mock_get_system.return_value = fake_system + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + response = task.driver.management.get_boot_mode(task) + expected = boot_modes.LEGACY_BIOS + self.assertEqual(expected, response) + def test_get_sensors_data(self): with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: 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 762a1c28aa..eb5cb467b8 100644 --- a/ironic/tests/unit/drivers/third_party_driver_mock_specs.py +++ b/ironic/tests/unit/drivers/third_party_driver_mock_specs.py @@ -163,6 +163,8 @@ SUSHY_CONSTANTS_SPEC = ( 'RESET_NMI', 'BOOT_SOURCE_ENABLED_CONTINUOUS', 'BOOT_SOURCE_ENABLED_ONCE', + 'BOOT_SOURCE_MODE_BIOS', + 'BOOT_SOURCE_MODE_UEFI', ) XCLARITY_SPEC = ( diff --git a/ironic/tests/unit/drivers/third_party_driver_mocks.py b/ironic/tests/unit/drivers/third_party_driver_mocks.py index 055707fe3c..24f8f27373 100644 --- a/ironic/tests/unit/drivers/third_party_driver_mocks.py +++ b/ironic/tests/unit/drivers/third_party_driver_mocks.py @@ -241,7 +241,9 @@ if not sushy: RESET_FORCE_RESTART='force restart', RESET_NMI='nmi', BOOT_SOURCE_ENABLED_CONTINUOUS='continuous', - BOOT_SOURCE_ENABLED_ONCE='once' + BOOT_SOURCE_ENABLED_ONCE='once', + BOOT_SOURCE_MODE_BIOS='bios', + BOOT_SOURCE_MODE_UEFI='uefi' ) sys.modules['sushy'] = sushy diff --git a/releasenotes/notes/add-redfish-boot-mode-support-2f1a2568e71c65d0.yaml b/releasenotes/notes/add-redfish-boot-mode-support-2f1a2568e71c65d0.yaml new file mode 100644 index 0000000000..c86bb30ee9 --- /dev/null +++ b/releasenotes/notes/add-redfish-boot-mode-support-2f1a2568e71c65d0.yaml @@ -0,0 +1,4 @@ +--- +features: + - Adds support for reading and setting bare metal node's boot mode + using the ``redfish`` management interface.