diff --git a/test/functional/api/cas/cli_messages.py b/test/functional/api/cas/cli_messages.py index 1ba8d87..55fac85 100644 --- a/test/functional/api/cas/cli_messages.py +++ b/test/functional/api/cas/cli_messages.py @@ -63,6 +63,13 @@ load_and_force = [ r"Use of \'load\' and \'force\' simultaneously is forbidden\." ] +try_add_core_sector_size_mismatch = [ + r"Error while adding core device to cache instance \d+", + r"Cache device logical sector size is greater than core device logical sector size\.", + r"Consider changing logical sector size on current cache device", + r"or try other device with the same logical sector size as core device\." +] + def check_stderr_msg(output: Output, expected_messages): return __check_string_msg(output.stderr, expected_messages) diff --git a/test/functional/test-framework b/test/functional/test-framework index 7b8fb2b..0156c9f 160000 --- a/test/functional/test-framework +++ b/test/functional/test-framework @@ -1 +1 @@ -Subproject commit 7b8fb2ba6a3a1a293152f0dfef6568e103c86be4 +Subproject commit 0156c9ff20db765c11f547a042a2146b3ce06ddc diff --git a/test/functional/tests/misc/__init__.py b/test/functional/tests/misc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/functional/tests/misc/test_device_capabilities.py b/test/functional/tests/misc/test_device_capabilities.py new file mode 100644 index 0000000..b627b50 --- /dev/null +++ b/test/functional/tests/misc/test_device_capabilities.py @@ -0,0 +1,191 @@ +# +# Copyright(c) 2020 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause-Clear +# + +import math +import pytest +import os +from api.cas import casadm, cli_messages +from core.test_run import TestRun +from storage_devices.disk import DiskTypeSet, DiskType, DiskTypeLowerThan +from storage_devices.partition import Partition +from test_tools import disk_utils, fs_utils +from test_utils.output import CmdException +from test_utils.size import Size, Unit + + +@pytest.mark.require_disk("cache", DiskTypeSet([DiskType.optane, DiskType.nand])) +@pytest.mark.require_disk("core", DiskTypeLowerThan("cache")) +@pytest.mark.require_plugin("scsi_debug") +def test_device_capabilities(): + """ + title: Test whether CAS device capabilities are properly set. + description: | + Test if CAS device takes into consideration differences between devices which are used to + create it. + pass_criteria: + - CAS device starts successfully using differently configured devices. + - CAS device capabilities are as expected. + """ + + core_device = TestRun.disks['core'] + max_io_size_path = os.path.join(disk_utils.get_sysfs_path(core_device.get_device_id()), + 'queue/max_sectors_kb') + default_max_io_size = fs_utils.read_file(max_io_size_path) + + iteration_settings = [{"device": "SCSI-debug module", + "dev_size_mb": 1024, "logical_block_size": 512, "max_sectors_kb": 1024}, + {"device": "SCSI-debug module", + "dev_size_mb": 1024, "logical_block_size": 512, "max_sectors_kb": 256}, + {"device": "SCSI-debug module", + "dev_size_mb": 1024, "logical_block_size": 512, "max_sectors_kb": 128}, + {"device": "SCSI-debug module", + "dev_size_mb": 2048, "logical_block_size": 2048, "max_sectors_kb": 1024}, + {"device": "standard core device", + "max_sectors_kb": int(default_max_io_size)}, + {"device": "standard core device", "max_sectors_kb": 128}] + + for i in range(0, 6): + device = iteration_settings[i]["device"] + group_title = f"{device} | " + if device == "SCSI-debug module": + group_title += f"dev_size_mb = {iteration_settings[i]['dev_size_mb']} | " \ + f"logical_block_size = {iteration_settings[i]['logical_block_size']} | " + group_title += f"max_sectors_kb = {iteration_settings[i]['max_sectors_kb']}" + + with TestRun.group(group_title): + with TestRun.step("Prepare devices."): + core_device = prepare_core_device(iteration_settings[i]) + cache_device = TestRun.disks['cache'] + + with TestRun.step("Start cache and add prepared core device as core."): + cache, core, error_output = prepare_cas_device(cache_device, core_device) + with TestRun.step("Compare capabilities for CAS device, cache and core " + "(or check proper error if logical sector mismatch occurs)."): + compare_capabilities(cache_device, core_device, cache, core, error_output) + with TestRun.step("Recreate CAS device with switched cache and core devices."): + cache, core, error_output = prepare_cas_device(core_device, cache_device) + with TestRun.step("Compare capabilities for CAS device, cache and core " + "(or check proper error if logical sector mismatch occurs)."): + compare_capabilities(core_device, cache_device, cache, core, error_output) + + +# Methods used in test + +def prepare_core_device(settings): + if settings["device"] == "SCSI-debug module": + core_device = create_scsi_debug_device( + settings["logical_block_size"], 4, settings["dev_size_mb"]) + else: + core_device = TestRun.disks['core'] + core_device.set_max_io_size(Size(settings["max_sectors_kb"], Unit.KibiByte)) + return core_device + + +def create_scsi_debug_device(sector_size: int, physblk_exp: int, dev_size_mb=1024): + scsi_debug_params = { + "delay": "0", + "virtual_gb": "200", + "dev_size_mb": str(dev_size_mb), + "sector_size": str(sector_size), + "physblk_exp": str(physblk_exp) + } + scsi_debug = TestRun.plugin_manager.get_plugin('scsi_debug') + scsi_debug.params = scsi_debug_params + scsi_debug.reload() + return TestRun.scsi_debug_devices[0] + + +def prepare_cas_device(cache_device, core_device): + cache = casadm.start_cache(cache_device, force=True) + try: + cache_dev_bs = disk_utils.get_block_size(cache_device.get_device_id()) + core_dev_bs = disk_utils.get_block_size(core_device.get_device_id()) + core = cache.add_core(core_device) + if cache_dev_bs > core_dev_bs: + TestRun.LOGGER.error( + f"CAS device started with cache device logical block size ({cache_dev_bs}) " + f"greater than core device logical block size ({core_dev_bs})") + return cache, core, None + except CmdException as e: + if cache_dev_bs <= core_dev_bs: + TestRun.fail("Failed to create CAS device.") + TestRun.LOGGER.info("Cannot add core device with mismatching logical sector size. " + "Check output instead of capabilities.") + return cache, None, e.output + + +def method_min_not_zero(a, b): + return a if a != 0 and (a < b or b == 0) else b + + +def method_lcm_not_zero(a, b): + if a == 0 or b == 0: + return max([a, b]) + # gcd - greatest common divisor + return a * b / math.gcd(a, b) + + +# device capabilities and their test comparison methods +capabilities = {"logical_block_size": max, + "max_hw_sectors_kb": None, + "max_integrity_segments": method_min_not_zero, + "max_sectors_kb": None, + "max_segments": None, + "minimum_io_size": max, + "optimal_io_size": method_lcm_not_zero, + "physical_block_size": max, + "write_same_max_bytes": min} + + +def measure_capabilities(dev): + dev_capabilities = {} + dev_id = dev.parent_device.get_device_id() if isinstance(dev, Partition) \ + else dev.get_device_id() + for c in capabilities: + path = os.path.join(disk_utils.get_sysfs_path(dev_id), 'queue', c) + command = f"cat {path}" + output = TestRun.executor.run(command) + if output.exit_code == 0: + val = int(output.stdout) + dev_capabilities.update({c: val}) + else: + TestRun.LOGGER.info(f"Could not measure capability: {c} for {dev_id}") + return dev_capabilities + + +def compare_capabilities(cache_device, core_device, cache, core, msg): + if core is None: + cli_messages.check_stderr_msg(msg, + cli_messages.try_add_core_sector_size_mismatch) + else: + core_dev_sectors_num = \ + disk_utils.get_size(core_device.get_device_id()) / disk_utils.get_block_size( + core_device.get_device_id()) + core_sectors_num = disk_utils.get_size(core.get_device_id()) / disk_utils.get_block_size( + core.get_device_id()) + if core_dev_sectors_num != core_sectors_num: + TestRun.LOGGER.error( + "Number of sectors in CAS device and attached core device is different.") + cache.stop() + return + cas_capabilities = measure_capabilities(core) + cache_dev_capabilities = measure_capabilities(cache_device) + core_dev_capabilities = measure_capabilities(core_device) + + for (capability, method) in capabilities.items(): + cas_val = cas_capabilities[capability] + cache_val = cache_dev_capabilities[capability] + core_val = core_dev_capabilities[capability] + + comparison_val = method(core_val, cache_val) if method is not None else core_val + + if comparison_val != cas_val: + TestRun.LOGGER.error(f"Cas device {capability} is not set properly. Is: {cas_val}, " + f"should be {comparison_val} (cache: {cache_val}, " + f"core: {core_val})") + continue + TestRun.LOGGER.info(f"Cas device {capability} has proper value: {cas_val} " + f"(cache: {cache_val}, core: {core_val})") + cache.stop()