Add iDRAC RAID cleaning steps tests
This adds tests for both the WS-Man and Redfish implementations of the iDRAC RAID cleaning steps. In this test, storage inventory input for RAID cleaning is provided by user in JSON format. The storage inventory is validated against schema once at the beginning of RAID tests execution and each test checks test specific requirements before execution. Change-Id: I183fdc47dbb7a12b2ffb4f8453753dada2d8f5fe
This commit is contained in:
parent
c4a2e5e139
commit
42656a0d8d
@ -79,6 +79,9 @@ BaremetalGroup = [
|
||||
'publicURL', 'adminURL', 'internalURL'],
|
||||
help="The endpoint type to use for the baremetal provisioning"
|
||||
" service"),
|
||||
cfg.StrOpt('root_device_name',
|
||||
default='/dev/md0',
|
||||
help="Root device name to be used for node deployment"),
|
||||
cfg.IntOpt('deploywait_timeout',
|
||||
default=15,
|
||||
help="Timeout for Ironic node to reach the "
|
||||
@ -134,6 +137,8 @@ BaremetalGroup = [
|
||||
cfg.StrOpt('ramdisk_iso_image_ref',
|
||||
help=("UUID (or url) of an ISO image for the ramdisk boot "
|
||||
"tests.")),
|
||||
cfg.StrOpt('storage_inventory_file',
|
||||
help="Path to storage inventory file for RAID cleaning tests."),
|
||||
cfg.ListOpt('enabled_drivers',
|
||||
default=['fake', 'pxe_ipmitool', 'agent_ipmitool'],
|
||||
help="List of Ironic enabled drivers."),
|
||||
|
@ -23,3 +23,7 @@ class IntrospectionTimeout(exceptions.TempestException):
|
||||
|
||||
class HypervisorUpdateTimeout(exceptions.TempestException):
|
||||
message = "Hypervisor stats update time out"
|
||||
|
||||
|
||||
class RaidCleaningInventoryValidationFailed(exceptions.TempestException):
|
||||
message = "RAID cleaning storage inventory validation failed"
|
||||
|
@ -642,7 +642,8 @@ class BaremetalStandaloneScenarioTest(BaremetalStandaloneManager):
|
||||
self.assertTrue(self.ping_ip_address(self.node_ip,
|
||||
should_succeed=should_succeed))
|
||||
|
||||
def build_raid_and_verify_node(self, config=None, deploy_time=False):
|
||||
def build_raid_and_verify_node(self, config=None, deploy_time=False,
|
||||
erase_device_metadata=True):
|
||||
config = config or self.raid_config
|
||||
if deploy_time:
|
||||
steps = [
|
||||
@ -670,15 +671,15 @@ class BaremetalStandaloneScenarioTest(BaremetalStandaloneManager):
|
||||
"interface": "raid",
|
||||
"step": "delete_configuration"
|
||||
},
|
||||
{
|
||||
"interface": "deploy",
|
||||
"step": "erase_devices_metadata",
|
||||
},
|
||||
{
|
||||
"interface": "raid",
|
||||
"step": "create_configuration",
|
||||
}
|
||||
]
|
||||
if erase_device_metadata:
|
||||
steps.insert(1, {
|
||||
"interface": "deploy",
|
||||
"step": "erase_devices_metadata"})
|
||||
self.baremetal_client.set_node_raid_config(self.node['uuid'],
|
||||
config)
|
||||
self.manual_cleaning(self.node, clean_steps=steps)
|
||||
@ -686,12 +687,14 @@ class BaremetalStandaloneScenarioTest(BaremetalStandaloneManager):
|
||||
# The node has been changed, anything at this point, we need to back
|
||||
# out the raid configuration.
|
||||
if not deploy_time:
|
||||
self.addCleanup(self.remove_raid_configuration, self.node)
|
||||
self.addCleanup(self.remove_raid_configuration, self.node,
|
||||
erase_device_metadata=erase_device_metadata)
|
||||
|
||||
# NOTE(dtantsur): this is not required, but it allows us to check that
|
||||
# the RAID device was in fact created and is used for deployment.
|
||||
patch = [{'path': '/properties/root_device',
|
||||
'op': 'add', 'value': {'name': '/dev/md0'}}]
|
||||
'op': 'add', 'value': {
|
||||
'name': CONF.baremetal.root_device_name}}]
|
||||
if deploy_time:
|
||||
patch.append({'path': '/instance_info/traits',
|
||||
'op': 'add', 'value': ['CUSTOM_RAID']})
|
||||
@ -707,18 +710,18 @@ class BaremetalStandaloneScenarioTest(BaremetalStandaloneManager):
|
||||
'op': 'remove'}]
|
||||
self.update_node(self.node['uuid'], patch=patch)
|
||||
|
||||
def remove_raid_configuration(self, node):
|
||||
def remove_raid_configuration(self, node, erase_device_metadata=True):
|
||||
self.baremetal_client.set_node_raid_config(node['uuid'], {})
|
||||
steps = [
|
||||
{
|
||||
"interface": "raid",
|
||||
"step": "delete_configuration",
|
||||
},
|
||||
{
|
||||
"interface": "deploy",
|
||||
"step": "erase_devices_metadata",
|
||||
}
|
||||
]
|
||||
if erase_device_metadata:
|
||||
steps.append({
|
||||
"interface": "deploy",
|
||||
"step": "erase_devices_metadata"})
|
||||
self.manual_cleaning(node, clean_steps=steps)
|
||||
|
||||
def rescue_unrescue(self):
|
||||
|
@ -0,0 +1,101 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Storage inventory JSON schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"storage_inventory": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"controllers": {
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "The unique identifier for this storage controller.",
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"serial_number": {
|
||||
"description": "The serial number for this storage controller.",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"manufacturer": {
|
||||
"description": "The manufacturer of this storage controller.",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"model": {
|
||||
"description": "The model of the storage controller.",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"supported_device_protocols": {
|
||||
"description": "The protocols that the storage controller can use to communicate with attached devices.",
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["sas", "sata", "scsi"]
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"supported_raid_types": {
|
||||
"description": "The set of RAID types supported by the storage controller.",
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["JBOD", "0", "1", "2", "5", "6", "1+0", "5+0", "6+0"]
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"drives": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "The unique identifier for the physical drive.",
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"size_gb": {
|
||||
"description": "The size in GiB of the physical drive.",
|
||||
"type": ["number", "null"],
|
||||
"minimum": 0
|
||||
},
|
||||
"model": {
|
||||
"description": "The model for the physical drive.",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"media_type": {
|
||||
"description": "The media type for the physical drive.",
|
||||
"enum": ["hdd", "ssd", null]
|
||||
},
|
||||
"serial_number": {
|
||||
"description": "The serial number for the physical drive.",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"protocol": {
|
||||
"description": "The protocol that this drive currently uses to communicate to the storage controller.",
|
||||
"enum": ["sas", "sata", "scsi", null]
|
||||
}
|
||||
},
|
||||
"required": ["id", "size_gb", "model", "media_type", "serial_number", "protocol"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["id", "serial_number", "manufacturer", "model", "supported_device_protocols", "supported_raid_types"],
|
||||
"additionalProperties": false,
|
||||
"dependencies": {
|
||||
"drives": ["id"]
|
||||
}
|
||||
},
|
||||
"minItems": 1
|
||||
}
|
||||
},
|
||||
"required": ["controllers"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["storage_inventory"],
|
||||
"additionalProperties": false
|
||||
}
|
@ -15,11 +15,17 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import jsonschema
|
||||
from jsonschema import exceptions as json_schema_exc
|
||||
from oslo_log import log as logging
|
||||
from tempest.common import utils
|
||||
from tempest import config
|
||||
from tempest.lib import decorators
|
||||
|
||||
from ironic_tempest_plugin import exceptions
|
||||
from ironic_tempest_plugin.tests.scenario import \
|
||||
baremetal_standalone_manager as bsm
|
||||
|
||||
@ -227,3 +233,122 @@ class BaremetalIdracWSManManagementCleaning(
|
||||
|
||||
management_interface = 'idrac-wsman'
|
||||
power_interface = 'idrac-wsman'
|
||||
|
||||
|
||||
class BaremetalIdracRaidCleaning(bsm.BaremetalStandaloneScenarioTest):
|
||||
|
||||
mandatory_attr = ['driver', 'raid_interface']
|
||||
image_ref = CONF.baremetal.whole_disk_image_ref
|
||||
wholedisk_image = True
|
||||
storage_inventory_info = None
|
||||
driver = 'idrac'
|
||||
api_microversion = '1.31' # to set raid_interface
|
||||
delete_node = False
|
||||
|
||||
@classmethod
|
||||
def skip_checks(cls):
|
||||
"""Validates the storage information passed in file using JSON schema.
|
||||
|
||||
:raises: skipException if,
|
||||
1) storage inventory path is not provided in tempest execution
|
||||
file.
|
||||
2) storage inventory file is not found on given path.
|
||||
:raises: RaidCleaningInventoryValidationFailed if,
|
||||
validation of the storage inventory fails.
|
||||
"""
|
||||
super(BaremetalIdracRaidCleaning, cls).skip_checks()
|
||||
storage_inventory = CONF.baremetal.storage_inventory_file
|
||||
if not storage_inventory:
|
||||
raise cls.skipException("Storage inventory file path missing "
|
||||
"in tempest configuration file. "
|
||||
"Skipping Test case.")
|
||||
try:
|
||||
with open(storage_inventory, 'r') as storage_invent_fobj:
|
||||
cls.storage_inventory_info = json.load(storage_invent_fobj)
|
||||
except IOError:
|
||||
msg = ("Storage Inventory file %(inventory)s is not found. "
|
||||
"Skipping Test Case." %
|
||||
{'inventory': storage_inventory})
|
||||
raise cls.skipException(msg)
|
||||
storage_inventory_schema = os.path.join(os.path.dirname(
|
||||
__file__), 'storage_inventory_schema.json')
|
||||
with open(storage_inventory_schema, 'r') as storage_schema_fobj:
|
||||
schema = json.load(storage_schema_fobj)
|
||||
try:
|
||||
jsonschema.validate(cls.storage_inventory_info, schema)
|
||||
except json_schema_exc.ValidationError as e:
|
||||
error_msg = ("Storage Inventory validation error: %(error)s " %
|
||||
{'error': e})
|
||||
raise exceptions.RaidCleaningInventoryValidationFailed(error_msg)
|
||||
|
||||
def _validate_raid_type_and_drives_count(self, raid_type,
|
||||
minimum_drives_required):
|
||||
for controller in (self.storage_inventory_info[
|
||||
'storage_inventory']['controllers']):
|
||||
supported_raid_types = controller['supported_raid_types']
|
||||
physical_disks = [pdisk['id'] for pdisk in (
|
||||
controller['drives'])]
|
||||
if raid_type in supported_raid_types and (
|
||||
minimum_drives_required <= len(physical_disks)):
|
||||
return controller
|
||||
error_msg = ("No Controller present in storage inventory which "
|
||||
"supports RAID type %(raid_type)s "
|
||||
"and has at least %(disk_count)s drives." %
|
||||
{'raid_type': raid_type,
|
||||
'disk_count': minimum_drives_required})
|
||||
raise exceptions.RaidCleaningInventoryValidationFailed(error_msg)
|
||||
|
||||
@decorators.idempotent_id('8a908a3c-f2af-48fb-8553-9163715aa403')
|
||||
@utils.services('image', 'network')
|
||||
def test_hardware_raid(self):
|
||||
controller = self._validate_raid_type_and_drives_count(
|
||||
raid_type='1', minimum_drives_required=2)
|
||||
raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": 40,
|
||||
"raid_level": "1",
|
||||
"controller": controller['id']
|
||||
}
|
||||
]
|
||||
}
|
||||
self.build_raid_and_verify_node(
|
||||
config=raid_config,
|
||||
deploy_time=CONF.baremetal_feature_enabled.deploy_time_raid,
|
||||
erase_device_metadata=False)
|
||||
self.remove_root_device_hint()
|
||||
self.terminate_node(self.node['uuid'], force_delete=True)
|
||||
|
||||
@decorators.idempotent_id('92fe534d-77f1-422d-84e4-e30fe9e3d928')
|
||||
@utils.services('image', 'network')
|
||||
def test_raid_cleaning_max_size_raid_10(self):
|
||||
controller = self._validate_raid_type_and_drives_count(
|
||||
raid_type='1+0', minimum_drives_required=4)
|
||||
physical_disks = [pdisk['id'] for pdisk in (
|
||||
controller['drives'])]
|
||||
raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": "MAX",
|
||||
"raid_level": "1+0",
|
||||
"controller": controller['id'],
|
||||
"physical_disks": physical_disks
|
||||
}
|
||||
]
|
||||
}
|
||||
self.build_raid_and_verify_node(
|
||||
config=raid_config,
|
||||
deploy_time=CONF.baremetal_feature_enabled.deploy_time_raid,
|
||||
erase_device_metadata=False)
|
||||
self.remove_root_device_hint()
|
||||
self.terminate_node(self.node['uuid'], force_delete=True)
|
||||
|
||||
|
||||
class BaremetalIdracRedfishRaidCleaning(
|
||||
BaremetalIdracRaidCleaning):
|
||||
raid_interface = 'idrac-redfish'
|
||||
|
||||
|
||||
class BaremetalIdracWSManRaidCleaning(
|
||||
BaremetalIdracRaidCleaning):
|
||||
raid_interface = 'idrac-wsman'
|
||||
|
Loading…
Reference in New Issue
Block a user