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:
Kamlesh Chauvhan 2022-05-12 14:11:14 +00:00 committed by kamlesh chauvhan
parent c4a2e5e139
commit 42656a0d8d
5 changed files with 250 additions and 12 deletions

View File

@ -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."),

View File

@ -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"

View File

@ -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):

View File

@ -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
}

View File

@ -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'