From 952695be334c2d68b3abc8315822e512a1caf900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aija=20Jaunt=C4=93va?= Date: Fri, 5 Feb 2021 04:59:01 -0500 Subject: [PATCH] Add Redfish RAID interface to idrac HW type Adds MVP support for idrac-redfish to RAID interface. Based on generic redfish implementation, but requires OEM extension to check when `Immediate` time becomes available shortly after IPA starts executing steps. Does not support foreign disks, convert from non-RAID mode. Story: 2008602 Task: 41778 Depends-On: https://review.opendev.org/c/x/sushy-oem-idrac/+/776224 Change-Id: Iefb7f882c97e33a176962e4e907163d9e4809445 --- doc/source/admin/drivers/idrac.rst | 38 ++-- driver-requirements.txt | 2 +- ironic/drivers/drac.py | 5 +- ironic/drivers/modules/drac/raid.py | 164 ++++++++++++++++++ ironic/drivers/modules/redfish/raid.py | 26 +++ ironic/tests/unit/db/utils.py | 4 + .../unit/drivers/modules/drac/test_raid.py | 162 +++++++++++++++++ .../unit/drivers/modules/redfish/test_raid.py | 16 ++ ironic/tests/unit/drivers/test_drac.py | 12 +- ...redfish-raid-support-414aad5e633a160f.yaml | 18 ++ setup.cfg | 1 + 11 files changed, 434 insertions(+), 14 deletions(-) create mode 100644 releasenotes/notes/idrac-add-redfish-raid-support-414aad5e633a160f.yaml diff --git a/doc/source/admin/drivers/idrac.rst b/doc/source/admin/drivers/idrac.rst index d94a577ef8..b5d217554f 100644 --- a/doc/source/admin/drivers/idrac.rst +++ b/doc/source/admin/drivers/idrac.rst @@ -55,17 +55,13 @@ Enabling The iDRAC driver supports WSMAN for the bios, inspect, management, power, raid, and vendor interfaces. In addition, it supports Redfish for -the bios, inspect, management, and power interfaces. The iDRAC driver +the bios, inspect, management, power, and raid interfaces. The iDRAC driver allows you to mix and match WSMAN and Redfish interfaces. The ``idrac-wsman`` implementation must be enabled to use WSMAN for an interface. The ``idrac-redfish`` implementation must be enabled to use Redfish for an interface. -.. NOTE:: - Redfish is supported for only the bios, inspect, management, and power - interfaces at the present time. - To enable the ``idrac`` hardware type with the minimum interfaces, all using WSMAN, add the following to your ``/etc/ironic/ironic.conf``: @@ -88,7 +84,7 @@ following configuration: enabled_inspect_interfaces=idrac-redfish enabled_management_interfaces=idrac-redfish enabled_power_interfaces=idrac-redfish - enabled_raid_interfaces=idrac-wsman + enabled_raid_interfaces=idrac-redfish enabled_vendor_interfaces=idrac-redfish Below is the list of supported interface implementations in priority @@ -106,7 +102,7 @@ Interface Supported Implementations ``management`` ``idrac-wsman``, ``idrac``, ``idrac-redfish`` ``network`` ``flat``, ``neutron``, ``noop`` ``power`` ``idrac-wsman``, ``idrac``, ``idrac-redfish`` -``raid`` ``idrac-wsman``, ``idrac``, ``no-raid`` +``raid`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``, ``no-raid`` ``rescue`` ``no-rescue``, ``agent`` ``storage`` ``noop``, ``cinder``, ``external`` ``vendor`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``, @@ -180,7 +176,7 @@ hardware type using Redfish for all interfaces: --inspect-interface idrac-redfish \ --management-interface idrac-redfish \ --power-interface idrac-redfish \ - --raid-interface no-raid \ + --raid-interface idrac-redfish \ --vendor-interface idrac-redfish The following command enrolls a bare metal node with the ``idrac`` @@ -283,9 +279,12 @@ RAID Interface See :doc:`/admin/raid` for more information on Ironic RAID support. -The following properties are supported by the iDRAC WSMAN raid interface -implementation, ``idrac-wsman``: +The following properties are supported by the iDRAC WSMAN and Redfish RAID +interface implementation: +.. NOTE:: + When using ``idrac-redfish`` for RAID interface iDRAC firmware greater than + 4.40.00.00 is required. Mandatory properties -------------------- @@ -310,6 +309,11 @@ Optional properties Backing physical disk hints --------------------------- +.. NOTE:: + Backing physical disk hints are not widely tested with ``idrac-redfish`` yet + and they might not work as desired. This will be addressed in future + releases. + See :doc:`/admin/raid` for more information on backing disk hints. These are machine-independent information. The hints are specified for each @@ -408,6 +412,20 @@ be used to fetch the information directly from the Dell bare metal: physical_disks = client.list_physical_disks() print(physical_disks) +Or using ``sushy`` with Redfish: + +.. code-block:: python + + import sushy + + + client = sushy.Sushy('https://192.168.1.1', username='root', password='calvin', verify=False) + for s in client.get_system_collection().get_members(): + print("System: %(id)s" % {'id': s.identity}) + for c in system1.storage.get_members(): + print("\tController: %(id)s" % {'id': c.identity}) + for d in c.drives: + print("\t\tDrive: %(id)s" % {'id': d.identity}) Vendor Interface ================ diff --git a/driver-requirements.txt b/driver-requirements.txt index 1bd25844a1..921c2a6490 100644 --- a/driver-requirements.txt +++ b/driver-requirements.txt @@ -20,4 +20,4 @@ ansible>=2.7 python-ibmcclient>=0.2.2,<0.3.0 # Dell EMC iDRAC sushy OEM extension -sushy-oem-idrac<2.0.0 +sushy-oem-idrac>=2.0.0,<3.0.0 diff --git a/ironic/drivers/drac.py b/ironic/drivers/drac.py index 87d7e7217e..266b21bee4 100644 --- a/ironic/drivers/drac.py +++ b/ironic/drivers/drac.py @@ -74,8 +74,9 @@ class IDRACHardware(generic.GenericHardware): @property def supported_raid_interfaces(self): """List of supported raid interfaces.""" - return [raid.DracWSManRAID, raid.DracRAID] + super( - IDRACHardware, self).supported_raid_interfaces + return [raid.DracWSManRAID, raid.DracRAID, + raid.DracRedfishRAID] + super( + IDRACHardware, self).supported_raid_interfaces @property def supported_vendor_interfaces(self): diff --git a/ironic/drivers/modules/drac/raid.py b/ironic/drivers/modules/drac/raid.py index f7319a140d..2d3e639a84 100644 --- a/ironic/drivers/modules/drac/raid.py +++ b/ironic/drivers/modules/drac/raid.py @@ -23,6 +23,7 @@ from ironic_lib import metrics_utils from oslo_log import log as logging from oslo_utils import importutils from oslo_utils import units +import tenacity from ironic.common import exception from ironic.common.i18n import _ @@ -34,9 +35,12 @@ from ironic.drivers import base from ironic.drivers.modules import deploy_utils from ironic.drivers.modules.drac import common as drac_common from ironic.drivers.modules.drac import job as drac_job +from ironic.drivers.modules.redfish import raid as redfish_raid +from ironic.drivers.modules.redfish import utils as redfish_utils drac_exceptions = importutils.try_import('dracclient.exceptions') drac_constants = importutils.try_import('dracclient.constants') +sushy = importutils.try_import('sushy') LOG = logging.getLogger(__name__) @@ -1160,6 +1164,166 @@ def _get_disk_free_size_mb(disk, pending_delete): return disk.size_mb if pending_delete else disk.free_size_mb +def _wait_till_realtime_ready(task): + """Waits till real time operations are ready to be executed. + + Useful for RAID operations where almost all controllers support + real time configuration, but controllers might not be ready for + it by the time IPA starts executing steps. It can take minute or + bit more to be ready for real time configuration. + + :param task: TaskManager object containing the node. + :raises RedfishError: If can't find OEM extension or it fails to + execute + """ + try: + _retry_till_realtime_ready(task) + except tenacity.RetryError: + LOG.debug('Retries exceeded while waiting for real-time ready ' + 'for node %(node)s. Will proceed with out real-time ' + 'ready state', {'node': task.node.uuid}) + + +@tenacity.retry( + stop=(tenacity.stop_after_attempt(30)), + wait=tenacity.wait_fixed(10), + retry=tenacity.retry_if_result(lambda result: not result)) +def _retry_till_realtime_ready(task): + """Retries till real time operations are ready to be executed. + + :param task: TaskManager object containing the node. + :raises RedfishError: If can't find OEM extension or it fails to + execute + :raises RetryError: If retries exceeded and still not ready for real-time + """ + return _is_realtime_ready(task) + + +def _is_realtime_ready(task): + """Gets is real time ready status + + Uses sushy-oem-idrac extension. + + :param task: TaskManager object containing the node. + :returns: True, if real time operations are ready, otherwise False. + :raises RedfishError: If can't find OEM extension or it fails to + execute + """ + system = redfish_utils.get_system(task.node) + for manager in system.managers: + try: + manager_oem = manager.get_oem_extension('Dell') + except sushy.exceptions.OEMExtensionNotFoundError as e: + error_msg = (_("Search for Sushy OEM extension Python package " + "'sushy-oem-idrac' failed for node %(node)s. " + "Ensure it is installed. Error: %(error)s") % + {'node': task.node.uuid, 'error': e}) + LOG.error(error_msg) + raise exception.RedfishError(error=error_msg) + + try: + return manager_oem.lifecycle_service.is_realtime_ready() + except sushy.exceptions.SushyError as e: + LOG.debug("Failed to get real time ready status with system " + "%(system)s manager %(manager)s for node %(node)s. Will " + "try next manager, if available. Error: %(error)s", + {'system': system.uuid if system.uuid else + system.identity, + 'manager': manager.uuid if manager.uuid else + manager.identity, + 'node': task.node.uuid, + 'error': e}) + continue + break + + else: + error_msg = (_("iDRAC Redfish get real time ready status failed for " + "node %(node)s, because system %(system)s has no " + "manager%(no_manager)s.") % + {'node': task.node.uuid, + 'system': system.uuid if system.uuid else + system.identity, + 'no_manager': '' if not system.managers else + ' which could'}) + LOG.error(error_msg) + raise exception.RedfishError(error=error_msg) + + +class DracRedfishRAID(redfish_raid.RedfishRAID): + """iDRAC Redfish interface for RAID related actions. + + Includes iDRAC specific adjustments for RAID related actions. + """ + + @base.clean_step(priority=0, abortable=False, argsinfo={ + 'create_root_volume': { + 'description': ( + 'This specifies whether to create the root volume. ' + 'Defaults to `True`.' + ), + 'required': False + }, + 'create_nonroot_volumes': { + 'description': ( + 'This specifies whether to create the non-root volumes. ' + 'Defaults to `True`.' + ), + 'required': False + }, + 'delete_existing': { + 'description': ( + 'Setting this to `True` indicates to delete existing RAID ' + 'configuration prior to creating the new configuration. ' + 'Default value is `False`.' + ), + 'required': False, + } + }) + def create_configuration(self, task, create_root_volume=True, + create_nonroot_volumes=True, + delete_existing=False): + """Create RAID configuration on the node. + + This method creates the RAID configuration as read from + node.target_raid_config. This method + by default will create all logical disks. + + :param task: TaskManager object containing the node. + :param create_root_volume: Setting this to False indicates + not to create root volume that is specified in the node's + target_raid_config. Default value is True. + :param create_nonroot_volumes: Setting this to False indicates + not to create non-root volumes (all except the root volume) in + the node's target_raid_config. Default value is True. + :param delete_existing: Setting this to True indicates to delete RAID + configuration prior to creating the new configuration. Default is + False. + :returns: states.CLEANWAIT if RAID configuration is in progress + asynchronously or None if it is complete. + :raises: RedfishError if there is an error creating the configuration + """ + _wait_till_realtime_ready(task) + return super(DracRedfishRAID, self).create_configuration( + task, create_root_volume, create_nonroot_volumes, + delete_existing) + + @base.clean_step(priority=0) + @base.deploy_step(priority=0) + def delete_configuration(self, task): + """Delete RAID configuration on the node. + + :param task: TaskManager object containing the node. + :returns: states.CLEANWAIT (cleaning) or states.DEPLOYWAIT (deployment) + if deletion is in progress asynchronously or None if it is + complete. + """ + _wait_till_realtime_ready(task) + return super(DracRedfishRAID, self).delete_configuration(task) + + def _validate_vendor(self, task): + pass # for now assume idrac-redfish is used with iDRAC BMC, thus pass + + class DracWSManRAID(base.RAIDInterface): def get_properties(self): diff --git a/ironic/drivers/modules/redfish/raid.py b/ironic/drivers/modules/redfish/raid.py index 7e14735e2b..f9b84bb860 100644 --- a/ironic/drivers/modules/redfish/raid.py +++ b/ironic/drivers/modules/redfish/raid.py @@ -687,6 +687,32 @@ class RedfishRAID(base.RAIDInterface): """ return redfish_utils.COMMON_PROPERTIES.copy() + def _validate_vendor(self, task): + vendor = task.node.properties.get('vendor') + if not vendor: + return + + if 'dell' in vendor.lower().split(): + raise exception.InvalidParameterValue( + _("The %(iface)s raid interface is not suitable for node " + "%(node)s with vendor %(vendor)s, use idrac-redfish instead") + % {'iface': task.node.raid_interface, + 'node': task.node.uuid, 'vendor': vendor}) + + def validate(self, task): + """Validates the RAID Interface. + + This method validates the properties defined by Ironic for RAID + configuration. Driver implementations of this interface can override + this method for doing more validations (such as BMC's credentials). + + :param task: A TaskManager instance. + :raises: InvalidParameterValue, if the RAID configuration is invalid. + :raises: MissingParameterValue, if some parameters are missing. + """ + self._validate_vendor(task) + super(RedfishRAID, self).validate(task) + @base.deploy_step(priority=0, argsinfo=base.RAID_APPLY_CONFIGURATION_ARGSINFO) def apply_configuration(self, task, raid_config, create_root_volume=True, diff --git a/ironic/tests/unit/db/utils.py b/ironic/tests/unit/db/utils.py index 4368490549..bf025d6afb 100644 --- a/ironic/tests/unit/db/utils.py +++ b/ironic/tests/unit/db/utils.py @@ -92,6 +92,10 @@ def get_test_drac_info(): "drac_protocol": "https", "drac_username": "admin", "drac_password": "fake", + "redfish_address": "1.2.3.4", + "redfish_system_id": "/redfish/v1/Systems/System.Embedded.1", + "redfish_username": "admin", + "redfish_password": "fake" } diff --git a/ironic/tests/unit/drivers/modules/drac/test_raid.py b/ironic/tests/unit/drivers/modules/drac/test_raid.py index 48f38a09b5..3dd48f1b0e 100644 --- a/ironic/tests/unit/drivers/modules/drac/test_raid.py +++ b/ironic/tests/unit/drivers/modules/drac/test_raid.py @@ -20,6 +20,8 @@ from unittest import mock from dracclient import constants from dracclient import exceptions as drac_exceptions +from oslo_utils import importutils +import tenacity from ironic.common import exception from ironic.common import states @@ -28,9 +30,13 @@ from ironic.drivers import base from ironic.drivers.modules.drac import common as drac_common from ironic.drivers.modules.drac import job as drac_job from ironic.drivers.modules.drac import raid as drac_raid +from ironic.drivers.modules.redfish import raid as redfish_raid +from ironic.drivers.modules.redfish import utils as redfish_utils from ironic.tests.unit.drivers.modules.drac import utils as test_utils from ironic.tests.unit.objects import utils as obj_utils +sushy = importutils.try_import('sushy') + INFO_DICT = test_utils.INFO_DICT @@ -2239,3 +2245,159 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest): mock_apply_configuration.assert_called_once_with( task.driver.raid, task, self.target_raid_configuration, False, True, False) + + +class DracRedfishRAIDTestCase(test_utils.BaseDracTest): + + def setUp(self): + super(DracRedfishRAIDTestCase, self).setUp() + self.node = obj_utils.create_test_node(self.context, + driver='idrac', + driver_info=INFO_DICT) + self.raid = drac_raid.DracRedfishRAID() + + @mock.patch.object(drac_raid, '_wait_till_realtime_ready', autospec=True) + @mock.patch.object(redfish_raid.RedfishRAID, 'create_configuration', + autospec=True) + def test_create_configuration(self, mock_redfish_create, mock_wait): + task = mock.Mock(node=self.node, context=self.context) + + self.raid.create_configuration(task) + + mock_wait.assert_called_once_with(task) + mock_redfish_create.assert_called_once_with( + self.raid, task, True, True, False) + + @mock.patch.object(drac_raid, '_wait_till_realtime_ready', autospec=True) + @mock.patch.object(redfish_raid.RedfishRAID, 'delete_configuration', + autospec=True) + def test_delete_configuration(self, mock_redfish_delete, mock_wait): + task = mock.Mock(node=self.node, context=self.context) + + self.raid.delete_configuration(task) + + mock_wait.assert_called_once_with(task) + mock_redfish_delete.assert_called_once_with(self.raid, task) + + @mock.patch.object(drac_raid, '_retry_till_realtime_ready', autospec=True) + def test__wait_till_realtime_ready(self, mock_ready): + task = mock.Mock(node=self.node, context=self.context) + drac_raid._wait_till_realtime_ready(task) + mock_ready.assert_called_once_with(task) + + @mock.patch.object(drac_raid, 'LOG', autospec=True) + @mock.patch.object(drac_raid, '_retry_till_realtime_ready', autospec=True) + def test__wait_till_realtime_ready_retryerror(self, mock_ready, mock_log): + task = mock.Mock(node=self.node, context=self.context) + mock_ready.side_effect = tenacity.RetryError(3) + drac_raid._wait_till_realtime_ready(task) + mock_ready.assert_called_once_with(task) + self.assertEqual(mock_log.debug.call_count, 1) + + @mock.patch.object(drac_raid, '_is_realtime_ready', autospec=True) + def test__retry_till_realtime_ready_retry_exceeded(self, mock_ready): + drac_raid._retry_till_realtime_ready.retry.sleep = mock.Mock() + drac_raid._retry_till_realtime_ready.retry.stop =\ + tenacity.stop_after_attempt(3) + task = mock.Mock(node=self.node, context=self.context) + mock_ready.return_value = False + + self.assertRaises( + tenacity.RetryError, + drac_raid._retry_till_realtime_ready, task) + + self.assertEqual(3, mock_ready.call_count) + + @mock.patch.object(drac_raid, '_is_realtime_ready', autospec=True) + def test__retry_till_realtime_ready_retry_fails(self, mock_ready): + drac_raid._retry_till_realtime_ready.retry.sleep = mock.Mock() + drac_raid._retry_till_realtime_ready.retry.stop =\ + tenacity.stop_after_attempt(3) + task = mock.Mock(node=self.node, context=self.context) + mock_ready.side_effect = [False, exception.RedfishError] + + self.assertRaises( + exception.RedfishError, + drac_raid._retry_till_realtime_ready, task) + + self.assertEqual(2, mock_ready.call_count) + + @mock.patch.object(drac_raid, '_is_realtime_ready', autospec=True) + def test__retry_till_realtime_ready(self, mock_ready): + drac_raid._retry_till_realtime_ready.retry.sleep = mock.Mock() + task = mock.Mock(node=self.node, context=self.context) + mock_ready.side_effect = [False, True] + + is_ready = drac_raid._retry_till_realtime_ready(task) + + self.assertTrue(is_ready) + self.assertEqual(2, mock_ready.call_count) + + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + def test__is_realtime_ready_no_managers(self, mock_get_system): + task = mock.Mock(node=self.node, context=self.context) + fake_system = mock.Mock(managers=[]) + mock_get_system.return_value = fake_system + self.assertRaises(exception.RedfishError, + drac_raid._is_realtime_ready, task) + + @mock.patch.object(drac_raid, 'LOG', autospec=True) + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + def test__is_realtime_ready_oem_not_found(self, mock_get_system, mock_log): + task = mock.Mock(node=self.node, context=self.context) + fake_manager1 = mock.Mock() + fake_manager1.get_oem_extension.side_effect = ( + sushy.exceptions.OEMExtensionNotFoundError) + fake_system = mock.Mock(managers=[fake_manager1]) + mock_get_system.return_value = fake_system + self.assertRaises(exception.RedfishError, + drac_raid._is_realtime_ready, task) + self.assertEqual(mock_log.error.call_count, 1) + + @mock.patch.object(drac_raid, 'LOG', autospec=True) + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + def test__is_realtime_ready_all_managers_fail(self, mock_get_system, + mock_log): + task = mock.Mock(node=self.node, context=self.context) + fake_manager_oem1 = mock.Mock() + fake_manager_oem1.lifecycle_service.is_realtime_ready.side_effect = ( + sushy.exceptions.SushyError) + fake_manager1 = mock.Mock() + fake_manager1.get_oem_extension.return_value = fake_manager_oem1 + fake_manager_oem2 = mock.Mock() + fake_manager_oem2.lifecycle_service.is_realtime_ready.side_effect = ( + sushy.exceptions.SushyError) + fake_manager2 = mock.Mock() + fake_manager2.get_oem_extension.return_value = fake_manager_oem2 + fake_system = mock.Mock(managers=[fake_manager1, fake_manager2]) + mock_get_system.return_value = fake_system + self.assertRaises(exception.RedfishError, + drac_raid._is_realtime_ready, task) + self.assertEqual(mock_log.debug.call_count, 2) + + @mock.patch.object(drac_raid, 'LOG', autospec=True) + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + def test__is_realtime_ready(self, mock_get_system, mock_log): + task = mock.Mock(node=self.node, context=self.context) + fake_manager_oem1 = mock.Mock() + fake_manager_oem1.lifecycle_service.is_realtime_ready.side_effect = ( + sushy.exceptions.SushyError) + fake_manager1 = mock.Mock() + fake_manager1.get_oem_extension.return_value = fake_manager_oem1 + fake_manager_oem2 = mock.Mock() + fake_manager_oem2.lifecycle_service.is_realtime_ready.return_value = ( + True) + fake_manager2 = mock.Mock() + fake_manager2.get_oem_extension.return_value = fake_manager_oem2 + fake_system = mock.Mock(managers=[fake_manager1, fake_manager2]) + mock_get_system.return_value = fake_system + + is_ready = drac_raid._is_realtime_ready(task) + + self.assertTrue(is_ready) + self.assertEqual(mock_log.debug.call_count, 1) + + def test_validate_correct_vendor(self): + task = mock.Mock(node=self.node, context=self.context) + self.node.properties['vendor'] = 'Dell Inc.' + self.raid.validate(task) diff --git a/ironic/tests/unit/drivers/modules/redfish/test_raid.py b/ironic/tests/unit/drivers/modules/redfish/test_raid.py index 3e46c388d9..28e57acdf5 100644 --- a/ironic/tests/unit/drivers/modules/redfish/test_raid.py +++ b/ironic/tests/unit/drivers/modules/redfish/test_raid.py @@ -844,3 +844,19 @@ class RedfishRAIDTestCase(db_base.DbTestCase): mock_error_handler.assert_called_once_with( task, sushy_error, volume_collection, expected_payload ) + + def test_validate(self, mock_get_system): + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + task.node.properties['vendor'] = "Supported vendor" + + task.driver.raid.validate(task) + + def test_validate_unsupported_vendor(self, mock_get_system): + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + task.node.properties['vendor'] = "Dell Inc." + + self.assertRaisesRegex(exception.InvalidParameterValue, + "with vendor Dell.Inc.", + task.driver.raid.validate, task) diff --git a/ironic/tests/unit/drivers/test_drac.py b/ironic/tests/unit/drivers/test_drac.py index 554c04eaec..6af1c2de67 100644 --- a/ironic/tests/unit/drivers/test_drac.py +++ b/ironic/tests/unit/drivers/test_drac.py @@ -43,7 +43,8 @@ class IDRACHardwareTestCase(db_base.DbTestCase): 'no-inspect'], enabled_network_interfaces=['flat', 'neutron', 'noop'], enabled_raid_interfaces=[ - 'idrac', 'idrac-wsman', 'no-raid', 'agent'], + 'idrac', 'idrac-wsman', 'idrac-redfish', 'no-raid', + 'agent'], enabled_vendor_interfaces=[ 'idrac', 'idrac-wsman', 'no-vendor'], enabled_bios_interfaces=[ @@ -113,6 +114,15 @@ class IDRACHardwareTestCase(db_base.DbTestCase): with task_manager.acquire(self.context, node.id) as task: self._validate_interfaces(task.driver, raid=impl) + def test_override_with_redfish_raid(self): + node = obj_utils.create_test_node(self.context, + uuid=uuidutils.generate_uuid(), + driver='idrac', + raid_interface='idrac-redfish') + with task_manager.acquire(self.context, node.id) as task: + self._validate_interfaces(task.driver, + raid=drac.raid.DracRedfishRAID) + def test_override_no_vendor(self): node = obj_utils.create_test_node(self.context, driver='idrac', vendor_interface='no-vendor') diff --git a/releasenotes/notes/idrac-add-redfish-raid-support-414aad5e633a160f.yaml b/releasenotes/notes/idrac-add-redfish-raid-support-414aad5e633a160f.yaml new file mode 100644 index 0000000000..141150f738 --- /dev/null +++ b/releasenotes/notes/idrac-add-redfish-raid-support-414aad5e633a160f.yaml @@ -0,0 +1,18 @@ +--- +features: + - | + Adds basic support for managing RAID configuration via the Redfish + out-of-band (OOB) management protocol to the ``idrac`` hardware type by + adding new interface named ``idrac-redfish``. + + iDRAC firmware greater than 4.40.00.00 is required. Compared to + ``idrac-wsman`` implementation does not yet support foreign disks and + converting from non-RAID mode. + + Backing physical disk hints are not widely tested with ``idrac-redfish`` + yet and they might not work as desired. Backing physical disks + (``controller``, ``physical_disks``) with ``size_gb="MAX"`` are tested. + + The ``idrac`` hardware type now supports ``idrac-wsman``, ``idrac``, + ``idrac-redfish``, and ``no-raid`` interfaces in given priority + order. diff --git a/setup.cfg b/setup.cfg index 827fc565ad..4e581d8331 100644 --- a/setup.cfg +++ b/setup.cfg @@ -141,6 +141,7 @@ ironic.hardware.interfaces.raid = fake = ironic.drivers.modules.fake:FakeRAID ibmc = ironic.drivers.modules.ibmc.raid:IbmcRAID idrac = ironic.drivers.modules.drac.raid:DracRAID + idrac-redfish = ironic.drivers.modules.drac.raid:DracRedfishRAID idrac-wsman = ironic.drivers.modules.drac.raid:DracWSManRAID ilo5 = ironic.drivers.modules.ilo.raid:Ilo5RAID irmc = ironic.drivers.modules.irmc.raid:IRMCRAID