From 58a395bf8318ae390957684c5dee30578f4137e3 Mon Sep 17 00:00:00 2001
From: Anshul Jain <theanshuljain@gmail.com>
Date: Fri, 23 Mar 2018 02:11:32 -0500
Subject: [PATCH] OOB RAID implementation for ilo5 based HPE Proliant servers.

This commit adds functionality to perform out-of-band RAID operations
for ilo5 based HPE Proliant servers. Using this a user can perform
create and delete the RAID configuration from the server.

Co-Authored-By: Paresh Sao <paresh.sao@hpe.com>
Change-Id: Iad0c609e59dca56729967133c6bbcff73b50a51e
Story: 2003349
Task: 24391
---
 driver-requirements.txt                       |   2 +-
 ironic/common/raid.py                         |  48 +++
 ironic/drivers/ilo.py                         |  13 +
 ironic/drivers/modules/agent.py               |  30 +-
 ironic/drivers/modules/ilo/raid.py            | 235 ++++++++++++
 ironic/tests/unit/common/test_raid.py         |  51 ++-
 .../unit/drivers/modules/ilo/test_raid.py     | 342 ++++++++++++++++++
 .../tests/unit/drivers/modules/test_agent.py  |  52 ++-
 ironic/tests/unit/drivers/test_ilo.py         |  45 +++
 .../unit/drivers/third_party_driver_mocks.py  |   2 +
 .../notes/ilo5-oob-raid-a0eac60f7d77a4fc.yaml |  12 +
 setup.cfg                                     |   2 +
 12 files changed, 787 insertions(+), 47 deletions(-)
 create mode 100644 ironic/drivers/modules/ilo/raid.py
 create mode 100644 ironic/tests/unit/drivers/modules/ilo/test_raid.py
 create mode 100644 releasenotes/notes/ilo5-oob-raid-a0eac60f7d77a4fc.yaml

diff --git a/driver-requirements.txt b/driver-requirements.txt
index 5ee2943f02..8241d62503 100644
--- a/driver-requirements.txt
+++ b/driver-requirements.txt
@@ -4,7 +4,7 @@
 # python projects they should package as optional dependencies for Ironic.
 
 # These are available on pypi
-proliantutils>=2.6.0
+proliantutils>=2.7.0
 pysnmp>=4.3.0,<5.0.0
 python-ironic-inspector-client>=1.5.0
 python-scciclient>=0.8.0
diff --git a/ironic/common/raid.py b/ironic/common/raid.py
index 54a3419c09..3f503beb3c 100644
--- a/ironic/common/raid.py
+++ b/ironic/common/raid.py
@@ -126,3 +126,51 @@ def update_raid_info(node, raid_config):
         node.properties = properties
 
     node.save()
+
+
+def filter_target_raid_config(
+        node, create_root_volume=True, create_nonroot_volumes=True):
+    """Filter the target raid config based on root volume creation
+
+    This method can be used by any raid interface which wants to filter
+    out target raid config based on condition whether the root volume
+    will be created or not.
+
+    :param node: a node object
+    :param create_root_volume: A boolean default value True governing
+        if the root volume is returned else root volumes will be filtered
+        out.
+    :param create_nonroot_volumes: A boolean default value True governing
+        if the non root volume is returned else non-root volumes will be
+        filtered out.
+    :raises: MissingParameterValue, if node.target_raid_config is missing
+        or was found to be empty after skipping root volume and/or non-root
+        volumes.
+    :returns: It will return filtered target_raid_config
+    """
+    if not node.target_raid_config:
+        raise exception.MissingParameterValue(
+            _("Node %s has no target RAID configuration.") % node.uuid)
+
+    target_raid_config = node.target_raid_config.copy()
+
+    error_msg_list = []
+    if not create_root_volume:
+        target_raid_config['logical_disks'] = [
+            x for x in target_raid_config['logical_disks']
+            if not x.get('is_root_volume')]
+        error_msg_list.append(_("skipping root volume"))
+
+    if not create_nonroot_volumes:
+        target_raid_config['logical_disks'] = [
+            x for x in target_raid_config['logical_disks']
+            if x.get('is_root_volume')]
+        error_msg_list.append(_("skipping non-root volumes"))
+
+    if not target_raid_config['logical_disks']:
+        error_msg = _(' and ').join(error_msg_list)
+        raise exception.MissingParameterValue(
+            _("Node %(node)s has empty target RAID configuration "
+              "after %(msg)s.") % {'node': node.uuid, 'msg': error_msg})
+
+    return target_raid_config
diff --git a/ironic/drivers/ilo.py b/ironic/drivers/ilo.py
index 4cf4a6317e..3540c6944c 100644
--- a/ironic/drivers/ilo.py
+++ b/ironic/drivers/ilo.py
@@ -22,6 +22,7 @@ from ironic.drivers.modules.ilo import console
 from ironic.drivers.modules.ilo import inspect
 from ironic.drivers.modules.ilo import management
 from ironic.drivers.modules.ilo import power
+from ironic.drivers.modules.ilo import raid
 from ironic.drivers.modules.ilo import vendor
 from ironic.drivers.modules import inspector
 from ironic.drivers.modules import noop
@@ -69,3 +70,15 @@ class IloHardware(generic.GenericHardware):
     def supported_vendor_interfaces(self):
         """List of supported power interfaces."""
         return [vendor.VendorPassthru, noop.NoVendor]
+
+
+class Ilo5Hardware(IloHardware):
+    """iLO5 hardware type.
+
+    iLO5 hardware type is targeted for iLO5 based Proliant Gen10 servers.
+    """
+
+    @property
+    def supported_raid_interfaces(self):
+        """List of supported raid interfaces."""
+        return [raid.Ilo5RAID, noop.NoRAID]
diff --git a/ironic/drivers/modules/agent.py b/ironic/drivers/modules/agent.py
index 020ebbc1f2..c9344ab153 100644
--- a/ironic/drivers/modules/agent.py
+++ b/ironic/drivers/modules/agent.py
@@ -698,32 +698,10 @@ class AgentRAID(base.RAIDInterface):
                    'create_nonroot_volumes': create_nonroot_volumes,
                    'target_raid_config': node.target_raid_config})
 
