summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnshul Jain <theanshuljain@gmail.com>2018-03-23 02:11:32 -0500
committerparesh-sao <paresh.sao@hpe.com>2019-01-03 09:03:36 +0000
commit58a395bf8318ae390957684c5dee30578f4137e3 (patch)
tree3bd69b1ba3ca3b90825f9b5fd7f03e84a68089d7
parent565cd192fc108bca6fc8394012249aa004743310 (diff)
downloadironic-58a395bf8318ae390957684c5dee30578f4137e3.tar.gz
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
-rw-r--r--driver-requirements.txt2
-rw-r--r--ironic/common/raid.py48
-rw-r--r--ironic/drivers/ilo.py13
-rw-r--r--ironic/drivers/modules/agent.py30
-rw-r--r--ironic/drivers/modules/ilo/raid.py235
-rw-r--r--ironic/tests/unit/common/test_raid.py51
-rw-r--r--ironic/tests/unit/drivers/modules/ilo/test_raid.py342
-rw-r--r--ironic/tests/unit/drivers/modules/test_agent.py52
-rw-r--r--ironic/tests/unit/drivers/test_ilo.py45
-rw-r--r--ironic/tests/unit/drivers/third_party_driver_mocks.py2
-rw-r--r--releasenotes/notes/ilo5-oob-raid-a0eac60f7d77a4fc.yaml12
-rw-r--r--setup.cfg2
12 files changed, 787 insertions, 47 deletions
diff --git a/driver-requirements.txt b/driver-requirements.txt
index 5ee2943f0..8241d6250 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 54a3419c0..3f503beb3 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 4cf4a6317..3540c6944 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 020ebbc1f..c9344ab15 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 000000000..07d695133
--- /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 40ae9f400..004af870d 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 000000000..34b859023
--- /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 16c653c43..5e893a98c 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 321ace576..ed5359fa0 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 56c3a3878..03850a48e 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 000000000..4325cb09f
--- /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 128b75e5a..cfab34dd3 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