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
This commit is contained in:
parent
a06e403b11
commit
952695be33
|
@ -55,17 +55,13 @@ Enabling
|
||||||
|
|
||||||
The iDRAC driver supports WSMAN for the bios, inspect, management, power,
|
The iDRAC driver supports WSMAN for the bios, inspect, management, power,
|
||||||
raid, and vendor interfaces. In addition, it supports Redfish for
|
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.
|
allows you to mix and match WSMAN and Redfish interfaces.
|
||||||
|
|
||||||
The ``idrac-wsman`` implementation must be enabled to use WSMAN for
|
The ``idrac-wsman`` implementation must be enabled to use WSMAN for
|
||||||
an interface. The ``idrac-redfish`` implementation must be enabled
|
an interface. The ``idrac-redfish`` implementation must be enabled
|
||||||
to use Redfish for an interface.
|
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,
|
To enable the ``idrac`` hardware type with the minimum interfaces,
|
||||||
all using WSMAN, add the following to your ``/etc/ironic/ironic.conf``:
|
all using WSMAN, add the following to your ``/etc/ironic/ironic.conf``:
|
||||||
|
|
||||||
|
@ -88,7 +84,7 @@ following configuration:
|
||||||
enabled_inspect_interfaces=idrac-redfish
|
enabled_inspect_interfaces=idrac-redfish
|
||||||
enabled_management_interfaces=idrac-redfish
|
enabled_management_interfaces=idrac-redfish
|
||||||
enabled_power_interfaces=idrac-redfish
|
enabled_power_interfaces=idrac-redfish
|
||||||
enabled_raid_interfaces=idrac-wsman
|
enabled_raid_interfaces=idrac-redfish
|
||||||
enabled_vendor_interfaces=idrac-redfish
|
enabled_vendor_interfaces=idrac-redfish
|
||||||
|
|
||||||
Below is the list of supported interface implementations in priority
|
Below is the list of supported interface implementations in priority
|
||||||
|
@ -106,7 +102,7 @@ Interface Supported Implementations
|
||||||
``management`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``
|
``management`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``
|
||||||
``network`` ``flat``, ``neutron``, ``noop``
|
``network`` ``flat``, ``neutron``, ``noop``
|
||||||
``power`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``
|
``power`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``
|
||||||
``raid`` ``idrac-wsman``, ``idrac``, ``no-raid``
|
``raid`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``, ``no-raid``
|
||||||
``rescue`` ``no-rescue``, ``agent``
|
``rescue`` ``no-rescue``, ``agent``
|
||||||
``storage`` ``noop``, ``cinder``, ``external``
|
``storage`` ``noop``, ``cinder``, ``external``
|
||||||
``vendor`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``,
|
``vendor`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``,
|
||||||
|
@ -180,7 +176,7 @@ hardware type using Redfish for all interfaces:
|
||||||
--inspect-interface idrac-redfish \
|
--inspect-interface idrac-redfish \
|
||||||
--management-interface idrac-redfish \
|
--management-interface idrac-redfish \
|
||||||
--power-interface idrac-redfish \
|
--power-interface idrac-redfish \
|
||||||
--raid-interface no-raid \
|
--raid-interface idrac-redfish \
|
||||||
--vendor-interface idrac-redfish
|
--vendor-interface idrac-redfish
|
||||||
|
|
||||||
The following command enrolls a bare metal node with the ``idrac``
|
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.
|
See :doc:`/admin/raid` for more information on Ironic RAID support.
|
||||||
|
|
||||||
The following properties are supported by the iDRAC WSMAN raid interface
|
The following properties are supported by the iDRAC WSMAN and Redfish RAID
|
||||||
implementation, ``idrac-wsman``:
|
interface implementation:
|
||||||
|
|
||||||
|
.. NOTE::
|
||||||
|
When using ``idrac-redfish`` for RAID interface iDRAC firmware greater than
|
||||||
|
4.40.00.00 is required.
|
||||||
|
|
||||||
Mandatory properties
|
Mandatory properties
|
||||||
--------------------
|
--------------------
|
||||||
|
@ -310,6 +309,11 @@ Optional properties
|
||||||
Backing physical disk hints
|
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.
|
See :doc:`/admin/raid` for more information on backing disk hints.
|
||||||
|
|
||||||
These are machine-independent information. The hints are specified for each
|
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()
|
physical_disks = client.list_physical_disks()
|
||||||
print(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
|
Vendor Interface
|
||||||
================
|
================
|
||||||
|
|
|
@ -20,4 +20,4 @@ ansible>=2.7
|
||||||
python-ibmcclient>=0.2.2,<0.3.0
|
python-ibmcclient>=0.2.2,<0.3.0
|
||||||
|
|
||||||
# Dell EMC iDRAC sushy OEM extension
|
# Dell EMC iDRAC sushy OEM extension
|
||||||
sushy-oem-idrac<2.0.0
|
sushy-oem-idrac>=2.0.0,<3.0.0
|
||||||
|
|
|
@ -74,8 +74,9 @@ class IDRACHardware(generic.GenericHardware):
|
||||||
@property
|
@property
|
||||||
def supported_raid_interfaces(self):
|
def supported_raid_interfaces(self):
|
||||||
"""List of supported raid interfaces."""
|
"""List of supported raid interfaces."""
|
||||||
return [raid.DracWSManRAID, raid.DracRAID] + super(
|
return [raid.DracWSManRAID, raid.DracRAID,
|
||||||
IDRACHardware, self).supported_raid_interfaces
|
raid.DracRedfishRAID] + super(
|
||||||
|
IDRACHardware, self).supported_raid_interfaces
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_vendor_interfaces(self):
|
def supported_vendor_interfaces(self):
|
||||||
|
|
|
@ -23,6 +23,7 @@ from ironic_lib import metrics_utils
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import importutils
|
from oslo_utils import importutils
|
||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
|
import tenacity
|
||||||
|
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
from ironic.common.i18n import _
|
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 import deploy_utils
|
||||||
from ironic.drivers.modules.drac import common as drac_common
|
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 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_exceptions = importutils.try_import('dracclient.exceptions')
|
||||||
drac_constants = importutils.try_import('dracclient.constants')
|
drac_constants = importutils.try_import('dracclient.constants')
|
||||||
|
sushy = importutils.try_import('sushy')
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
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
|
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):
|
class DracWSManRAID(base.RAIDInterface):
|
||||||
|
|
||||||
def get_properties(self):
|
def get_properties(self):
|
||||||
|
|
|
@ -687,6 +687,32 @@ class RedfishRAID(base.RAIDInterface):
|
||||||
"""
|
"""
|
||||||
return redfish_utils.COMMON_PROPERTIES.copy()
|
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,
|
@base.deploy_step(priority=0,
|
||||||
argsinfo=base.RAID_APPLY_CONFIGURATION_ARGSINFO)
|
argsinfo=base.RAID_APPLY_CONFIGURATION_ARGSINFO)
|
||||||
def apply_configuration(self, task, raid_config, create_root_volume=True,
|
def apply_configuration(self, task, raid_config, create_root_volume=True,
|
||||||
|
|
|
@ -92,6 +92,10 @@ def get_test_drac_info():
|
||||||
"drac_protocol": "https",
|
"drac_protocol": "https",
|
||||||
"drac_username": "admin",
|
"drac_username": "admin",
|
||||||
"drac_password": "fake",
|
"drac_password": "fake",
|
||||||
|
"redfish_address": "1.2.3.4",
|
||||||
|
"redfish_system_id": "/redfish/v1/Systems/System.Embedded.1",
|
||||||
|
"redfish_username": "admin",
|
||||||
|
"redfish_password": "fake"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ from unittest import mock
|
||||||
|
|
||||||
from dracclient import constants
|
from dracclient import constants
|
||||||
from dracclient import exceptions as drac_exceptions
|
from dracclient import exceptions as drac_exceptions
|
||||||
|
from oslo_utils import importutils
|
||||||
|
import tenacity
|
||||||
|
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
from ironic.common import states
|
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 common as drac_common
|
||||||
from ironic.drivers.modules.drac import job as drac_job
|
from ironic.drivers.modules.drac import job as drac_job
|
||||||
from ironic.drivers.modules.drac import raid as drac_raid
|
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.drivers.modules.drac import utils as test_utils
|
||||||
from ironic.tests.unit.objects import utils as obj_utils
|
from ironic.tests.unit.objects import utils as obj_utils
|
||||||
|
|
||||||
|
sushy = importutils.try_import('sushy')
|
||||||
|
|
||||||
INFO_DICT = test_utils.INFO_DICT
|
INFO_DICT = test_utils.INFO_DICT
|
||||||
|
|
||||||
|
|
||||||
|
@ -2239,3 +2245,159 @@ class DracRaidInterfaceTestCase(test_utils.BaseDracTest):
|
||||||
mock_apply_configuration.assert_called_once_with(
|
mock_apply_configuration.assert_called_once_with(
|
||||||
task.driver.raid, task,
|
task.driver.raid, task,
|
||||||
self.target_raid_configuration, False, True, False)
|
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)
|
||||||
|
|
|
@ -844,3 +844,19 @@ class RedfishRAIDTestCase(db_base.DbTestCase):
|
||||||
mock_error_handler.assert_called_once_with(
|
mock_error_handler.assert_called_once_with(
|
||||||
task, sushy_error, volume_collection, expected_payload
|
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)
|
||||||
|
|
|
@ -43,7 +43,8 @@ class IDRACHardwareTestCase(db_base.DbTestCase):
|
||||||
'no-inspect'],
|
'no-inspect'],
|
||||||
enabled_network_interfaces=['flat', 'neutron', 'noop'],
|
enabled_network_interfaces=['flat', 'neutron', 'noop'],
|
||||||
enabled_raid_interfaces=[
|
enabled_raid_interfaces=[
|
||||||
'idrac', 'idrac-wsman', 'no-raid', 'agent'],
|
'idrac', 'idrac-wsman', 'idrac-redfish', 'no-raid',
|
||||||
|
'agent'],
|
||||||
enabled_vendor_interfaces=[
|
enabled_vendor_interfaces=[
|
||||||
'idrac', 'idrac-wsman', 'no-vendor'],
|
'idrac', 'idrac-wsman', 'no-vendor'],
|
||||||
enabled_bios_interfaces=[
|
enabled_bios_interfaces=[
|
||||||
|
@ -113,6 +114,15 @@ class IDRACHardwareTestCase(db_base.DbTestCase):
|
||||||
with task_manager.acquire(self.context, node.id) as task:
|
with task_manager.acquire(self.context, node.id) as task:
|
||||||
self._validate_interfaces(task.driver, raid=impl)
|
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):
|
def test_override_no_vendor(self):
|
||||||
node = obj_utils.create_test_node(self.context, driver='idrac',
|
node = obj_utils.create_test_node(self.context, driver='idrac',
|
||||||
vendor_interface='no-vendor')
|
vendor_interface='no-vendor')
|
||||||
|
|
|
@ -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.
|
|
@ -141,6 +141,7 @@ ironic.hardware.interfaces.raid =
|
||||||
fake = ironic.drivers.modules.fake:FakeRAID
|
fake = ironic.drivers.modules.fake:FakeRAID
|
||||||
ibmc = ironic.drivers.modules.ibmc.raid:IbmcRAID
|
ibmc = ironic.drivers.modules.ibmc.raid:IbmcRAID
|
||||||
idrac = ironic.drivers.modules.drac.raid:DracRAID
|
idrac = ironic.drivers.modules.drac.raid:DracRAID
|
||||||
|
idrac-redfish = ironic.drivers.modules.drac.raid:DracRedfishRAID
|
||||||
idrac-wsman = ironic.drivers.modules.drac.raid:DracWSManRAID
|
idrac-wsman = ironic.drivers.modules.drac.raid:DracWSManRAID
|
||||||
ilo5 = ironic.drivers.modules.ilo.raid:Ilo5RAID
|
ilo5 = ironic.drivers.modules.ilo.raid:Ilo5RAID
|
||||||
irmc = ironic.drivers.modules.irmc.raid:IRMCRAID
|
irmc = ironic.drivers.modules.irmc.raid:IRMCRAID
|
||||||
|
|
Loading…
Reference in New Issue