-        if not node.target_raid_config:
-            raise exception.MissingParameterValue(
-                _("Node %s has no target RAID configuration.") % node.uuid)
-
-        target_raid_config = node.target_raid_config.copy()
-
-        error_msg_list = []
-        if not create_root_volume:
-            target_raid_config['logical_disks'] = [
-                x for x in target_raid_config['logical_disks']
-                if not x.get('is_root_volume')]
-            error_msg_list.append(_("skipping root volume"))
-
-        if not create_nonroot_volumes:
-            error_msg_list.append(_("skipping non-root volumes"))
-
-            target_raid_config['logical_disks'] = [
-                x for x in target_raid_config['logical_disks']
-                if x.get('is_root_volume')]
-
-        if not target_raid_config['logical_disks']:
-            error_msg = _(' and ').join(error_msg_list)
-            raise exception.MissingParameterValue(
-                _("Node %(node)s has empty target RAID configuration "
-                  "after %(msg)s.") % {'node': node.uuid, 'msg': error_msg})
-
+        target_raid_config = raid.filter_target_raid_config(
+            node,
+            create_root_volume=create_root_volume,
+            create_nonroot_volumes=create_nonroot_volumes)
         # Rewrite it back to the node object, but no need to save it as
         # we need to just send this to the agent ramdisk.
         node.driver_internal_info['target_raid_config'] = target_raid_config
