From cc1632a82db116a40f19337d13bfab6aa6a2d8bf Mon Sep 17 00:00:00 2001
From: Dmitry Tantsur <dtantsur@protonmail.com>
Date: Tue, 17 Mar 2020 13:28:18 +0100
Subject: [PATCH] Allow specifying target devices for software RAID

This change adds support for the physical_disks RAID parameter in
a form of device hints (same as for root device selection).

Depends-On: https://review.opendev.org/713209
Change-Id: I9751ab0f86ada41e3b668670dc112d58093b8099
Story: #2006369
Task: #36153
---
 doc/source/admin/raid.rst                     | 26 ++++++++++++++++++-
 ironic/drivers/raid_config_schema.json        |  6 +++--
 ironic/tests/unit/common/test_raid.py         | 12 +++++++++
 ironic/tests/unit/raid_constants.py           | 26 +++++++++++++++++++
 .../notes/raid-hints-c27097ded0137f7c.yaml    |  7 +++++
 5 files changed, 74 insertions(+), 3 deletions(-)
 create mode 100644 releasenotes/notes/raid-hints-c27097ded0137f7c.yaml

diff --git a/doc/source/admin/raid.rst b/doc/source/admin/raid.rst
index 697253ffcf..68b62f00e9 100644
--- a/doc/source/admin/raid.rst
+++ b/doc/source/admin/raid.rst
@@ -143,7 +143,12 @@ are hardware dependent.
   In order to trigger the setup of a Software RAID via the Ironic Python
   Agent, the value of this property needs to be set to ``software``.
 - ``physical_disks`` - A list of physical disks to use as read by the
-  RAID interface. Not supported for software RAID.
+  RAID interface.
+
+  For software RAID ``physical_disks`` is a list of device hints in the same
+  format as used for :ref:`root-device-hints`. The number of provided hints
+  must match the expected number of backing devices (repeat the same hint if
+  necessary).
 
 .. note::
     If properties from both "Backing physical disk hints" or
@@ -260,6 +265,25 @@ HDD:
     ]
   }
 
+*Example 6*. Software RAID, limiting backing block devices to exactly two
+devices with the size exceeding 100 GiB:
+
+.. code-block:: json
+
+  {
+    "logical_disks": [
+      {
+        "size_gb": "MAX",
+        "raid_level": "0",
+        "controller": "software",
+        "physical_disks": [
+          {"size": "> 100"},
+          {"size": "> 100"}
+        ]
+      }
+    ]
+  }
+
 Current RAID configuration
 --------------------------
 After target RAID configuration is applied on the bare metal node, Ironic
diff --git a/ironic/drivers/raid_config_schema.json b/ironic/drivers/raid_config_schema.json
index 6a36b51b7d..ebf49f9370 100644
--- a/ironic/drivers/raid_config_schema.json
+++ b/ironic/drivers/raid_config_schema.json
@@ -57,8 +57,10 @@
                         "description": "Controller to use for this logical disk. If not specified, the driver will choose a suitable RAID controller on the bare metal node. Optional."
                     },
                     "physical_disks": {
-                        "type": "array",
-                        "items": { "type": "string" },
+                        "anyOf": [
+                            {"type": "array", "items": { "type": "string" }},
+                            {"type": "array", "items": { "type": "object" }, "minItems": 2}
+                        ],
                         "description": "The physical disks to use for this logical disk. If not specified, the driver will choose suitable physical disks to use. Optional."
                     }
                 },
diff --git a/ironic/tests/unit/common/test_raid.py b/ironic/tests/unit/common/test_raid.py
index 004af870d0..fd1677d8ab 100644
--- a/ironic/tests/unit/common/test_raid.py
+++ b/ironic/tests/unit/common/test_raid.py
@@ -35,6 +35,11 @@ class ValidateRaidConfigurationTestCase(base.TestCase):
         raid.validate_configuration(
             raid_config, raid_config_schema=self.schema)
 
+    def test_validate_configuration_okay_software(self):
+        raid_config = json.loads(raid_constants.RAID_SW_CONFIG_OKAY)
+        raid.validate_configuration(
+            raid_config, raid_config_schema=self.schema)
+
     def test_validate_configuration_no_logical_disk(self):
         self.assertRaises(exception.InvalidParameterValue,
                           raid.validate_configuration,
@@ -140,6 +145,13 @@ class ValidateRaidConfigurationTestCase(base.TestCase):
                           raid_config,
                           raid_config_schema=self.schema)
 
+    def test_validate_configuration_too_few_physical_disks(self):
+        raid_config = json.loads(raid_constants.RAID_CONFIG_TOO_FEW_PHY_DISKS)
+        self.assertRaises(exception.InvalidParameterValue,
+                          raid.validate_configuration,
+                          raid_config,
+                          raid_config_schema=self.schema)
+
     def test_validate_configuration_additional_property(self):
         raid_config = json.loads(raid_constants.RAID_CONFIG_ADDITIONAL_PROP)
         self.assertRaises(exception.InvalidParameterValue,
diff --git a/ironic/tests/unit/raid_constants.py b/ironic/tests/unit/raid_constants.py
index 022349a5ba..9cfc2d159f 100644
--- a/ironic/tests/unit/raid_constants.py
+++ b/ironic/tests/unit/raid_constants.py
@@ -36,6 +36,19 @@ RAID_CONFIG_OKAY = '''
 }
 '''
 
+RAID_SW_CONFIG_OKAY = '''
+{
+  "logical_disks": [
+      {
+       "raid_level": "1",
+       "size_gb": 100,
+       "controller": "software",
+       "physical_disks": [{"size": ">= 50"}, {"name": "/dev/sdc"}]
+      }
+  ]
+}
+'''
+
 RAID_CONFIG_NO_LOGICAL_DISKS = '''
 {
   "logical_disks": []
@@ -196,6 +209,19 @@ RAID_CONFIG_INVALID_PHY_DISKS = '''
 }
 '''
 
+RAID_CONFIG_TOO_FEW_PHY_DISKS = '''
+{
+  "logical_disks": [
+      {
+       "raid_level": "1",
+       "size_gb": 100,
+       "controller": "Smart Array P822 in Slot 2",
+       "physical_disks": [{"size": ">= 50"}]
+      }
+  ]
+}
+'''
+
 RAID_CONFIG_ADDITIONAL_PROP = '''
 {
   "logical_disks": [
diff --git a/releasenotes/notes/raid-hints-c27097ded0137f7c.yaml b/releasenotes/notes/raid-hints-c27097ded0137f7c.yaml
new file mode 100644
index 0000000000..bfdb4dafc3
--- /dev/null
+++ b/releasenotes/notes/raid-hints-c27097ded0137f7c.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Target devices for software RAID can now be specified in the form of
+    device hints (same as for root devices) in the ``physical_disks``
+    parameter of a logical disk configuration. This requires
+    ironic-python-agent from the Ussuri release series.