summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2021-03-23 01:47:56 +0000
committerGerrit Code Review <review@openstack.org>2021-03-23 01:47:56 +0000
commit961a85ada72532ba167f6b655571711c3c6df279 (patch)
treebf21742a141e3c487ff1cd4afbf885f8954a07cd
parentacd4b451dc866f4aee48b46f958cc46fd2220d09 (diff)
parent952695be334c2d68b3abc8315822e512a1caf900 (diff)
downloadironic-961a85ada72532ba167f6b655571711c3c6df279.tar.gz
Merge "Add Redfish RAID interface to idrac HW type"
-rw-r--r--doc/source/admin/drivers/idrac.rst38
-rw-r--r--driver-requirements.txt2
-rw-r--r--ironic/drivers/drac.py5
-rw-r--r--ironic/drivers/modules/drac/raid.py164
-rw-r--r--ironic/drivers/modules/redfish/raid.py26
-rw-r--r--ironic/tests/unit/db/utils.py4
-rw-r--r--ironic/tests/unit/drivers/modules/drac/test_raid.py162
-rw-r--r--ironic/tests/unit/drivers/modules/redfish/test_raid.py16
-rw-r--r--ironic/tests/unit/drivers/test_drac.py12
-rw-r--r--releasenotes/notes/idrac-add-redfish-raid-support-414aad5e633a160f.yaml18
-rw-r--r--setup.cfg1
11 files changed, 434 insertions, 14 deletions
diff --git a/doc/source/admin/drivers/idrac.rst b/doc/source/admin/drivers/idrac.rst
index d94a577ef..b5d217554 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 1bd25844a..921c2a649 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 87d7e7217..266b21bee 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 f7319a140..2d3e639a8 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 7e14735e2..f9b84bb86 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 436849054..bf025d6af 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 48f38a09b..3dd48f1b0 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 3e46c388d..28e57acdf 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 554c04eae..6af1c2de6 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 000000000..141150f73
--- /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 83d308e63..9d99f6dff 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -142,6 +142,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