diff --git a/ironic/drivers/modules/ilo/raid.py b/ironic/drivers/modules/ilo/raid.py
new file mode 100644
index 0000000000..07d6951331
--- /dev/null
+++ b/ironic/drivers/modules/ilo/raid.py
@@ -0,0 +1,235 @@
+# Copyright 2018 Hewlett Packard Enterprise Development LP
+#
+# 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.
+
+"""
+iLO5 RAID specific methods
+"""
+
+from ironic_lib import metrics_utils
+from oslo_log import log as logging
+from oslo_utils import importutils
+
+from ironic.common import exception
+from ironic.common.i18n import _
+from ironic.common import raid
+from ironic.common import states
+from ironic.conductor import utils as manager_utils
+from ironic import conf
+from ironic.drivers import base
+from ironic.drivers.modules import deploy_utils
+from ironic.drivers.modules.ilo import common as ilo_common
+
+
+LOG = logging.getLogger(__name__)
+CONF = conf.CONF
+METRICS = metrics_utils.get_metrics_logger(__name__)
+
+ilo_error = importutils.try_import('proliantutils.exception')
+
+
+class Ilo5RAID(base.RAIDInterface):
+    """Implementation of OOB RAIDInterface for iLO5."""
+
+    def get_properties(self):
+        """Return the properties of the interface."""
+        return ilo_common.REQUIRED_PROPERTIES
+
+    def _set_clean_failed(self, task, msg, exc):
+        LOG.error("RAID configuration job failed for node %(node)s. "
+                  "Message: '%(message)s'.",
+                  {'node': task.node.uuid, 'message': msg})
+        task.node.last_error = msg
+        task.process_event('fail')
+
+    def _set_driver_internal_true_value(self, task, *keys):
+        driver_internal_info = task.node.driver_internal_info
+        for key in keys:
+            driver_internal_info[key] = True
+        task.node.driver_internal_info = driver_internal_info
+        task.node.save()
+
+    def _set_driver_internal_false_value(self, task, *keys):
+        driver_internal_info = task.node.driver_internal_info
+        for key in keys:
+            driver_internal_info[key] = False
+        task.node.driver_internal_info = driver_internal_info
+        task.node.save()
+
+    def _pop_driver_internal_values(self, task, *keys):
+        driver_internal_info = task.node.driver_internal_info
+        for key in keys:
+            driver_internal_info.pop(key, None)
+        task.node.driver_internal_info = driver_internal_info
+        task.node.save()
+
+    def _prepare_for_read_raid(self, task, raid_step):
+        deploy_opts = deploy_utils.build_agent_options(task.node)
+        task.driver.boot.prepare_ramdisk(task, deploy_opts)
+        manager_utils.node_power_action(task, states.REBOOT)
+        if raid_step == 'create_raid':
+            self._set_driver_internal_true_value(
+                task, 'ilo_raid_create_in_progress')
+        else:
+            self._set_driver_internal_true_value(
+                task, 'ilo_raid_delete_in_progress')
+        self._set_driver_internal_true_value(task, 'cleaning_reboot')
+        self._set_driver_internal_false_value(task, 'skip_current_clean_step')
+
+    @METRICS.timer('Ilo5RAID.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
+        }
+    })
+    def create_configuration(self, task, create_root_volume=True,
+                             create_nonroot_volumes=True):
+        """Create a RAID configuration on a bare metal using agent ramdisk.
+
+        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.
+        :raises: MissingParameterValue, if node.target_raid_config is missing
+            or was found to be empty after skipping root volume and/or non-root
+            volumes.
+        :raises: NodeCleaningFailure, on failure to execute step.
+        """
+        node = task.node
+        target_raid_config = raid.filter_target_raid_config(
+            node, create_root_volume=create_root_volume,
+            create_nonroot_volumes=create_nonroot_volumes)
+        driver_internal_info = node.driver_internal_info
+        driver_internal_info['target_raid_config'] = target_raid_config
+        LOG.debug("Calling OOB RAID create_configuration for node %(node)s "
+                  "with the following target RAID configuration: %(target)s",
+                  {'node': node.uuid, 'target': target_raid_config})
+        ilo_object = ilo_common.get_ilo_object(node)
+
+        try:
+            # Raid configuration in progress, checking status
+            if not driver_internal_info.get('ilo_raid_create_in_progress'):
+                ilo_object.create_raid_configuration(target_raid_config)
+                self._prepare_for_read_raid(task, 'create_raid')
+                return states.CLEANWAIT
+            else:
+                # Raid configuration is done, updating raid_config
+                raid_conf = (
+                    ilo_object.read_raid_configuration(
+                        raid_config=target_raid_config))
+                if len(raid_conf['logical_disks']):
+                    raid.update_raid_info(node, raid_conf)
+                    LOG.debug("Node %(uuid)s raid create clean step is done.",
+                              {'uuid': node.uuid})
+                    self._pop_driver_internal_values(
+                        task, 'ilo_raid_create_in_progress',
+                        'cleaning_reboot', 'skip_current_clean_step')
+                    node.driver_internal_info = driver_internal_info
+                    node.save()
+                else:
+                    # Raid configuration failed
+                    msg = "Unable to create raid"
+                    self._pop_driver_internal_values(
+                        task, 'ilo_raid_create_in_progress',
+                        'cleaning_reboot', 'skip_current_clean_step')
+                    node.driver_internal_info = driver_internal_info
+                    node.save()
+                    raise exception.NodeCleaningFailure(
+                        "Clean step create_configuration failed "
+                        "on node %(node)s with error: %(err)s" %
+                        {'node': node.uuid, 'err': msg})
+        except ilo_error.IloError as ilo_exception:
+            operation = (_("Failed to create raid configuration on node %s")
+                         % node.uuid)
+            self._pop_driver_internal_values(task,
+                                             'ilo_raid_create_in_progress',
+                                             'cleaning_reboot',
+                                             'skip_current_clean_step')
+            node.driver_internal_info = driver_internal_info
+            node.save()
+            self._set_clean_failed(task, operation, ilo_exception)
+
+    @METRICS.timer('Ilo5RAID.delete_configuration')
+    @base.clean_step(priority=0, abortable=False)
+    def delete_configuration(self, task):
+        """Delete the RAID configuration.
+
+        :param task: a TaskManager instance  containing the node to act on.
+        :raises: NodeCleaningFailure, on failure to execute step.
+        """
+        node = task.node
+        LOG.debug("OOB RAID delete_configuration invoked for node %s.",
+                  node.uuid)
+        driver_internal_info = node.driver_internal_info
+        ilo_object = ilo_common.get_ilo_object(node)
+
+        try:
+            # Raid configuration in progress, checking status
+            if not driver_internal_info.get('ilo_raid_delete_in_progress'):
+                ilo_object.delete_raid_configuration()
+                self._prepare_for_read_raid(task, 'delete_raid')
+                return states.CLEANWAIT
+            else:
+                # Raid configuration is done, updating raid_config
+                raid_conf = ilo_object.read_raid_configuration()
+                if not len(raid_conf['logical_disks']):
+                    node.raid_config = {}
+                    LOG.debug("Node %(uuid)s raid delete clean step is done.",
+                              {'uuid': node.uuid})
+                    self._pop_driver_internal_values(
+                        task, 'ilo_raid_delete_in_progress',
+                        'cleaning_reboot', 'skip_current_clean_step')
+                    node.driver_internal_info = driver_internal_info
+                    node.save()
+                else:
+                    # Raid configuration failed
+                    msg = ("Unable to delete this logical disks: %s" %
+                           raid_conf['logical_disks'])
+                    self._pop_driver_internal_values(
+                        task, 'ilo_raid_delete_in_progress',
+                        'cleaning_reboot', 'skip_current_clean_step')
+                    node.driver_internal_info = driver_internal_info
+                    node.save()
+                    raise exception.NodeCleaningFailure(
+                        "Clean step delete_configuration failed "
+                        "on node %(node)s with error: %(err)s" %
+                        {'node': node.uuid, 'err': msg})
+        except ilo_error.IloLogicalDriveNotFoundError:
+            LOG.info("No logical drive found to delete on node %(node)s",
+                     {'node': node.uuid})
+        except ilo_error.IloError as ilo_exception:
+            operation = (_("Failed to delete raid configuration on node %s")
+                         % node.uuid)
+            self._pop_driver_internal_values(task,
+                                             'ilo_raid_delete_in_progress',
+                                             'cleaning_reboot',
+                                             'skip_current_clean_step')
+            node.driver_internal_info = driver_internal_info
+            node.save()
+            self._set_clean_failed(task, operation, ilo_exception)
diff --git a/ironic/tests/unit/common/test_raid.py b/ironic/tests/unit/common/test_raid.py
index 40ae9f400f..004af870d0 100644
--- a/ironic/tests/unit/common/test_raid.py
+++ b/ironic/tests/unit/common/test_raid.py
@@ -161,6 +161,21 @@ class ValidateRaidConfigurationTestCase(base.TestCase):
 
 class RaidPublicMethodsTestCase(db_base.DbTestCase):
 
+    def setUp(self):
+        super(RaidPublicMethodsTestCase, self).setUp()
+        self.target_raid_config = {
+            "logical_disks": [
+                {'size_gb': 200, 'raid_level': 0, 'is_root_volume': True},
+                {'size_gb': 200, 'raid_level': 5}
+            ]}
+        n = {
+            'boot_interface': 'pxe',
+            'deploy_interface': 'direct',
+            'raid_interface': 'agent',
+            'target_raid_config': self.target_raid_config,
+        }
+        self.node = obj_utils.create_test_node(self.context, **n)
+
     def test_get_logical_disk_properties(self):
         with open(drivers_base.RAID_CONFIG_SCHEMA, 'r') as raid_schema_fobj:
             schema = json.load(raid_schema_fobj)
@@ -186,7 +201,7 @@ class RaidPublicMethodsTestCase(db_base.DbTestCase):
 
     def _test_update_raid_info(self, current_config,
                                capabilities=None):
-        node = obj_utils.create_test_node(self.context)
+        node = self.node
         if capabilities:
             properties = node.properties
             properties['capabilities'] = capabilities
@@ -239,3 +254,37 @@ class RaidPublicMethodsTestCase(db_base.DbTestCase):
         self.assertRaises(exception.InvalidParameterValue,
                           self._test_update_raid_info,
                           current_config)
