From e43aee3db8f1ac95ff1c8683a83a98ac5decb1d8 Mon Sep 17 00:00:00 2001 From: Shivanand Tendulker Date: Wed, 17 Jun 2020 11:19:36 -0400 Subject: [PATCH] Adds boot mode support to iLO management interface This change adds 'get_boot_mode', 'set_boot_mode' and 'get_supported_boot_modes' methods to driver management interface of 'ilo' and 'ilo5' hardware types. Story: 2007827 Task: 40104 Change-Id: I34701b790ba91431b0b943ae8060a43d7ea9abbb --- ironic/drivers/modules/ilo/common.py | 27 ++++++ ironic/drivers/modules/ilo/management.py | 54 ++++++++++++ .../unit/drivers/modules/ilo/test_common.py | 23 +++++ .../drivers/modules/ilo/test_management.py | 85 +++++++++++++++++++ ...mode-management-apis-8173002daf79894c.yaml | 5 ++ 5 files changed, 194 insertions(+) create mode 100644 releasenotes/notes/ilo-support-boot-mode-management-apis-8173002daf79894c.yaml diff --git a/ironic/drivers/modules/ilo/common.py b/ironic/drivers/modules/ilo/common.py index 62c6bbb233..ef330c5c2b 100644 --- a/ironic/drivers/modules/ilo/common.py +++ b/ironic/drivers/modules/ilo/common.py @@ -116,6 +116,15 @@ POST_INPOSTDISCOVERY_STATE = "InPostDiscoveryComplete" POST_FINISHEDPOST_STATE = "FinishedPost" """ Node is in FinishedPost post state.""" +SUPPORTED_BOOT_MODE_LEGACY_BIOS_ONLY = 'legacy bios only' +""" Node supports only legacy BIOS boot mode.""" + +SUPPORTED_BOOT_MODE_UEFI_ONLY = 'uefi only' +""" Node supports only UEFI boot mode.""" + +SUPPORTED_BOOT_MODE_LEGACY_BIOS_AND_UEFI = 'legacy bios and uefi' +""" Node supports both legacy BIOS and UEFI boot mode.""" + def copy_image_to_web_server(source_file_path, destination): """Copies the given image to the http web server. @@ -492,6 +501,24 @@ def set_boot_mode(node, boot_mode): {'uuid': node.uuid, 'boot_mode': boot_mode}) +def get_current_boot_mode(node): + """Get the current boot mode for a node. + + :param node: an ironic node object. + :raises: IloOperationError if failed to fetch boot mode. + :raises: IloOperationNotSupported if node does not support getting pending + boot mode. + """ + ilo_object = get_ilo_object(node) + operation = _("Get current boot mode") + try: + c_boot_mode = ilo_object.get_current_boot_mode() + return BOOT_MODE_ILO_TO_GENERIC[c_boot_mode.lower()] + except ilo_error.IloError as ilo_exception: + raise exception.IloOperationError(operation=operation, + error=ilo_exception) + + def update_boot_mode(task): """Update instance_info with boot mode to be used for deploy. diff --git a/ironic/drivers/modules/ilo/management.py b/ironic/drivers/modules/ilo/management.py index 07cbe7b418..45bac59a12 100644 --- a/ironic/drivers/modules/ilo/management.py +++ b/ironic/drivers/modules/ilo/management.py @@ -24,6 +24,7 @@ from oslo_utils import excutils 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.common import states @@ -684,6 +685,59 @@ class IloManagement(base.ManagementInterface): raise exception.IloOperationError(operation=operation, error=ilo_exception) + def get_supported_boot_modes(self, task): + """Get a list of the supported boot devices. + + :param task: a task from TaskManager. + :raises: IloOperationError if any exception happens in proliantutils + :returns: A list with the supported boot devices defined + in :mod:`ironic.common.boot_devices`. + """ + node = task.node + ilo_object = ilo_common.get_ilo_object(node) + try: + modes = ilo_object.get_supported_boot_mode() + if modes == ilo_common.SUPPORTED_BOOT_MODE_LEGACY_BIOS_ONLY: + return [boot_modes.LEGACY_BIOS] + elif modes == ilo_common.SUPPORTED_BOOT_MODE_UEFI_ONLY: + return [boot_modes.UEFI] + elif modes == ilo_common.SUPPORTED_BOOT_MODE_LEGACY_BIOS_AND_UEFI: + return [boot_modes.UEFI, boot_modes.LEGACY_BIOS] + except ilo_error.IloError as ilo_exception: + operation = _("Get supported boot modes") + raise exception.IloOperationError(operation=operation, + error=ilo_exception) + + @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: IloOperationError if setting boot mode failed. + """ + if mode not in self.get_supported_boot_modes(task): + raise exception.InvalidParameterValue(_( + "The given boot mode '%s' is not supported.") % mode) + ilo_common.set_boot_mode(task.node, mode) + + 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: IloOperationError on an error from IloClient library. + :returns: The boot mode, one of :mod:`ironic.common.boot_mode` or + None if it is unknown. + """ + return ilo_common.get_current_boot_mode(task.node) + class Ilo5Management(IloManagement): diff --git a/ironic/tests/unit/drivers/modules/ilo/test_common.py b/ironic/tests/unit/drivers/modules/ilo/test_common.py index f06e21af3d..d5f486aba1 100644 --- a/ironic/tests/unit/drivers/modules/ilo/test_common.py +++ b/ironic/tests/unit/drivers/modules/ilo/test_common.py @@ -436,6 +436,29 @@ class IloCommonMethodsTestCase(BaseIloTest): get_ilo_object_mock.assert_called_once_with(self.node) get_pending_boot_mode_mock.assert_called_once_with() + @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True, + autospec=True) + def test_get_current_boot_mode(self, get_ilo_object_mock): + ilo_object_mock = get_ilo_object_mock.return_value + get_current_boot_mode_mock = ilo_object_mock.get_current_boot_mode + get_current_boot_mode_mock.return_value = 'LEGACY' + ret = ilo_common.get_current_boot_mode(self.node) + self.assertEqual('bios', ret) + get_ilo_object_mock.assert_called_once_with(self.node) + get_current_boot_mode_mock.assert_called_once_with() + + @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True, + autospec=True) + def test_get_current_boot_mode_fail(self, get_ilo_object_mock): + ilo_object_mock = get_ilo_object_mock.return_value + get_current_boot_mode_mock = ilo_object_mock.get_current_boot_mode + exc = ilo_error.IloError('error') + get_current_boot_mode_mock.side_effect = exc + self.assertRaises(exception.IloOperationError, + ilo_common.get_current_boot_mode, self.node) + get_ilo_object_mock.assert_called_once_with(self.node) + get_current_boot_mode_mock.assert_called_once_with() + @mock.patch.object(ilo_common, 'set_boot_mode', spec_set=True, autospec=True) def test_update_boot_mode_instance_info_exists(self, diff --git a/ironic/tests/unit/drivers/modules/ilo/test_management.py b/ironic/tests/unit/drivers/modules/ilo/test_management.py index 396df5ebee..e7cb060c9f 100644 --- a/ironic/tests/unit/drivers/modules/ilo/test_management.py +++ b/ironic/tests/unit/drivers/modules/ilo/test_management.py @@ -16,10 +16,12 @@ from unittest import mock +import ddt from oslo_utils import importutils from oslo_utils import uuidutils from ironic.common import boot_devices +from ironic.common import boot_modes from ironic.common import exception from ironic.common import states from ironic.conductor import task_manager @@ -41,6 +43,7 @@ ilo_error = importutils.try_import('proliantutils.exception') INFO_DICT = db_utils.get_test_ilo_info() +@ddt.ddt class IloManagementTestCase(test_common.BaseIloTest): def setUp(self): @@ -1181,6 +1184,88 @@ class IloManagementTestCase(test_common.BaseIloTest): task.driver.management.inject_nmi, task) + @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True, + autospec=True) + @ddt.data((ilo_common.SUPPORTED_BOOT_MODE_LEGACY_BIOS_ONLY, + ['bios']), + (ilo_common.SUPPORTED_BOOT_MODE_UEFI_ONLY, + ['uefi']), + (ilo_common.SUPPORTED_BOOT_MODE_LEGACY_BIOS_AND_UEFI, + ['uefi', 'bios'])) + @ddt.unpack + def test_get_supported_boot_modes(self, boot_modes_val, + exp_boot_modes, + get_ilo_object_mock): + ilo_object_mock = get_ilo_object_mock.return_value + ilo_object_mock.get_supported_boot_mode.return_value = boot_modes_val + 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(exp_boot_modes, supported_boot_modes) + + @mock.patch.object(ilo_common, 'set_boot_mode', spec_set=True, + autospec=True) + @mock.patch.object(ilo_management.IloManagement, + 'get_supported_boot_modes', + spec_set=True, autospec=True) + def test_set_boot_mode(self, supp_boot_modes_mock, + set_boot_mode_mock): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + exp_boot_modes = [boot_modes.UEFI, boot_modes.LEGACY_BIOS] + supp_boot_modes_mock.return_value = exp_boot_modes + + for mode in exp_boot_modes: + task.driver.management.set_boot_mode(task, mode=mode) + supp_boot_modes_mock.assert_called_once_with(mock.ANY, task) + set_boot_mode_mock.assert_called_once_with(task.node, mode) + set_boot_mode_mock.reset_mock() + supp_boot_modes_mock.reset_mock() + + @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True, + autospec=True) + @mock.patch.object(ilo_management.IloManagement, + 'get_supported_boot_modes', + spec_set=True, autospec=True) + def test_set_boot_mode_fail(self, supp_boot_modes_mock, + get_ilo_object_mock): + ilo_mock_obj = get_ilo_object_mock.return_value + ilo_mock_obj.get_pending_boot_mode.return_value = 'legacy' + exc = ilo_error.IloError('error') + ilo_mock_obj.set_pending_boot_mode.side_effect = exc + exp_boot_modes = [boot_modes.UEFI, boot_modes.LEGACY_BIOS] + supp_boot_modes_mock.return_value = exp_boot_modes + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + self.assertRaisesRegex( + exception.IloOperationError, 'uefi as boot mode failed', + task.driver.management.set_boot_mode, task, boot_modes.UEFI) + supp_boot_modes_mock.assert_called_once_with(mock.ANY, task) + + @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True, + autospec=True) + def test_get_boot_mode(self, get_ilo_object_mock): + expected = 'bios' + ilo_mock_obj = get_ilo_object_mock.return_value + ilo_mock_obj.get_current_boot_mode.return_value = 'LEGACY' + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + response = task.driver.management.get_boot_mode(task) + self.assertEqual(expected, response) + + @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True, + autospec=True) + def test_get_boot_mode_fail(self, get_ilo_object_mock): + ilo_mock_obj = get_ilo_object_mock.return_value + exc = ilo_error.IloError('error') + ilo_mock_obj.get_current_boot_mode.side_effect = exc + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + self.assertRaisesRegex( + exception.IloOperationError, 'Get current boot mode', + task.driver.management.get_boot_mode, task) + class Ilo5ManagementTestCase(db_base.DbTestCase): diff --git a/releasenotes/notes/ilo-support-boot-mode-management-apis-8173002daf79894c.yaml b/releasenotes/notes/ilo-support-boot-mode-management-apis-8173002daf79894c.yaml new file mode 100644 index 0000000000..bb3f9ae520 --- /dev/null +++ b/releasenotes/notes/ilo-support-boot-mode-management-apis-8173002daf79894c.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds support for boot mode retrieval and setting with the ``ilo`` and + ``ilo5`` hardware types.