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
This commit is contained in:
parent
565cd192fc
commit
58a395bf83
@ -4,7 +4,7 @@
|
|||||||
# python projects they should package as optional dependencies for Ironic.
|
# python projects they should package as optional dependencies for Ironic.
|
||||||
|
|
||||||
# These are available on pypi
|
# These are available on pypi
|
||||||
proliantutils>=2.6.0
|
proliantutils>=2.7.0
|
||||||
pysnmp>=4.3.0,<5.0.0
|
pysnmp>=4.3.0,<5.0.0
|
||||||
python-ironic-inspector-client>=1.5.0
|
python-ironic-inspector-client>=1.5.0
|
||||||
python-scciclient>=0.8.0
|
python-scciclient>=0.8.0
|
||||||
|
@ -126,3 +126,51 @@ def update_raid_info(node, raid_config):
|
|||||||
node.properties = properties
|
node.properties = properties
|
||||||
|
|
||||||
node.save()
|
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
|
||||||
|
@ -22,6 +22,7 @@ from ironic.drivers.modules.ilo import console
|
|||||||
from ironic.drivers.modules.ilo import inspect
|
from ironic.drivers.modules.ilo import inspect
|
||||||
from ironic.drivers.modules.ilo import management
|
from ironic.drivers.modules.ilo import management
|
||||||
from ironic.drivers.modules.ilo import power
|
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.ilo import vendor
|
||||||
from ironic.drivers.modules import inspector
|
from ironic.drivers.modules import inspector
|
||||||
from ironic.drivers.modules import noop
|
from ironic.drivers.modules import noop
|
||||||
@ -69,3 +70,15 @@ class IloHardware(generic.GenericHardware):
|
|||||||
def supported_vendor_interfaces(self):
|
def supported_vendor_interfaces(self):
|
||||||
"""List of supported power interfaces."""
|
"""List of supported power interfaces."""
|
||||||
return [vendor.VendorPassthru, noop.NoVendor]
|
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]
|
||||||
|
@ -698,32 +698,10 @@ class AgentRAID(base.RAIDInterface):
|
|||||||
'create_nonroot_volumes': create_nonroot_volumes,
|
'create_nonroot_volumes': create_nonroot_volumes,
|
||||||
'target_raid_config': node.target_raid_config})
|
'target_raid_config': node.target_raid_config})
|
||||||
|
|
||||||
if not node.target_raid_config:
|
target_raid_config = raid.filter_target_raid_config(
|
||||||
raise exception.MissingParameterValue(
|
node,
|
||||||
_("Node %s has no target RAID configuration.") % node.uuid)
|
create_root_volume=create_root_volume,
|
||||||
|
create_nonroot_volumes=create_nonroot_volumes)
|
||||||
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})
|
|
||||||
|
|
||||||
# Rewrite it back to the node object, but no need to save it as
|
# Rewrite it back to the node object, but no need to save it as
|
||||||
# we need to just send this to the agent ramdisk.
|
# we need to just send this to the agent ramdisk.
|
||||||
node.driver_internal_info['target_raid_config'] = target_raid_config
|
node.driver_internal_info['target_raid_config'] = target_raid_config
|
||||||
|
235
ironic/drivers/modules/ilo/raid.py
Normal file
235
ironic/drivers/modules/ilo/raid.py
Normal file
@ -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)
|
@ -161,6 +161,21 @@ class ValidateRaidConfigurationTestCase(base.TestCase):
|
|||||||
|
|
||||||
class RaidPublicMethodsTestCase(db_base.DbTestCase):
|
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):
|
def test_get_logical_disk_properties(self):
|
||||||
with open(drivers_base.RAID_CONFIG_SCHEMA, 'r') as raid_schema_fobj:
|
with open(drivers_base.RAID_CONFIG_SCHEMA, 'r') as raid_schema_fobj:
|
||||||
schema = json.load(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,
|
def _test_update_raid_info(self, current_config,
|
||||||
capabilities=None):
|
capabilities=None):
|
||||||
node = obj_utils.create_test_node(self.context)
|
node = self.node
|
||||||
if capabilities:
|
if capabilities:
|
||||||
properties = node.properties
|
properties = node.properties
|
||||||
properties['capabilities'] = capabilities
|
properties['capabilities'] = capabilities
|
||||||
@ -239,3 +254,37 @@ class RaidPublicMethodsTestCase(db_base.DbTestCase):
|
|||||||
self.assertRaises(exception.InvalidParameterValue,
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
self._test_update_raid_info,
|
self._test_update_raid_info,
|
||||||
current_config)
|
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)
|
||||||
|
342
ironic/tests/unit/drivers/modules/ilo/test_raid.py
Normal file
342
ironic/tests/unit/drivers/modules/ilo/test_raid.py
Normal file
@ -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)
|
@ -1406,12 +1406,15 @@ class AgentRAIDTestCase(db_base.DbTestCase):
|
|||||||
self.assertEqual(0, ret[0]['priority'])
|
self.assertEqual(0, ret[0]['priority'])
|
||||||
self.assertEqual(0, ret[1]['priority'])
|
self.assertEqual(0, ret[1]['priority'])
|
||||||
|
|
||||||
|
@mock.patch.object(raid, 'filter_target_raid_config')
|
||||||
@mock.patch.object(deploy_utils, 'agent_execute_clean_step',
|
@mock.patch.object(deploy_utils, 'agent_execute_clean_step',
|
||||||
autospec=True)
|
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:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
execute_mock.return_value = states.CLEANWAIT
|
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)
|
return_value = task.driver.raid.create_configuration(task)
|
||||||
|
|
||||||
self.assertEqual(states.CLEANWAIT, return_value)
|
self.assertEqual(states.CLEANWAIT, return_value)
|
||||||
@ -1420,65 +1423,76 @@ class AgentRAIDTestCase(db_base.DbTestCase):
|
|||||||
task.node.driver_internal_info['target_raid_config'])
|
task.node.driver_internal_info['target_raid_config'])
|
||||||
execute_mock.assert_called_once_with(task, self.clean_step)
|
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',
|
@mock.patch.object(deploy_utils, 'agent_execute_clean_step',
|
||||||
autospec=True)
|
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:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
execute_mock.return_value = states.CLEANWAIT
|
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 = {
|
exp_target_raid_config = {
|
||||||
"logical_disks": [
|
"logical_disks": [
|
||||||
{'size_gb': 200, 'raid_level': 5}
|
{'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(
|
self.assertEqual(
|
||||||
exp_target_raid_config,
|
exp_target_raid_config,
|
||||||
task.node.driver_internal_info['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',
|
@mock.patch.object(deploy_utils, 'agent_execute_clean_step',
|
||||||
autospec=True)
|
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:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
execute_mock.return_value = states.CLEANWAIT
|
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 = {
|
exp_target_raid_config = {
|
||||||
"logical_disks": [
|
"logical_disks": [
|
||||||
{'size_gb': 200, 'raid_level': 0, 'is_root_volume': True},
|
{'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(
|
self.assertEqual(
|
||||||
exp_target_raid_config,
|
exp_target_raid_config,
|
||||||
task.node.driver_internal_info['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',
|
@mock.patch.object(deploy_utils, 'agent_execute_clean_step',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
def test_create_configuration_no_target_raid_config_after_skipping(
|
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:
|
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(
|
self.assertRaises(
|
||||||
exception.MissingParameterValue,
|
exception.MissingParameterValue,
|
||||||
task.driver.raid.create_configuration,
|
task.driver.raid.create_configuration,
|
||||||
task, create_root_volume=False,
|
task, create_root_volume=False,
|
||||||
create_nonroot_volumes=False)
|
create_nonroot_volumes=False)
|
||||||
|
|
||||||
self.assertFalse(execute_mock.called)
|
self.assertFalse(execute_mock.called)
|
||||||
|
|
||||||
|
@mock.patch.object(raid, 'filter_target_raid_config')
|
||||||
@mock.patch.object(deploy_utils, 'agent_execute_clean_step',
|
@mock.patch.object(deploy_utils, 'agent_execute_clean_step',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
def test_create_configuration_empty_target_raid_config(
|
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
|
execute_mock.return_value = states.CLEANING
|
||||||
self.node.target_raid_config = {}
|
self.node.target_raid_config = {}
|
||||||
self.node.save()
|
self.node.save()
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
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,
|
self.assertRaises(exception.MissingParameterValue,
|
||||||
task.driver.raid.create_configuration,
|
task.driver.raid.create_configuration,
|
||||||
task)
|
task)
|
||||||
|
@ -19,6 +19,7 @@ Test class for iLO Drivers
|
|||||||
from ironic.conductor import task_manager
|
from ironic.conductor import task_manager
|
||||||
from ironic.drivers import ilo
|
from ironic.drivers import ilo
|
||||||
from ironic.drivers.modules import agent
|
from ironic.drivers.modules import agent
|
||||||
|
from ironic.drivers.modules.ilo import raid
|
||||||
from ironic.drivers.modules import inspector
|
from ironic.drivers.modules import inspector
|
||||||
from ironic.drivers.modules import iscsi_deploy
|
from ironic.drivers.modules import iscsi_deploy
|
||||||
from ironic.drivers.modules import noop
|
from ironic.drivers.modules import noop
|
||||||
@ -165,3 +166,47 @@ class IloHardwareTestCase(db_base.DbTestCase):
|
|||||||
agent.AgentDeploy)
|
agent.AgentDeploy)
|
||||||
self.assertIsInstance(task.driver.raid,
|
self.assertIsInstance(task.driver.raid,
|
||||||
agent.AgentRAID)
|
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)
|
||||||
|
@ -56,6 +56,8 @@ if not proliantutils:
|
|||||||
sys.modules['proliantutils.utils'] = proliantutils.utils
|
sys.modules['proliantutils.utils'] = proliantutils.utils
|
||||||
proliantutils.utils.process_firmware_image = mock.MagicMock()
|
proliantutils.utils.process_firmware_image = mock.MagicMock()
|
||||||
proliantutils.exception.IloError = type('IloError', (Exception,), {})
|
proliantutils.exception.IloError = type('IloError', (Exception,), {})
|
||||||
|
proliantutils.exception.IloLogicalDriveNotFoundError = (
|
||||||
|
type('IloLogicalDriveNotFoundError', (Exception,), {}))
|
||||||
command_exception = type('IloCommandNotSupportedError', (Exception,), {})
|
command_exception = type('IloCommandNotSupportedError', (Exception,), {})
|
||||||
proliantutils.exception.IloCommandNotSupportedError = command_exception
|
proliantutils.exception.IloCommandNotSupportedError = command_exception
|
||||||
proliantutils.exception.IloCommandNotSupportedInBiosError = type(
|
proliantutils.exception.IloCommandNotSupportedInBiosError = type(
|
||||||
|
12
releasenotes/notes/ilo5-oob-raid-a0eac60f7d77a4fc.yaml
Normal file
12
releasenotes/notes/ilo5-oob-raid-a0eac60f7d77a4fc.yaml
Normal file
@ -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'.
|
@ -125,6 +125,7 @@ ironic.hardware.interfaces.raid =
|
|||||||
agent = ironic.drivers.modules.agent:AgentRAID
|
agent = ironic.drivers.modules.agent:AgentRAID
|
||||||
fake = ironic.drivers.modules.fake:FakeRAID
|
fake = ironic.drivers.modules.fake:FakeRAID
|
||||||
idrac = ironic.drivers.modules.drac.raid:DracRAID
|
idrac = ironic.drivers.modules.drac.raid:DracRAID
|
||||||
|
ilo5 = ironic.drivers.modules.ilo.raid:Ilo5RAID
|
||||||
irmc = ironic.drivers.modules.irmc.raid:IRMCRAID
|
irmc = ironic.drivers.modules.irmc.raid:IRMCRAID
|
||||||
no-raid = ironic.drivers.modules.noop:NoRAID
|
no-raid = ironic.drivers.modules.noop:NoRAID
|
||||||
|
|
||||||
@ -152,6 +153,7 @@ ironic.hardware.types =
|
|||||||
fake-hardware = ironic.drivers.fake_hardware:FakeHardware
|
fake-hardware = ironic.drivers.fake_hardware:FakeHardware
|
||||||
idrac = ironic.drivers.drac:IDRACHardware
|
idrac = ironic.drivers.drac:IDRACHardware
|
||||||
ilo = ironic.drivers.ilo:IloHardware
|
ilo = ironic.drivers.ilo:IloHardware
|
||||||
|
ilo5 = ironic.drivers.ilo:Ilo5Hardware
|
||||||
ipmi = ironic.drivers.ipmi:IPMIHardware
|
ipmi = ironic.drivers.ipmi:IPMIHardware
|
||||||
irmc = ironic.drivers.irmc:IRMCHardware
|
irmc = ironic.drivers.irmc:IRMCHardware
|
||||||
manual-management = ironic.drivers.generic:ManualManagementHardware
|
manual-management = ironic.drivers.generic:ManualManagementHardware
|
||||||
|
Loading…
Reference in New Issue
Block a user