+
+    def test_filter_target_raid_config(self):
+        result = raid.filter_target_raid_config(self.node)
+        self.assertEqual(self.node.target_raid_config, result)
+
+    def test_filter_target_raid_config_skip_root(self):
+        result = raid.filter_target_raid_config(
+            self.node, create_root_volume=False)
+        exp_target_raid_config = {
+            "logical_disks": [{'size_gb': 200, 'raid_level': 5}]}
+        self.assertEqual(exp_target_raid_config, result)
+
+    def test_filter_target_raid_config_skip_nonroot(self):
+        result = raid.filter_target_raid_config(
+            self.node, create_nonroot_volumes=False)
+        exp_target_raid_config = {
+            "logical_disks": [{'size_gb': 200,
+                               'raid_level': 0,
+                               'is_root_volume': True}]}
+        self.assertEqual(exp_target_raid_config, result)
+
+    def test_filter_target_raid_config_no_target_raid_config_after_skipping(
+            self):
+        self.assertRaises(exception.MissingParameterValue,
+                          raid.filter_target_raid_config,
+                          self.node, create_root_volume=False,
+                          create_nonroot_volumes=False)
+
+    def test_filter_target_raid_config_empty_target_raid_config(self):
+        self.node.target_raid_config = {}
+        self.node.save()
+        self.assertRaises(exception.MissingParameterValue,
+                          raid.filter_target_raid_config,
+                          self.node)
diff --git a/ironic/tests/unit/drivers/modules/ilo/test_raid.py b/ironic/tests/unit/drivers/modules/ilo/test_raid.py
new file mode 100644
index 0000000000..34b859023d
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/ilo/test_raid.py
@@ -0,0 +1,342 @@
+# Copyright 2018 Hewlett Packard Enterprise Development LP
+#
+# 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 Raid Interface used by iLO5."""
+
+import mock
+from oslo_utils import importutils
+
+from ironic.common import exception
+from ironic.common import raid
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.conductor import utils as manager_utils
+from ironic.drivers.modules import deploy_utils
+from ironic.drivers.modules.ilo import common as ilo_common
+from ironic.drivers.modules.ilo import raid as ilo_raid
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as obj_utils
+
+ilo_error = importutils.try_import('proliantutils.exception')
+
+INFO_DICT = db_utils.get_test_ilo_info()
+
+
+class Ilo5RAIDTestCase(db_base.DbTestCase):
+
+    def setUp(self):
+        super(Ilo5RAIDTestCase, 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': 200, 'raid_level': 5}
+            ]}
+        self.clean_step = {'step': 'create_configuration',
+                           'interface': 'raid'}
+        n = {
+            'driver': 'ilo5',
+            'driver_info': INFO_DICT,
+            'target_raid_config': self.target_raid_config,
+            'clean_step': self.clean_step,
+        }
+        self.config(enabled_hardware_types=['ilo5'],
+                    enabled_boot_interfaces=['ilo-virtual-media'],
+                    enabled_console_interfaces=['ilo'],
+                    enabled_deploy_interfaces=['iscsi'],
+                    enabled_inspect_interfaces=['ilo'],
+                    enabled_management_interfaces=['ilo'],
+                    enabled_power_interfaces=['ilo'],
+                    enabled_raid_interfaces=['ilo5'])
+        self.node = obj_utils.create_test_node(self.context, **n)
+
+    @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
+    @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+    def test__prepare_for_read_raid_create_raid(
+            self, mock_reboot, mock_build_opt):
+        with task_manager.acquire(self.context, self.node.uuid) as task:
+            mock_build_opt.return_value = []
+            task.driver.raid._prepare_for_read_raid(task, 'create_raid')
+            self.assertTrue(
+                task.node.driver_internal_info.get(
+                    'ilo_raid_create_in_progress'))
+            self.assertTrue(
+                task.node.driver_internal_info.get(
+                    'cleaning_reboot'))
+            self.assertFalse(
+                task.node.driver_internal_info.get(
+                    'skip_current_clean_step'))
+            mock_reboot.assert_called_once_with(task, states.REBOOT)
+
+    @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
+    @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+    def test__prepare_for_read_raid_delete_raid(
+            self, mock_reboot, mock_build_opt):
+        with task_manager.acquire(self.context, self.node.uuid) as task:
+            mock_build_opt.return_value = []
+            task.driver.raid._prepare_for_read_raid(task, 'delete_raid')
+            self.assertTrue(
+                task.node.driver_internal_info.get(
+                    'ilo_raid_delete_in_progress'))
+            self.assertTrue(
+                task.node.driver_internal_info.get(
+                    'cleaning_reboot'))
+            self.assertEqual(
+                task.node.driver_internal_info.get(
+                    'skip_current_clean_step'), False)
+            mock_reboot.assert_called_once_with(task, states.REBOOT)
+
+    @mock.patch.object(ilo_raid.Ilo5RAID, '_prepare_for_read_raid')
+    @mock.patch.object(raid, 'filter_target_raid_config')
+    @mock.patch.object(ilo_common, 'get_ilo_object', autospec=True)
+    def test_create_configuration(
+            self, ilo_mock, filter_target_raid_config_mock, prepare_raid_mock):
+        ilo_mock_object = ilo_mock.return_value
+        with task_manager.acquire(self.context, self.node.uuid) as task:
+            filter_target_raid_config_mock.return_value = (
+                self.target_raid_config)
+            result = task.driver.raid.create_configuration(task)
+            prepare_raid_mock.assert_called_once_with(task, 'create_raid')
+        (ilo_mock_object.create_raid_configuration.
+         assert_called_once_with(self.target_raid_config))
+        self.assertEqual(states.CLEANWAIT, result)
+
+    @mock.patch.object(raid, 'update_raid_info', autospec=True)
+    @mock.patch.object(raid, 'filter_target_raid_config')
+    @mock.patch.object(ilo_common, 'get_ilo_object', autospec=True)
+    def test_create_configuration_with_read_raid(
+            self, ilo_mock, filter_target_raid_config_mock, update_raid_mock):
+        raid_conf = {u'logical_disks':
+                     [{u'size_gb': 89,
+                       u'physical_disks': [u'5I:1:1'],
+                       u'raid_level': u'0',
+                       u'root_device_hint': {u'wwn': u'0x600508b1001c7e87'},
+                       u'controller': u'Smart Array P822 in Slot 1',
+                       u'volume_name': u'0006EB7BPDVTF0BRH5L0EAEDDA'}]
+                     }
+        ilo_mock_object = ilo_mock.return_value
+        self.node.driver_internal_info = {'ilo_raid_create_in_progress': True}
+        self.node.save()
+        with task_manager.acquire(self.context, self.node.uuid) as task:
+            filter_target_raid_config_mock.return_value = (
+                self.target_raid_config)
+            ilo_mock_object.read_raid_configuration.return_value = raid_conf
+            task.driver.raid.create_configuration(task)
+            update_raid_mock.assert_called_once_with(task.node, raid_conf)
+            self.assertNotIn('ilo_raid_create_in_progress',
+                             task.node.driver_internal_info)
+            self.assertNotIn('cleaning_reboot',
+                             task.node.driver_internal_info)
+            self.assertNotIn('skip_current_clean_step',
+                             task.node.driver_internal_info)
+
+    @mock.patch.object(raid, 'filter_target_raid_config')
+    @mock.patch.object(ilo_common, 'get_ilo_object', autospec=True)
+    def test_create_configuration_with_read_raid_failed(
+            self, ilo_mock, filter_target_raid_config_mock):
+        raid_conf = {u'logical_disks': []}
+        self.node.driver_internal_info = {'ilo_raid_create_in_progress': True}
+        self.node.save()
+        ilo_mock_object = ilo_mock.return_value
+        with task_manager.acquire(self.context, self.node.uuid) as task:
+            filter_target_raid_config_mock.return_value = (
+                self.target_raid_config)
+            ilo_mock_object.read_raid_configuration.return_value = raid_conf
+            self.assertRaises(exception.NodeCleaningFailure,
+                              task.driver.raid.create_configuration, task)
+            self.assertNotIn('ilo_raid_create_in_progress',
+                             task.node.driver_internal_info)
+            self.assertNotIn('cleaning_reboot',
+                             task.node.driver_internal_info)
+            self.assertNotIn('skip_current_clean_step',
+                             task.node.driver_internal_info)
+
+    @mock.patch.object(raid, 'filter_target_raid_config')
+    @mock.patch.object(ilo_common, 'get_ilo_object', autospec=True)
+    def test_create_configuration_empty_target_raid_config(
+            self, ilo_mock, filter_target_raid_config_mock):
+        self.node.target_raid_config = {}
+        self.node.save()
+        ilo_mock_object = ilo_mock.return_value
+        with task_manager.acquire(self.context, self.node.uuid) as task:
+            msg = "Node %s has no target RAID configuration" % self.node.uuid
+            filter_target_raid_config_mock.side_effect = (
+                exception.MissingParameterValue(msg))
+            self.assertRaises(exception.MissingParameterValue,
+                              task.driver.raid.create_configuration, task)
+        self.assertFalse(ilo_mock_object.create_raid_configuration.called)
+
+    @mock.patch.object(ilo_raid.Ilo5RAID, '_prepare_for_read_raid')
+    @mock.patch.object(raid, 'filter_target_raid_config')
+    @mock.patch.object(ilo_common, 'get_ilo_object', autospec=True)
+    def test_create_configuration_skip_root(
+            self, ilo_mock, filter_target_raid_config_mock,
+            prepare_raid_mock):
+        ilo_mock_object = ilo_mock.return_value
+        with task_manager.acquire(self.context, self.node.uuid) as task:
+            exp_target_raid_config = {
+                "logical_disks": [
+                    {'size_gb': 200, 'raid_level': 5}
+                ]}
+            filter_target_raid_config_mock.return_value = (
+                exp_target_raid_config)
+            result = task.driver.raid.create_configuration(
+                task, create_root_volume=False)
+            (ilo_mock_object.create_raid_configuration.
+             assert_called_once_with(exp_target_raid_config))
+            self.assertEqual(states.CLEANWAIT, result)
+            prepare_raid_mock.assert_called_once_with(task, 'create_raid')
+            self.assertEqual(
+                exp_target_raid_config,
+                task.node.driver_internal_info['target_raid_config'])
+
+    @mock.patch.object(ilo_raid.Ilo5RAID, '_prepare_for_read_raid')
+    @mock.patch.object(raid, 'filter_target_raid_config')
+    @mock.patch.object(ilo_common, 'get_ilo_object', autospec=True)
+    def test_create_configuration_skip_non_root(
+            self, ilo_mock, filter_target_raid_config_mock, prepare_raid_mock):
+        ilo_mock_object = ilo_mock.return_value
+        with task_manager.acquire(self.context, self.node.uuid) as task:
+            exp_target_raid_config = {
+                "logical_disks": [
+                    {'size_gb': 200, 'raid_level': 0, 'is_root_volume': True}
+                ]}
+            filter_target_raid_config_mock.return_value = (
+                exp_target_raid_config)
+            result = task.driver.raid.create_configuration(
+                task, create_nonroot_volumes=False)
+            (ilo_mock_object.create_raid_configuration.
+             assert_called_once_with(exp_target_raid_config))
+            prepare_raid_mock.assert_called_once_with(task, 'create_raid')
+            self.assertEqual(states.CLEANWAIT, result)
+            self.assertEqual(
+                exp_target_raid_config,
+                task.node.driver_internal_info['target_raid_config'])
+
+    @mock.patch.object(raid, 'filter_target_raid_config')
+    @mock.patch.object(ilo_common, 'get_ilo_object', autospec=True)
+    def test_create_configuration_skip_root_skip_non_root(
+            self, ilo_mock, filter_target_raid_config_mock):
+        ilo_mock_object = ilo_mock.return_value
+        with task_manager.acquire(self.context, self.node.uuid) as task:
+            msg = "Node %s has no target RAID configuration" % self.node.uuid
+            filter_target_raid_config_mock.side_effect = (
+                exception.MissingParameterValue(msg))
+            self.assertRaises(
+                exception.MissingParameterValue,
+                task.driver.raid.create_configuration,
+                task, False, False)
+            self.assertFalse(ilo_mock_object.create_raid_configuration.called)
+
+    @mock.patch.object(ilo_raid.Ilo5RAID, '_set_clean_failed')
+    @mock.patch.object(ilo_common, 'get_ilo_object', autospec=True)
+    def test_create_configuration_ilo_error(self, ilo_mock,
+                                            set_clean_failed_mock):
+        ilo_mock_object = ilo_mock.return_value
+        exc = ilo_error.IloError('error')
+        ilo_mock_object.create_raid_configuration.side_effect = exc
+        with task_manager.acquire(self.context, self.node.uuid) as task:
+            task.driver.raid.create_configuration(
+                task, create_nonroot_volumes=False)
+            set_clean_failed_mock.assert_called_once_with(
+                task,
+                'Failed to create raid configuration '
+                'on node %s' % self.node.uuid, exc)
+            self.assertNotIn('ilo_raid_create_in_progress',
+                             task.node.driver_internal_info)
+            self.assertNotIn('cleaning_reboot',
+                             task.node.driver_internal_info)
+            self.assertNotIn('skip_current_clean_step',
+                             task.node.driver_internal_info)
+
+    @mock.patch.object(ilo_raid.Ilo5RAID, '_prepare_for_read_raid')
+    @mock.patch.object(ilo_common, 'get_ilo_object', autospec=True)
+    def test_delete_configuration(self, ilo_mock, prepare_raid_mock):
+        ilo_mock_object = ilo_mock.return_value
+        with task_manager.acquire(self.context, self.node.uuid) as task:
+            result = task.driver.raid.delete_configuration(task)
+        self.assertEqual(states.CLEANWAIT, result)
+        ilo_mock_object.delete_raid_configuration.assert_called_once_with()
+        prepare_raid_mock.assert_called_once_with(task, 'delete_raid')
+
+    @mock.patch.object(ilo_raid.LOG, 'info', spec_set=True,
+                       autospec=True)
+    @mock.patch.object(ilo_raid.Ilo5RAID, '_prepare_for_read_raid')
+    @mock.patch.object(ilo_common, 'get_ilo_object', autospec=True)
+    def test_delete_configuration_no_logical_drive(
+            self, ilo_mock, prepare_raid_mock, log_mock):
+        ilo_mock_object = ilo_mock.return_value
+        exc = ilo_error.IloLogicalDriveNotFoundError('No logical drive found')
+        with task_manager.acquire(self.context, self.node.uuid) as task:
+            ilo_mock_object.delete_raid_configuration.side_effect = exc
+            task.driver.raid.delete_configuration(task)
+            self.assertTrue(log_mock.called)
+
+    @mock.patch.object(ilo_common, 'get_ilo_object', autospec=True)
+    def test_delete_configuration_with_read_raid(self, ilo_mock):
+        raid_conf = {u'logical_disks': []}
+        self.node.driver_internal_info = {'ilo_raid_delete_in_progress': True}
+        self.node.save()
+        ilo_mock_object = ilo_mock.return_value
+        with task_manager.acquire(self.context, self.node.uuid) as task:
+            ilo_mock_object.read_raid_configuration.return_value = raid_conf
+            task.driver.raid.delete_configuration(task)
+            self.assertEqual(self.node.raid_config, {})
+            self.assertNotIn('ilo_raid_delete_in_progress',
+                             task.node.driver_internal_info)
+            self.assertNotIn('cleaning_reboot',
+                             task.node.driver_internal_info)
+            self.assertNotIn('skip_current_clean_step',
+                             task.node.driver_internal_info)
+
+    @mock.patch.object(ilo_common, 'get_ilo_object', autospec=True)
+    def test_delete_configuration_with_read_raid_failed(self, ilo_mock):
+        raid_conf = {u'logical_disks': [{'size_gb': 200,
+                                         'raid_level': 0,
+                                         'is_root_volume': True}]}
+        self.node.driver_internal_info = {'ilo_raid_delete_in_progress': True}
+        self.node.save()
+        ilo_mock_object = ilo_mock.return_value
+        with task_manager.acquire(self.context, self.node.uuid) as task:
+            ilo_mock_object.read_raid_configuration.return_value = raid_conf
+            self.assertRaises(exception.NodeCleaningFailure,
+                              task.driver.raid.delete_configuration, task)
+            self.assertNotIn('ilo_raid_delete_in_progress',
+                             task.node.driver_internal_info)
+            self.assertNotIn('cleaning_reboot',
+                             task.node.driver_internal_info)
+            self.assertNotIn('skip_current_clean_step',
+                             task.node.driver_internal_info)
+
+    @mock.patch.object(ilo_raid.Ilo5RAID, '_set_clean_failed')
+    @mock.patch.object(ilo_common, 'get_ilo_object', autospec=True)
+    def test_delete_configuration_ilo_error(self, ilo_mock,
+                                            set_clean_failed_mock):
+        ilo_mock_object = ilo_mock.return_value
+        exc = ilo_error.IloError('error')
+        ilo_mock_object.delete_raid_configuration.side_effect = exc
+        with task_manager.acquire(self.context, self.node.uuid) as task:
+            task.driver.raid.delete_configuration(task)
+            ilo_mock_object.delete_raid_configuration.assert_called_once_with()
+            self.assertNotIn('ilo_raid_delete_in_progress',
+                             task.node.driver_internal_info)
+            self.assertNotIn('cleaning_reboot',
+                             task.node.driver_internal_info)
+            self.assertNotIn('skip_current_clean_step',
+                             task.node.driver_internal_info)
+            set_clean_failed_mock.assert_called_once_with(
+                task,
+                'Failed to delete raid configuration '
+                'on node %s' % self.node.uuid, exc)
diff --git a/ironic/tests/unit/drivers/modules/test_agent.py b/ironic/tests/unit/drivers/modules/test_agent.py
index 16c653c433..5e893a98ca 100644
--- a/ironic/tests/unit/drivers/modules/test_agent.py
+++ b/ironic/tests/unit/drivers/modules/test_agent.py
@@ -1406,12 +1406,15 @@ class AgentRAIDTestCase(db_base.DbTestCase):
         self.assertEqual(0, ret[0]['priority'])
         self.assertEqual(0, ret[1]['priority'])
 
