summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2020-06-11 16:35:03 +0000
committerGerrit Code Review <review@openstack.org>2020-06-11 16:35:03 +0000
commit50e06f45826f8d9c51777e2035aebc0c11f95f2a (patch)
treeabeb57bb4d0d1784d26e259169cbfa7baa6f80f0
parentab238e7e476eed21cac0823eb081f96ad65d646b (diff)
parentcd6e5ec497f489bfbd313256d7d7841e6ca9fe67 (diff)
downloadironic-50e06f45826f8d9c51777e2035aebc0c11f95f2a.tar.gz
Merge "Feature: Add raid configuration support for ibmc driver"
-rw-r--r--doc/source/admin/drivers/ibmc.rst226
-rw-r--r--ironic/drivers/ibmc.py12
-rw-r--r--ironic/drivers/modules/ibmc/raid.py199
-rw-r--r--ironic/drivers/modules/ibmc/vendor.py21
-rw-r--r--ironic/tests/unit/drivers/modules/ibmc/base.py3
-rw-r--r--ironic/tests/unit/drivers/modules/ibmc/test_raid.py166
-rw-r--r--ironic/tests/unit/drivers/modules/ibmc/test_vendor.py20
-rw-r--r--ironic/tests/unit/drivers/test_ibmc.py11
-rw-r--r--releasenotes/notes/add-ibmc-raid-interface-0c13826e134fb4ce.yaml5
-rw-r--r--setup.cfg1
10 files changed, 647 insertions, 17 deletions
diff --git a/doc/source/admin/drivers/ibmc.rst b/doc/source/admin/drivers/ibmc.rst
index 6ed01f3f7..76dcc4631 100644
--- a/doc/source/admin/drivers/ibmc.rst
+++ b/doc/source/admin/drivers/ibmc.rst
@@ -9,6 +9,13 @@ The ``ibmc`` driver is targeted for Huawei V5 series rack server such as
2288H V5, CH121 V5. The iBMC hardware type enables the user to take advantage
of features of `Huawei iBMC`_ to control Huawei server.
+The ``ibmc`` hardware type supports the following Ironic interfaces:
+
+* Management Interface: Boot device management
+* Power Interface: Power management
+* `RAID Interface`_: RAID controller and disk management
+* `Vendor Interface`_: BIOS management
+
Prerequisites
=============
@@ -28,9 +35,10 @@ Enabling the iBMC driver
[DEFAULT]
...
- enabled_hardware_types = ibmc,ipmi
- enabled_power_interfaces = ibmc,ipmitool
- enabled_management_interfaces = ibmc,ipmitool
+ enabled_hardware_types = ibmc
+ enabled_power_interfaces = ibmc
+ enabled_management_interfaces = ibmc
+ enabled_raid_interfaces = ibmc
enabled_vendor_interfaces = ibmc
#. Restart the ironic conductor service::
@@ -91,19 +99,213 @@ a node with the ``ibmc`` driver. For example:
For more information about enrolling nodes see :ref:`enrollment`
in the install guide.
-Features of the ``ibmc`` hardware type
-=========================================
+RAID Interface
+==============
-Query boot up sequence
-^^^^^^^^^^^^^^^^^^^^^^
+Currently, only RAID controller which supports OOB management can be managed.
-The ``ibmc`` hardware type can query current boot up sequence from the
-bare metal node
+See :doc:`/admin/raid` for more information on Ironic RAID support.
+
+The following properties are supported by the iBMC raid interface
+implementation, ``ibmc``:
+
+Mandatory properties
+--------------------
+
+* ``size_gb``: Size in gigabytes (integer) for the logical disk. Use ``MAX`` as
+ ``size_gb`` if this logical disk is supposed to use the rest of the space
+ available.
+* ``raid_level``: RAID level for the logical disk. Valid values are
+ ``JBOD``, ``0``, ``1``, ``5``, ``6``, ``1+0``, ``5+0`` and ``6+0``. And it
+ is possible that some RAID controllers can only support a subset RAID
+ levels.
+
+.. NOTE::
+ RAID level ``2`` is not supported by ``iBMC`` driver.
+
+Optional properties
+-------------------
+
+* ``is_root_volume``: Optional. Specifies whether this disk is a root volume.
+ By default, this is ``False``.
+* ``volume_name``: Optional. Name of the volume to be created. If this is not
+ specified, it will be N/A.
+
+Backing physical disk hints
+---------------------------
+
+See :doc:`/admin/raid` for more information on backing disk hints.
+
+These are machine-independent properties. The hints are specified for each
+logical disk to help Ironic find the desired disks for RAID configuration.
+
+* ``share_physical_disks``
+* ``disk_type``
+* ``interface_type``
+* ``number_of_physical_disks``
+
+Backing physical disks
+----------------------
+
+These are HUAWEI RAID controller dependent properties:
+
+* ``controller``: Optional. Supported values are: RAID storage id,
+ RAID storage name or RAID controller name. If a bare metal server have more
+ than one controller, this is mandatory. Typical values would look like:
+
+ * RAID Storage Id: ``RAIDStorage0``
+ * RAID Storage Name: ``RAIDStorage0``
+ * RAID Controller Name: ``RAID Card1 Controller``.
+
+* ``physical_disks``: Optional. Supported values are: disk-id, disk-name or
+ disk serial number. Typical values for hdd disk would look like:
+
+ * Disk Id: ``HDDPlaneDisk0``
+ * Disk Name: ``Disk0``.
+ * Disk SerialNumber: ``38DGK77LF77D``
+
+Delete RAID configuration
+-------------------------
+
+For ``delete_configuration`` step, ``ibmc`` will do:
+
+* delete all logical disks
+* delete all hot-spare disks
+
+Logical disks creation priority
+-------------------------------
+
+Logical Disks creation priority based on three properties:
+
+* ``share_physical_disks``
+* ``physical_disks``
+* ``size_gb``
+
+The logical disks creation priority strictly follow the table below, if
+multiple logical disks have the same priority, then they will be created with
+the same order in ``logical_disks`` array.
+
+==================== ========================== =========
+Share physical disks Specified Physical Disks Size
+==================== ========================== =========
+no yes int|max
+no no int
+yes yes int
+yes yes max
+yes no int
+yes no max
+no no max
+==================== ========================== =========
+
+Physical disks choice strategy
+------------------------------
+
+* If no ``physical_disks`` are specified, the "waste least" strategy will be
+ used to choose the physical disks.
+
+ * waste least disk capacity: when using disks with different capacity, it
+ will cause a waste of disk capacity. This is to avoid with highest
+ priority.
+ * using least total disk capacity: for example, we can create 400G RAID 5
+ with both 5 100G-disks and 3 200G-disks. 5 100G disks is a better
+ strategy because it uses a 500G capacity totally. While 3 200G-disks
+ are 600G totally.
+ * using least disk count: finally, if waste capacity and total disk
+ capacity are both the same (it rarely happens?), we will choose the one
+ with the minimum number of disks.
+
+* when ``share_physical_disks`` option is present, ``ibmc`` driver will
+ create logical disk upon existing physical-disk-groups(logical-disks) first.
+ Only when no exists physical-disk-group matches, then it chooses unused
+ physical disks with same strategy described upon. When multiple exists
+ physical-disk-groups matches, it will use "waste least" strategy too,
+ the bigger capacity left the better. For example, to create a logical disk
+ shown below on a ``ibmc`` server which has two RAID5 logical disks already.
+ And the shareable capacity of this two logical-disks are 500G and 300G,
+ then ``ibmc`` driver will choose the second one.
+
+ .. code-block:: json
+
+ {
+ "logical_disks": [
+ {
+ "controller": "RAID Card1 Controller",
+ "raid_level": "5",
+ "size_gb": 100,
+ "share_physical_disks": true
+ }
+ ]
+ }
+
+ And the ``ibmc`` server has two RAID5 logical disks already.
+
+* When ``size_gb`` is set to ``MAX``, ``ibmc`` driver will auto work through
+ all possible cases and choose the "best" solution which has the biggest
+ capacity and use least capacity. For example: to create a RAID 5+0 logical
+ disk with MAX size in a server has 9 200G-disks, it will finally choose
+ "8 disks + span-number 2" but not "9 disks + span-number 3". Although they
+ both have 1200G capacity totally, but the former uses only 8 disks and the
+ latter uses 9 disks. If you want to choose the latter solution, you can
+ specified the disk count to use by adding ``number_of_physical_disks``
+ option.
+
+ .. code-block:: json
+
+ {
+ "logical_disks": [
+ {
+ "controller": "RAID Card1 Controller",
+ "raid_level": "5+0",
+ "size_gb": "MAX"
+ }
+ ]
+ }
+
+
+Examples
+--------
+
+A typical scene creates:
+ * RAID 5, 500G, root OS volume with 3 disks
+ * RAID 5, rest available space, data volume with rest disks
+
+.. code-block:: json
+
+ {
+ "logical_disks": [
+ {
+ "volume_name": "os_volume",
+ "controller": "RAID Card1 Controller",
+ "is_root_volume": "True",
+ "physical_disks": [
+ "Disk0",
+ "Disk1",
+ "Disk2"
+ ],
+ "raid_level": "5",
+ "size_gb": "500"
+ },
+ {
+ "volume_name": "data_volume",
+ "controller": "RAID Card1 Controller",
+ "raid_level": "5",
+ "size_gb": "MAX"
+ }
+ ]
+ }
+
+Vendor Interface
+=========================================
+
+The ``ibmc`` hardware type provides vendor passthru interfaces shown below:
-.. code-block:: bash
- openstack baremetal node passthru call --http-method GET \
- <node id or node name> boot_up_seq
+======================== ============ ======================================
+Method Name HTTP Method Description
+======================== ============ ======================================
+boot_up_seq GET Query boot up sequence
+get_raid_controller_list GET Query RAID controller summary info
+======================== ============ ======================================
PXE Boot and iSCSI Deploy Process with Ironic Standalone Environment
diff --git a/ironic/drivers/ibmc.py b/ironic/drivers/ibmc.py
index 0f9ae5a5b..cc8b519ed 100644
--- a/ironic/drivers/ibmc.py
+++ b/ironic/drivers/ibmc.py
@@ -18,7 +18,9 @@ CH121 V5.
from ironic.drivers import generic
from ironic.drivers.modules.ibmc import management as ibmc_mgmt
from ironic.drivers.modules.ibmc import power as ibmc_power
+from ironic.drivers.modules.ibmc import raid as ibmc_raid
from ironic.drivers.modules.ibmc import vendor as ibmc_vendor
+from ironic.drivers.modules import inspector
from ironic.drivers.modules import noop
@@ -39,3 +41,13 @@ class IBMCHardware(generic.GenericHardware):
def supported_vendor_interfaces(self):
"""List of supported vendor interfaces."""
return [ibmc_vendor.IBMCVendor, noop.NoVendor]
+
+ @property
+ def supported_raid_interfaces(self):
+ """List of supported raid interfaces."""
+ return [ibmc_raid.IbmcRAID, noop.NoRAID]
+
+ @property
+ def supported_inspect_interfaces(self):
+ """List of supported inspect interfaces."""
+ return [inspector.Inspector, noop.NoInspect]
diff --git a/ironic/drivers/modules/ibmc/raid.py b/ironic/drivers/modules/ibmc/raid.py
new file mode 100644
index 000000000..886329ab9
--- /dev/null
+++ b/ironic/drivers/modules/ibmc/raid.py
@@ -0,0 +1,199 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+iBMC RAID configuration specific methods
+"""
+
+from ironic_lib import metrics_utils
+from oslo_log import log as logging
+from oslo_utils import importutils
+
+from ironic.common.i18n import _
+from ironic.common import raid
+from ironic import conf
+from ironic.drivers import base
+from ironic.drivers.modules.ibmc import utils
+
+constants = importutils.try_import('ibmc_client.constants')
+ibmc_client = importutils.try_import('ibmc_client')
+ibmc_error = importutils.try_import('ibmc_client.exceptions')
+
+CONF = conf.CONF
+LOG = logging.getLogger(__name__)
+METRICS = metrics_utils.get_metrics_logger(__name__)
+
+
+class IbmcRAID(base.RAIDInterface):
+ """Implementation of RAIDInterface for iBMC."""
+
+ RAID_APPLY_CONFIGURATION_ARGSINFO = {
+ "raid_config": {
+ "description": "The RAID configuration to apply.",
+ "required": True,
+ },
+ "create_root_volume": {
+ "description": (
+ "Setting this to 'False' indicates not to create root "
+ "volume that is specified in 'raid_config'. Default "
+ "value is 'True'."
+ ),
+ "required": False,
+ },
+ "create_nonroot_volumes": {
+ "description": (
+ "Setting this to 'False' indicates not to create "
+ "non-root volumes (all except the root volume) in "
+ "'raid_config'. Default value is '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 'True'."
+ ),
+ "required": False,
+ }
+ }
+
+ def get_properties(self):
+ """Return the properties of the interface.
+
+ :returns: dictionary of <property name>:<property description> entries.
+ """
+ return utils.COMMON_PROPERTIES.copy()
+
+ @utils.handle_ibmc_exception('delete iBMC RAID configuration')
+ def _delete_raid_configuration(self, task):
+ """Delete the RAID configuration through `python-ibmcclient` lib.
+
+ :param task: a TaskManager instance containing the node to act on.
+ """
+ ibmc = utils.parse_driver_info(task.node)
+ with ibmc_client.connect(**ibmc) as conn:
+ # NOTE(qianbiao.ng): To reduce review workload, we should keep all
+ # delete logic in python-ibmcclient. And delete raid configuration
+ # logic should be synchronized. if async required, do it in
+ # python-ibmcclient.
+ conn.system.storage.delete_all_raid_configuration()
+
+ @utils.handle_ibmc_exception('create iBMC RAID configuration')
+ def _create_raid_configuration(self, task, logical_disks):
+ """Create the RAID configuration through `python-ibmcclient` lib.
+
+ :param task: a TaskManager instance containing the node to act on.
+ :param logical_disks: a list of JSON dictionaries which represents
+ the logical disks to be created. The JSON dictionary should match
+ the (ironic.drivers.raid_config_schema.json) scheme.
+ """
+ ibmc = utils.parse_driver_info(task.node)
+ with ibmc_client.connect(**ibmc) as conn:
+ # NOTE(qianbiao.ng): To reduce review workload, we should keep all
+ # apply logic in python-ibmcclient. And apply raid configuration
+ # logic should be synchronized. if async required, do it in
+ # python-ibmcclient.
+ conn.system.storage.apply_raid_configuration(logical_disks)
+
+ @base.deploy_step(priority=0,
+ argsinfo=RAID_APPLY_CONFIGURATION_ARGSINFO)
+ def apply_configuration(self, task, raid_config, create_root_volume=True,
+ create_nonroot_volumes=False):
+ return super(IbmcRAID, self).apply_configuration(
+ task, raid_config, create_root_volume=create_root_volume,
+ create_nonroot_volumes=create_nonroot_volumes)
+
+ @METRICS.timer('IbmcRAID.create_configuration')
+ @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 a RAID configuration.
+
+ This method creates a RAID configuration on the given node.
+
+ :param task: a TaskManager instance.
+ :param create_root_volume: If True, a root volume is created
+ during RAID configuration. Otherwise, no root volume is
+ created. Default is True.
+ :param create_nonroot_volumes: If True, non-root volumes are
+ created. If False, no non-root volumes are created. Default
+ is True.
+ :param delete_existing: Setting this to True indicates to delete RAID
+ configuration prior to creating the new configuration. Default is
+ False.
+ :raises: MissingParameterValue, if node.target_raid_config is missing
+ or empty after skipping root volume and/or non-root volumes.
+ :raises: IBMCError, on failure to execute step.
+ """
+ node = task.node
+ raid_config = raid.filter_target_raid_config(
+ node, create_root_volume=create_root_volume,
+ create_nonroot_volumes=create_nonroot_volumes)
+ LOG.info(_("Invoke RAID create_configuration step for node %s(uuid). "
+ "Current provision state is: %(status)s. "
+ "Target RAID configuration is: %(config)s."),
+ {'uuid': node.uuid, 'status': node.provision_state,
+ 'target': raid_config})
+
+ # cache current raid config to node's driver_internal_info
+ node.driver_internal_info['raid_config'] = raid_config
+ node.save()
+
+ # delete exist volumes if necessary
+ if delete_existing:
+ self._delete_raid_configuration(task)
+
+ # create raid configuration
+ logical_disks = raid_config.get('logical_disks', [])
+ self._create_raid_configuration(task, logical_disks)
+ LOG.info(_("Succeed to create raid configuration on node %s."),
+ task.node.uuid)
+
+ @METRICS.timer('IbmcRAID.delete_configuration')
+ @base.clean_step(priority=0, abortable=False)
+ @base.deploy_step(priority=0)
+ def delete_configuration(self, task):
+ """Delete the RAID configuration.
+
+ :param task: a TaskManager instance containing the node to act on.
+ :returns: states.CLEANWAIT if cleaning operation in progress
+ asynchronously or states.DEPLOYWAIT if deploy operation in
+ progress synchronously or None if it is completed.
+ :raises: IBMCError, on failure to execute step.
+ """
+ node = task.node
+ LOG.info("Invoke RAID delete_configuration step for node %s(uuid). "
+ "Current provision state is: %(status)s. ",
+ {'uuid': node.uuid, 'status': node.provision_state})
+ self._delete_raid_configuration(task)
+ LOG.info(_("Succeed to delete raid configuration on node %s."),
+ task.node.uuid)
diff --git a/ironic/drivers/modules/ibmc/vendor.py b/ironic/drivers/modules/ibmc/vendor.py
index 24d497cf7..00344cd3b 100644
--- a/ironic/drivers/modules/ibmc/vendor.py
+++ b/ironic/drivers/modules/ibmc/vendor.py
@@ -85,3 +85,24 @@ class IBMCVendor(base.VendorInterface):
system = conn.system.get()
boot_sequence = system.boot_sequence
return {'boot_up_sequence': boot_sequence}
+
+ @base.passthru(['GET'], async_call=False,
+ description=_('Returns a list of dictionary, every '
+ 'dictionary represents a RAID controller '
+ 'summary info'))
+ @utils.handle_ibmc_exception('get iBMC RAID controller summary')
+ def get_raid_controller_list(self, task, **kwargs):
+ """List RAID controllers summary info of the node.
+
+ :param task: A TaskManager instance containing the node to act on.
+ :param kwargs: Not used.
+ :raises: IBMCConnectionError when it fails to connect to iBMC
+ :raises: IBMCError when iBMC responses an error information
+ :returns: A list of dictionaries, every dictionary represents a RAID
+ controller summary of node.
+ """
+ driver_info = utils.parse_driver_info(task.node)
+ with ibmc_client.connect(**driver_info) as conn:
+ controllers = conn.system.storage.list()
+ summaries = [ctrl.summary() for ctrl in controllers]
+ return summaries
diff --git a/ironic/tests/unit/drivers/modules/ibmc/base.py b/ironic/tests/unit/drivers/modules/ibmc/base.py
index 158f510bb..9a282b1cb 100644
--- a/ironic/tests/unit/drivers/modules/ibmc/base.py
+++ b/ironic/tests/unit/drivers/modules/ibmc/base.py
@@ -28,7 +28,8 @@ class IBMCTestCase(db_base.DbTestCase):
self.config(enabled_hardware_types=['ibmc'],
enabled_power_interfaces=['ibmc'],
enabled_management_interfaces=['ibmc'],
- enabled_vendor_interfaces=['ibmc'])
+ enabled_vendor_interfaces=['ibmc'],
+ enabled_raid_interfaces=['ibmc'])
self.node = obj_utils.create_test_node(
self.context, driver='ibmc', driver_info=self.driver_info)
self.ibmc = utils.parse_driver_info(self.node)
diff --git a/ironic/tests/unit/drivers/modules/ibmc/test_raid.py b/ironic/tests/unit/drivers/modules/ibmc/test_raid.py
new file mode 100644
index 000000000..38ab6be52
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/ibmc/test_raid.py
@@ -0,0 +1,166 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Test class for iBMC RAID interface."""
+
+import mock
+from oslo_utils import importutils
+
+from ironic.common import exception
+from ironic.conductor import task_manager
+from ironic.drivers.modules.ilo import raid as ilo_raid
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.drivers.modules.ibmc import base
+
+constants = importutils.try_import('ibmc_client.constants')
+ibmc_client = importutils.try_import('ibmc_client')
+ibmc_error = importutils.try_import('ibmc_client.exceptions')
+
+INFO_DICT = db_utils.get_test_ilo_info()
+
+
+class IbmcRAIDTestCase(base.IBMCTestCase):
+
+ def setUp(self):
+ super(IbmcRAIDTestCase, self).setUp()
+ self.driver = mock.Mock(raid=ilo_raid.Ilo5RAID())
+ self.target_raid_config = {
+ "logical_disks": [
+ {
+ 'size_gb': 200,
+ 'raid_level': 0,
+ 'is_root_volume': True
+ },
+ {
+ 'size_gb': 'MAX',
+ 'raid_level': 5
+ }
+ ]
+ }
+ self.node.target_raid_config = self.target_raid_config
+ self.node.save()
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_sync_create_configuration_without_delete(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ conn.system.storage.apply_raid_configuration.return_value = None
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ result = task.driver.raid.create_configuration(
+ task, create_root_volume=True, create_nonroot_volumes=True,
+ delete_existing=False)
+ self.assertIsNone(result, "synchronous create raid configuration "
+ "should return None")
+
+ conn.system.storage.apply_raid_configuration.assert_called_once_with(
+ self.node.target_raid_config.get('logical_disks')
+ )
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_sync_create_configuration_with_delete(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ conn.system.storage.delete_all_raid_configuration.return_value = None
+ conn.system.storage.apply_raid_configuration.return_value = None
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ result = task.driver.raid.create_configuration(
+ task, create_root_volume=True, create_nonroot_volumes=True,
+ delete_existing=True)
+ self.assertIsNone(result, "synchronous create raid configuration "
+ "should return None")
+
+ conn.system.storage.delete_all_raid_configuration.assert_called_once()
+ conn.system.storage.apply_raid_configuration.assert_called_once_with(
+ self.node.target_raid_config.get('logical_disks')
+ )
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_sync_create_configuration_without_nonroot(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ conn.system.storage.delete_all_raid_configuration.return_value = None
+ conn.system.storage.apply_raid_configuration.return_value = None
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ result = task.driver.raid.create_configuration(
+ task, create_root_volume=True, create_nonroot_volumes=False,
+ delete_existing=True)
+ self.assertIsNone(result, "synchronous create raid configuration "
+ "should return None")
+
+ conn.system.storage.delete_all_raid_configuration.assert_called_once()
+ conn.system.storage.apply_raid_configuration.assert_called_once_with(
+ [{'size_gb': 200, 'raid_level': 0, 'is_root_volume': True}]
+ )
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_sync_create_configuration_without_root(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ conn.system.storage.delete_all_raid_configuration.return_value = None
+ conn.system.storage.apply_raid_configuration.return_value = None
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ result = task.driver.raid.create_configuration(
+ task, create_root_volume=False, create_nonroot_volumes=True,
+ delete_existing=True)
+ self.assertIsNone(result, "synchronous create raid configuration "
+ "should return None")
+
+ conn.system.storage.delete_all_raid_configuration.assert_called_once()
+ conn.system.storage.apply_raid_configuration.assert_called_once_with(
+ [{'size_gb': 'MAX', 'raid_level': 5}]
+ )
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_sync_create_configuration_failed(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ conn.system.storage.delete_all_raid_configuration.return_value = None
+ conn.system.storage.apply_raid_configuration.side_effect = (
+ ibmc_error.IBMCClientError
+ )
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaisesRegex(
+ exception.IBMCError, 'create iBMC RAID configuration',
+ task.driver.raid.create_configuration, task,
+ create_root_volume=True, create_nonroot_volumes=True,
+ delete_existing=True)
+
+ conn.system.storage.delete_all_raid_configuration.assert_called_once()
+ conn.system.storage.apply_raid_configuration.assert_called_once_with(
+ self.node.target_raid_config.get('logical_disks')
+ )
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_sync_delete_configuration_success(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ conn.system.storage.delete_all_raid_configuration.return_value = None
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ result = task.driver.raid.delete_configuration(task)
+ self.assertIsNone(result, "synchronous delete raid configuration "
+ "should return None")
+
+ conn.system.storage.delete_all_raid_configuration.assert_called_once()
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_sync_delete_configuration_failed(self, connect_ibmc):
+ conn = self.mock_ibmc_conn(connect_ibmc)
+ conn.system.storage.delete_all_raid_configuration.side_effect = (
+ ibmc_error.IBMCClientError
+ )
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaisesRegex(
+ exception.IBMCError, 'delete iBMC RAID configuration',
+ task.driver.raid.delete_configuration, task)
+
+ conn.system.storage.delete_all_raid_configuration.assert_called_once()
diff --git a/ironic/tests/unit/drivers/modules/ibmc/test_vendor.py b/ironic/tests/unit/drivers/modules/ibmc/test_vendor.py
index ef693e765..2714203ca 100644
--- a/ironic/tests/unit/drivers/modules/ibmc/test_vendor.py
+++ b/ironic/tests/unit/drivers/modules/ibmc/test_vendor.py
@@ -57,6 +57,24 @@ class IBMCVendorTestCase(base.IBMCTestCase):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
seq = task.driver.vendor.boot_up_seq(task)
- conn.system.get.assert_called_once()
+ conn.system.get.assert_called_once_with()
connect_ibmc.assert_called_once_with(**self.ibmc)
self.assertEqual(expected, seq)
+
+ @mock.patch.object(ibmc_client, 'connect', autospec=True)
+ def test_list_raid_controller(self, connect_ibmc):
+ # Mocks
+ conn = self.mock_ibmc_conn(connect_ibmc)
+
+ ctrl = mock.Mock()
+ summary = mock.Mock()
+ ctrl.summary.return_value = summary
+ conn.system.storage.list.return_value = [ctrl]
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ summries = task.driver.vendor.get_raid_controller_list(task)
+ ctrl.summary.assert_called_once_with()
+ conn.system.storage.list.assert_called_once_with()
+ connect_ibmc.assert_called_once_with(**self.ibmc)
+ self.assertEqual([summary], summries)
diff --git a/ironic/tests/unit/drivers/test_ibmc.py b/ironic/tests/unit/drivers/test_ibmc.py
index 731311b54..7e1a9fe30 100644
--- a/ironic/tests/unit/drivers/test_ibmc.py
+++ b/ironic/tests/unit/drivers/test_ibmc.py
@@ -16,7 +16,9 @@
from ironic.conductor import task_manager
from ironic.drivers.modules.ibmc import management as ibmc_mgmt
from ironic.drivers.modules.ibmc import power as ibmc_power
+from ironic.drivers.modules.ibmc import raid as ibmc_raid
from ironic.drivers.modules.ibmc import vendor as ibmc_vendor
+from ironic.drivers.modules import inspector
from ironic.drivers.modules import iscsi_deploy
from ironic.drivers.modules import noop
from ironic.drivers.modules import pxe
@@ -31,7 +33,9 @@ class IBMCHardwareTestCase(db_base.DbTestCase):
self.config(enabled_hardware_types=['ibmc'],
enabled_power_interfaces=['ibmc'],
enabled_management_interfaces=['ibmc'],
- enabled_vendor_interfaces=['ibmc'])
+ enabled_vendor_interfaces=['ibmc'],
+ enabled_raid_interfaces=['ibmc'],
+ enabled_inspect_interfaces=['inspector', 'no-inspect'])
def test_default_interfaces(self):
node = obj_utils.create_test_node(self.context, driver='ibmc')
@@ -41,7 +45,8 @@ class IBMCHardwareTestCase(db_base.DbTestCase):
self.assertIsInstance(task.driver.power,
ibmc_power.IBMCPower)
self.assertIsInstance(task.driver.boot, pxe.PXEBoot)
- self.assertIsInstance(task.driver.deploy, iscsi_deploy.ISCSIDeploy)
self.assertIsInstance(task.driver.console, noop.NoConsole)
- self.assertIsInstance(task.driver.raid, noop.NoRAID)
+ self.assertIsInstance(task.driver.deploy, iscsi_deploy.ISCSIDeploy)
+ self.assertIsInstance(task.driver.raid, ibmc_raid.IbmcRAID)
self.assertIsInstance(task.driver.vendor, ibmc_vendor.IBMCVendor)
+ self.assertIsInstance(task.driver.inspect, inspector.Inspector)
diff --git a/releasenotes/notes/add-ibmc-raid-interface-0c13826e134fb4ce.yaml b/releasenotes/notes/add-ibmc-raid-interface-0c13826e134fb4ce.yaml
new file mode 100644
index 000000000..2ab644aaf
--- /dev/null
+++ b/releasenotes/notes/add-ibmc-raid-interface-0c13826e134fb4ce.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Add raid interface for ibmc driver which includes ``delete_configuration``
+ and ``create_configuration`` steps.
diff --git a/setup.cfg b/setup.cfg
index 3cdd79ae2..1101c09b5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -136,6 +136,7 @@ ironic.hardware.interfaces.power =
ironic.hardware.interfaces.raid =
agent = ironic.drivers.modules.agent:AgentRAID
fake = ironic.drivers.modules.fake:FakeRAID
+ ibmc = ironic.drivers.modules.ibmc.raid:IbmcRAID
idrac = ironic.drivers.modules.drac.raid:DracRAID
idrac-wsman = ironic.drivers.modules.drac.raid:DracWSManRAID
ilo5 = ironic.drivers.modules.ilo.raid:Ilo5RAID