diff --git a/ironic_tempest_plugin/config.py b/ironic_tempest_plugin/config.py index dc709b6f..d5cdcfa1 100644 --- a/ironic_tempest_plugin/config.py +++ b/ironic_tempest_plugin/config.py @@ -132,6 +132,9 @@ BaremetalGroup = [ cfg.ListOpt('enabled_boot_interfaces', default=['fake', 'pxe'], help="List of Ironic enabled boot interfaces."), + cfg.ListOpt('enabled_raid_interfaces', + default=['no-raid', 'agent'], + help="List of Ironic enabled RAID interfaces."), cfg.StrOpt('default_rescue_interface', help="Ironic default rescue interface."), cfg.IntOpt('adjusted_root_disk_size_gb', @@ -152,6 +155,11 @@ BaremetalFeaturesGroup = [ # requires the plugin to be able to read ipmi_password. default=False, help="Defines if adoption is enabled"), + cfg.BoolOpt('software_raid', + default=False, + help="Defines if software RAID is enabled (available " + "starting with Train). Requires at least two disks " + "on testing nodes."), ] BaremetalIntrospectionGroup = [ diff --git a/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py b/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py index 8d5fec5c..9f001b80 100644 --- a/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py +++ b/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py @@ -509,6 +509,7 @@ class BaremetalClient(base.BaremetalClient): 'driver', 'bios_interface', 'deploy_interface', + 'raid_interface', 'rescue_interface', 'instance_uuid', 'resource_class', diff --git a/ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py b/ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py index 4cf500d4..bbd77829 100644 --- a/ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py +++ b/ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py @@ -424,6 +424,12 @@ class BaremetalStandaloneScenarioTest(BaremetalStandaloneManager): # set via a different test). boot_interface = None + # The raid interface to use by the HW type. The raid interface of the + # node used in the test will be set to this value. If set to None, the + # node will retain its existing raid_interface value (which may have been + # set via a different test). + raid_interface = None + # Boolean value specify if image is wholedisk or not. wholedisk_image = None @@ -476,6 +482,13 @@ class BaremetalStandaloneScenarioTest(BaremetalStandaloneManager): "in the list of enabled boot interfaces %(enabled)s" % { 'iface': cls.boot_interface, 'enabled': CONF.baremetal.enabled_boot_interfaces}) + if (cls.raid_interface and cls.raid_interface not in + CONF.baremetal.enabled_raid_interfaces): + raise cls.skipException( + "RAID interface %(iface)s required by test is not " + "in the list of enabled RAID interfaces %(enabled)s" % { + 'iface': cls.raid_interface, + 'enabled': CONF.baremetal.enabled_raid_interfaces}) if not cls.wholedisk_image and CONF.baremetal.use_provision_network: raise cls.skipException( 'Partitioned images are not supported with multitenancy.') @@ -512,6 +525,8 @@ class BaremetalStandaloneScenarioTest(BaremetalStandaloneManager): boot_kwargs['rescue_interface'] = cls.rescue_interface if cls.boot_interface: boot_kwargs['boot_interface'] = cls.boot_interface + if cls.raid_interface: + boot_kwargs['raid_interface'] = cls.raid_interface # just get an available node cls.node = cls.get_and_reserve_node() @@ -540,3 +555,34 @@ class BaremetalStandaloneScenarioTest(BaremetalStandaloneManager): self.set_node_to_active(image_ref, image_checksum) self.assertTrue(self.ping_ip_address(self.node_ip, should_succeed=should_succeed)) + + def build_raid_and_verify_node(self, config=None, clean_steps=None): + config = config or self.raid_config + clean_steps = clean_steps or [ + { + "interface": "raid", + "step": "delete_configuration" + }, + # NOTE(dtantsur): software RAID building fails if any + # partitions exist on holder devices. + { + "interface": "deploy", + "step": "erase_devices_metadata" + }, + { + "interface": "raid", + "step": "create_configuration" + } + ] + + self.baremetal_client.set_node_raid_config(self.node['uuid'], config) + self.manual_cleaning(self.node, clean_steps=clean_steps) + + # 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'}}] + self.update_node(self.node['uuid'], patch=patch) + # NOTE(dtantsur): apparently cirros cannot boot from md devices :( + # So we only move the node to active (verifying deployment). + self.set_node_to_active() diff --git a/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_cleaning.py b/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_cleaning.py index e30b9efc..7df98ce3 100644 --- a/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_cleaning.py +++ b/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_cleaning.py @@ -69,3 +69,66 @@ class BaremetalCleaningIpmiWholedisk( @utils.services('image', 'network') def test_manual_cleaning(self): self.check_manual_partition_cleaning(self.node) + + +class SoftwareRaidIscsi(bsm.BaremetalStandaloneScenarioTest): + + driver = 'ipmi' + image_ref = CONF.baremetal.whole_disk_image_ref + wholedisk_image = True + deploy_interface = 'iscsi' + raid_interface = 'agent' + api_microversion = '1.31' + + raid_config = { + "logical_disks": [ + { + "size_gb": "MAX", + "raid_level": "1", + "controller": "software" + }, + ] + } + + @classmethod + def skip_checks(cls): + super(SoftwareRaidIscsi, cls).skip_checks() + 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() + + +class SoftwareRaidDirect(bsm.BaremetalStandaloneScenarioTest): + + driver = 'ipmi' + image_ref = CONF.baremetal.whole_disk_image_ref + wholedisk_image = True + deploy_interface = 'direct' + raid_interface = 'agent' + api_microversion = '1.31' + + # 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 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()