+    @mock.patch.object(raid, 'filter_target_raid_config')
     @mock.patch.object(deploy_utils, 'agent_execute_clean_step',
                        autospec=True)
-    def test_create_configuration(self, execute_mock):
+    def test_create_configuration(self, execute_mock,
+                                  filter_target_raid_config_mock):
         with task_manager.acquire(self.context, self.node.uuid) as task:
             execute_mock.return_value = states.CLEANWAIT
-
+            filter_target_raid_config_mock.return_value = (
+                self.target_raid_config)
             return_value = task.driver.raid.create_configuration(task)
 
             self.assertEqual(states.CLEANWAIT, return_value)
@@ -1420,65 +1423,76 @@ class AgentRAIDTestCase(db_base.DbTestCase):
                 task.node.driver_internal_info['target_raid_config'])
             execute_mock.assert_called_once_with(task, self.clean_step)
 
+    @mock.patch.object(raid, 'filter_target_raid_config')
     @mock.patch.object(deploy_utils, 'agent_execute_clean_step',
                        autospec=True)
-    def test_create_configuration_skip_root(self, execute_mock):
+    def test_create_configuration_skip_root(self, execute_mock,
+                                            filter_target_raid_config_mock):
         with task_manager.acquire(self.context, self.node.uuid) as task:
             execute_mock.return_value = states.CLEANWAIT
