# # Copyright 2017 Mirantis Inc. # # Copyright (c) 2022 Dell Inc. or its subsidiaries. # # 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. 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 LOG = logging.getLogger(__name__) CONF = config.CONF class BaremetalCleaningAgentIpmitoolWholedisk( bsm.BaremetalStandaloneScenarioTest): driver = 'agent_ipmitool' image_ref = CONF.baremetal.whole_disk_image_ref wholedisk_image = True delete_node = False api_microversion = '1.28' @decorators.idempotent_id('0d82cedd-9697-4cf7-8e4a-80d510f53615') @utils.services('image', 'network') def test_manual_cleaning(self): self.check_manual_partition_cleaning(self.node) class BaremetalCleaningPxeIpmitoolWholedisk( bsm.BaremetalStandaloneScenarioTest): driver = 'pxe_ipmitool' image_ref = CONF.baremetal.whole_disk_image_ref wholedisk_image = True delete_node = False api_microversion = '1.28' @decorators.idempotent_id('fb03abfa-cdfc-41ec-aaa8-c70402786a85') @utils.services('image', 'network') def test_manual_cleaning(self): self.check_manual_partition_cleaning(self.node) class BaremetalCleaningIpmiWholedisk( bsm.BaremetalStandaloneScenarioTest): driver = 'ipmi' image_ref = CONF.baremetal.whole_disk_image_ref wholedisk_image = True delete_node = False deploy_interface = 'iscsi' api_microversion = '1.31' @classmethod def skip_checks(cls): super(BaremetalCleaningIpmiWholedisk, cls).skip_checks() if CONF.baremetal_feature_enabled.software_raid: raise cls.skipException("Cleaning is covered in the RAID test") @decorators.idempotent_id('065238db-1b6d-4d75-a9da-c240f8cbd956') @utils.services('image', 'network') def test_manual_cleaning(self): self.check_manual_partition_cleaning(self.node) class SoftwareRaidIscsi(bsm.BaremetalStandaloneScenarioTest): if 'redfish' in CONF.baremetal.enabled_hardware_types: driver = 'redfish' else: driver = 'ipmi' image_ref = CONF.baremetal.whole_disk_image_ref wholedisk_image = True deploy_interface = 'iscsi' raid_interface = 'agent' api_microversion = '1.55' # Software RAID is always local boot boot_option = 'local' delete_node = False raid_config = { "logical_disks": [ { "size_gb": "MAX", "raid_level": "1", "controller": "software" }, ] } @classmethod def skip_checks(cls): super(SoftwareRaidIscsi, cls).skip_checks() if cls.driver == 'ipmi': raise cls.skipException("Testing with redfish driver") if not CONF.baremetal_feature_enabled.software_raid: raise cls.skipException("Software RAID feature is not enabled") @decorators.idempotent_id('7ecba4f7-98b8-4ea1-b95e-3ec399f46798') @utils.services('image', 'network') def test_software_raid(self): self.build_raid_and_verify_node( deploy_time=CONF.baremetal_feature_enabled.deploy_time_raid) # NOTE(TheJulia): tearing down/terminating the instance does not # remove the root device hint, so it is best for us to go ahead # and remove it before exiting the test. self.remove_root_device_hint() self.terminate_node(self.node['uuid'], force_delete=True) class SoftwareRaidDirect(bsm.BaremetalStandaloneScenarioTest): if 'redfish' in CONF.baremetal.enabled_hardware_types: driver = 'redfish' else: driver = 'ipmi' image_ref = CONF.baremetal.whole_disk_image_ref wholedisk_image = True deploy_interface = 'direct' raid_interface = 'agent' api_microversion = '1.55' # Software RAID is always local boot boot_option = 'local' delete_node = False # TODO(dtantsur): more complex layout in this job raid_config = { "logical_disks": [ { "size_gb": "MAX", "raid_level": "1", "controller": "software" }, ] } @classmethod def skip_checks(cls): super(SoftwareRaidDirect, cls).skip_checks() if cls.driver == 'redfish': raise cls.skipException("Testing with ipmi driver") if not CONF.baremetal_feature_enabled.software_raid: raise cls.skipException("Software RAID feature is not enabled") @decorators.idempotent_id('125361ac-0eb3-4d79-8be2-a91936aa3f46') @utils.services('image', 'network') def test_software_raid(self): self.build_raid_and_verify_node( deploy_time=CONF.baremetal_feature_enabled.deploy_time_raid) # NOTE(TheJulia): tearing down/terminating the instance does not # remove the root device hint, so it is best for us to go ahead # and remove it before exiting the test. self.remove_root_device_hint() self.terminate_node(self.node['uuid'], force_delete=True) class BaremetalIdracManagementCleaning( bsm.BaremetalStandaloneScenarioTest): mandatory_attr = ['driver', 'management_interface', 'power_interface'] driver = 'idrac' delete_node = False # Minimum version for manual cleaning is 1.15 (# v1.15: Add ability to # do manual cleaning of nodes). The test cases clean up at the end by # detaching the VIF. Support for VIFs was introduced by version 1.28 # (# v1.28: Add vifs subcontroller to node). api_microversion = '1.28' @decorators.idempotent_id('d085ff72-abef-4931-a5b0-06efd5f9a037') def test_reset_idrac(self): clean_steps = [ { "interface": "management", "step": "reset_idrac" } ] self.manual_cleaning(self.node, clean_steps=clean_steps) @decorators.idempotent_id('9252ec6f-6b5b-447e-a323-c52775b88b4e') def test_clear_job_queue(self): clean_steps = [ { "interface": "management", "step": "clear_job_queue" } ] self.manual_cleaning(self.node, clean_steps=clean_steps) @decorators.idempotent_id('7baeff52-7d6e-4dea-a48f-a85a6bfc9f62') def test_known_good_state(self): clean_steps = [ { "interface": "management", "step": "known_good_state" } ] self.manual_cleaning(self.node, clean_steps=clean_steps) class BaremetalIdracRedfishManagementCleaning( BaremetalIdracManagementCleaning): management_interface = 'idrac-redfish' power_interface = 'idrac-redfish' class BaremetalIdracWSManManagementCleaning( BaremetalIdracManagementCleaning): 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' class BaremetalRedfishFirmwareUpdate(bsm.BaremetalStandaloneScenarioTest): api_microversion = '1.68' # to support redfish firmware update driver = 'redfish' delete_node = False image_ref = CONF.baremetal.whole_disk_image_ref wholedisk_image = True @classmethod def skip_checks(cls): super(BaremetalRedfishFirmwareUpdate, cls).skip_checks() if not CONF.baremetal.firmware_image_url: raise cls.skipException("Firmware image URL is not " "provided. Skipping test case.") if not CONF.baremetal.firmware_image_checksum: raise cls.skipException("Firmware image SHA1 checksum is not " "provided. Skipping test case.") def _firmware_update(self, fw_image_url, fw_image_checksum): steps = [ { "interface": "management", "step": "update_firmware", "args": { "firmware_images": [ { "url": fw_image_url, "checksum": fw_image_checksum, "wait": 300 } ] } } ] self.manual_cleaning(self.node, clean_steps=steps) @utils.services('network') @decorators.idempotent_id('360e0c0e-3c17-4d2e-b052-55a932c1a4c7') def test_firmware_update(self): # WARNING: Removing power from a server while it is in the process of # updating firmware may result in devices in the server, or the server # itself becoming inoperable. # Execution of firmware test case needs careful execution as it # changes state of server, may result in break down of server on # interruption. As it deals with firmware of component, make sure # to provide proper image url path while testing with correct SHA1 # checksum of image. self._firmware_update(CONF.baremetal.firmware_image_url, CONF.baremetal.firmware_image_checksum) if (CONF.baremetal.firmware_rollback_image_url and CONF.baremetal.firmware_rollback_image_checksum): self.addCleanup(self._firmware_update, CONF.baremetal.firmware_rollback_image_url, CONF.baremetal.firmware_rollback_image_checksum) class BaremetalIdracRedfishFirmwareUpdate(BaremetalRedfishFirmwareUpdate): driver = 'idrac' boot_interface = 'ipxe' management_interface = 'idrac-redfish' power_interface = 'idrac-redfish' class BaremetalIdracRedfishConfiguartionMolds( bsm.BaremetalStandaloneScenarioTest): api_microversion = '1.72' # to support configuration molds functionality delete_node = False image_ref = CONF.baremetal.whole_disk_image_ref driver = 'idrac' boot_interface = 'ipxe' management_interface = 'idrac-redfish' power_interface = 'idrac-redfish' wholedisk_image = True @utils.services('network') @decorators.idempotent_id('69386cf6-bbf2-451e-b927-f68c66075f02') def test_configuration_molds_export(self): if not CONF.baremetal.export_location: raise self.skipException("Export configuration location path " "is not provided. Skipping test case. " "Make sure to provide correct " "configuration path in which " "configuration would be exported. " "In case of Swift object storage, " "make to sure to provide proper " "container path.") steps = [ { "interface": "management", "step": "export_configuration", "args": { "export_configuration_location": CONF.baremetal.export_location } } ] self.manual_cleaning(self.node, clean_steps=steps) @utils.services('network') @decorators.idempotent_id('cad15719-c293-4338-8fa9-986ef02b4682') def test_configuration_molds_import(self): if not CONF.baremetal.import_location: raise self.skipException("Import configuration JSON file location " "is not provided. Make sure to provide " "correct configuration file with proper " "configuration which needs to be tested " "during execution of this test. " "Skipping test case.") steps = [ { "interface": "management", "step": "import_configuration", "args": { "import_configuration_location": CONF.baremetal.import_location } } ] self.manual_cleaning(self.node, clean_steps=steps) if CONF.baremetal.rollback_import_location: rollback_steps = [ { "interface": "management", "step": "import_configuration", "args": { "import_configuration_location": CONF.baremetal.rollback_import_location } } ] self.addCleanup(self.manual_cleaning, self.node, clean_steps=rollback_steps)