-
-            return_value = task.driver.raid.create_configuration(
-                task, create_root_volume=False)
-
-            self.assertEqual(states.CLEANWAIT, return_value)
-            execute_mock.assert_called_once_with(task, self.clean_step)
             exp_target_raid_config = {
                 "logical_disks": [
                     {'size_gb': 200, 'raid_level': 5}
                 ]}
+            filter_target_raid_config_mock.return_value = (
+                exp_target_raid_config)
+            return_value = task.driver.raid.create_configuration(
+                task, create_root_volume=False)
+            self.assertEqual(states.CLEANWAIT, return_value)
+            execute_mock.assert_called_once_with(task, self.clean_step)
             self.assertEqual(
                 exp_target_raid_config,
                 task.node.driver_internal_info['target_raid_config'])
 
+    @mock.patch.object(raid, 'filter_target_raid_config')
     @mock.patch.object(deploy_utils, 'agent_execute_clean_step',
                        autospec=True)
-    def test_create_configuration_skip_nonroot(self, execute_mock):
+    def test_create_configuration_skip_nonroot(self, execute_mock,
+                                               filter_target_raid_config_mock):
         with task_manager.acquire(self.context, self.node.uuid) as task:
             execute_mock.return_value = states.CLEANWAIT
-
-            return_value = task.driver.raid.create_configuration(
-                task, create_nonroot_volumes=False)
-
-            self.assertEqual(states.CLEANWAIT, return_value)
-            execute_mock.assert_called_once_with(task, self.clean_step)
             exp_target_raid_config = {
                 "logical_disks": [
                     {'size_gb': 200, 'raid_level': 0, 'is_root_volume': True},
                 ]}
+            filter_target_raid_config_mock.return_value = (
+                exp_target_raid_config)
+            return_value = task.driver.raid.create_configuration(
+                task, create_nonroot_volumes=False)
+            self.assertEqual(states.CLEANWAIT, return_value)
+            execute_mock.assert_called_once_with(task, self.clean_step)
             self.assertEqual(
                 exp_target_raid_config,
                 task.node.driver_internal_info['target_raid_config'])
 
+    @mock.patch.object(raid, 'filter_target_raid_config')
     @mock.patch.object(deploy_utils, 'agent_execute_clean_step',
                        autospec=True)
     def test_create_configuration_no_target_raid_config_after_skipping(
-            self, execute_mock):
+            self, execute_mock, filter_target_raid_config_mock):
         with task_manager.acquire(self.context, self.node.uuid) as task:
+            msg = "Node %s has no target RAID configuration" % self.node.uuid
+            filter_target_raid_config_mock.side_effect = (
+                exception.MissingParameterValue(msg))
             self.assertRaises(
                 exception.MissingParameterValue,
                 task.driver.raid.create_configuration,
                 task, create_root_volume=False,
                 create_nonroot_volumes=False)
-
             self.assertFalse(execute_mock.called)
 
+    @mock.patch.object(raid, 'filter_target_raid_config')
     @mock.patch.object(deploy_utils, 'agent_execute_clean_step',
                        autospec=True)
     def test_create_configuration_empty_target_raid_config(
-            self, execute_mock):
+            self, execute_mock, filter_target_raid_config_mock):
         execute_mock.return_value = states.CLEANING
         self.node.target_raid_config = {}
         self.node.save()
         with task_manager.acquire(self.context, self.node.uuid) as task:
+            msg = "Node %s has no target RAID configuration" % self.node.uuid
+            filter_target_raid_config_mock.side_effect = (
+                exception.MissingParameterValue(msg))
             self.assertRaises(exception.MissingParameterValue,
                               task.driver.raid.create_configuration,
                               task)
diff --git a/ironic/tests/unit/drivers/test_ilo.py b/ironic/tests/unit/drivers/test_ilo.py
index 321ace576f..ed5359fa05 100644
--- a/ironic/tests/unit/drivers/test_ilo.py
+++ b/ironic/tests/unit/drivers/test_ilo.py
@@ -19,6 +19,7 @@ Test class for iLO Drivers
 from ironic.conductor import task_manager
 from ironic.drivers import ilo
 from ironic.drivers.modules import agent
+from ironic.drivers.modules.ilo import raid
 from ironic.drivers.modules import inspector
 from ironic.drivers.modules import iscsi_deploy
 from ironic.drivers.modules import noop
@@ -165,3 +166,47 @@ class IloHardwareTestCase(db_base.DbTestCase):
                                   agent.AgentDeploy)
             self.assertIsInstance(task.driver.raid,
                                   agent.AgentRAID)
+
+
+class Ilo5HardwareTestCase(db_base.DbTestCase):
+
+    def setUp(self):
+        super(Ilo5HardwareTestCase, self).setUp()
+        self.config(enabled_hardware_types=['ilo5'],
+                    enabled_boot_interfaces=['ilo-virtual-media', 'ilo-pxe'],
+                    enabled_console_interfaces=['ilo'],
+                    enabled_deploy_interfaces=['iscsi', 'direct'],
+                    enabled_inspect_interfaces=['ilo'],
+                    enabled_management_interfaces=['ilo'],
+                    enabled_power_interfaces=['ilo'],
+                    enabled_raid_interfaces=['ilo5'],
+                    enabled_rescue_interfaces=['no-rescue', 'agent'],
+                    enabled_vendor_interfaces=['ilo', 'no-vendor'])
+
+    def test_default_interfaces(self):
+        node = obj_utils.create_test_node(self.context, driver='ilo5')
+        with task_manager.acquire(self.context, node.id) as task:
+            self.assertIsInstance(task.driver.raid, raid.Ilo5RAID)
+
+    def test_override_with_no_raid(self):
+        self.config(enabled_raid_interfaces=['no-raid', 'ilo5'])
+        node = obj_utils.create_test_node(self.context, driver='ilo5',
+                                          raid_interface='no-raid')
+        with task_manager.acquire(self.context, node.id) as task:
+            self.assertIsInstance(task.driver.raid, noop.NoRAID)
+            self.assertIsInstance(task.driver.boot,
+                                  ilo.boot.IloVirtualMediaBoot)
+            self.assertIsInstance(task.driver.console,
+                                  ilo.console.IloConsoleInterface)
+            self.assertIsInstance(task.driver.deploy,
+                                  iscsi_deploy.ISCSIDeploy)
+            self.assertIsInstance(task.driver.inspect,
+                                  ilo.inspect.IloInspect)
+            self.assertIsInstance(task.driver.management,
+                                  ilo.management.IloManagement)
+            self.assertIsInstance(task.driver.power,
+                                  ilo.power.IloPower)
+            self.assertIsInstance(task.driver.rescue,
+                                  noop.NoRescue)
+            self.assertIsInstance(task.driver.vendor,
+                                  ilo.vendor.VendorPassthru)
diff --git a/ironic/tests/unit/drivers/third_party_driver_mocks.py b/ironic/tests/unit/drivers/third_party_driver_mocks.py
index 56c3a38783..03850a48e6 100644
--- a/ironic/tests/unit/drivers/third_party_driver_mocks.py
+++ b/ironic/tests/unit/drivers/third_party_driver_mocks.py
@@ -56,6 +56,8 @@ if not proliantutils:
     sys.modules['proliantutils.utils'] = proliantutils.utils
     proliantutils.utils.process_firmware_image = mock.MagicMock()
     proliantutils.exception.IloError = type('IloError', (Exception,), {})
+    proliantutils.exception.IloLogicalDriveNotFoundError = (
+        type('IloLogicalDriveNotFoundError', (Exception,), {}))
     command_exception = type('IloCommandNotSupportedError', (Exception,), {})
     proliantutils.exception.IloCommandNotSupportedError = command_exception
     proliantutils.exception.IloCommandNotSupportedInBiosError = type(
diff --git a/releasenotes/notes/ilo5-oob-raid-a0eac60f7d77a4fc.yaml b/releasenotes/notes/ilo5-oob-raid-a0eac60f7d77a4fc.yaml
new file mode 100644
index 0000000000..4325cb09fc
--- /dev/null
+++ b/releasenotes/notes/ilo5-oob-raid-a0eac60f7d77a4fc.yaml
@@ -0,0 +1,12 @@
+---
+features:
+  - Adds new hardware type ``ilo5``. Including all other hardware interfaces
+    ``ilo`` hardware type supports, this has one new RAID interface ``ilo5``.
+  - Adds functionality to perform out-of-band RAID operation for iLO5 based
+    HPE Proliant servers.
+upgrade:
+  - The ``create_raid_configuration``, ``delete_raid_configuration`` and
+    ``read_raid_configuration`` interfaces of 'proliantutils' library has been
+    enhanced to support out-of-band RAID operation for ``ilo5`` hardware type.
+    To leverage this feature, the 'proliantutils' library needs to be upgraded
+    to version '2.7.0'.
diff --git a/setup.cfg b/setup.cfg
index 128b75e5ab..cfab34dd3f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -125,6 +125,7 @@ ironic.hardware.interfaces.raid =
     agent = ironic.drivers.modules.agent:AgentRAID
     fake = ironic.drivers.modules.fake:FakeRAID
     idrac = ironic.drivers.modules.drac.raid:DracRAID
+    ilo5 = ironic.drivers.modules.ilo.raid:Ilo5RAID
     irmc = ironic.drivers.modules.irmc.raid:IRMCRAID
     no-raid = ironic.drivers.modules.noop:NoRAID
 
@@ -152,6 +153,7 @@ ironic.hardware.types =
     fake-hardware = ironic.drivers.fake_hardware:FakeHardware
     idrac = ironic.drivers.drac:IDRACHardware
     ilo = ironic.drivers.ilo:IloHardware
+    ilo5 = ironic.drivers.ilo:Ilo5Hardware
     ipmi = ironic.drivers.ipmi:IPMIHardware
     irmc = ironic.drivers.irmc:IRMCHardware
     manual-management = ironic.drivers.generic:ManualManagementHardware