diff --git a/casadm/cas_lib.c b/casadm/cas_lib.c index a66994c55..ff43630f2 100644 --- a/casadm/cas_lib.c +++ b/casadm/cas_lib.c @@ -2707,7 +2707,7 @@ int partition_set_config(struct kcas_io_classes *cnfg) return result; } -int partition_setup(unsigned int cache_id, const char *file) +int partition_setup(unsigned int cache_id, bool reclassify, const char *file) { int result = 0; CSVFILE *in; @@ -2739,6 +2739,7 @@ int partition_setup(unsigned int cache_id, const char *file) } if (0 == partition_get_config(in, cnfg, cache_id)) { + cnfg->repart_all = reclassify; result = partition_set_config(cnfg); } else { result = FAILURE; diff --git a/casadm/cas_lib.h b/casadm/cas_lib.h index 8c1d073ea..a34848286 100644 --- a/casadm/cas_lib.h +++ b/casadm/cas_lib.h @@ -1,6 +1,6 @@ /* * Copyright(c) 2012-2022 Intel Corporation -* Copyright(c) 2024 Huawei Technologies +* Copyright(c) 2024-2025 Huawei Technologies * Copyright(c) 2026 Unvertical * SPDX-License-Identifier: BSD-3-Clause */ @@ -311,7 +311,7 @@ int flush_core(unsigned int cache_id, unsigned int core_id); int check_cache_device(const char *device_path); int partition_list(unsigned int cache_id, unsigned int output_format); -int partition_setup(unsigned int cache_id, const char *file); +int partition_setup(unsigned int cache_id, bool reclassify, const char *file); int partition_is_name_valid(const char *name); int cas_module_version(char *buff, int size); diff --git a/casadm/cas_main.c b/casadm/cas_main.c index 6e7e998ba..b243d6420 100644 --- a/casadm/cas_main.c +++ b/casadm/cas_main.c @@ -1528,6 +1528,7 @@ enum { io_class_opt_cache_id, io_class_opt_cache_file_load, + io_class_opt_keep_classification, io_class_opt_output_format, io_class_opt_io_class_id, @@ -1585,6 +1586,14 @@ static cli_option io_class_params_options[] = { .priv = (1 << io_class_opt_subcmd_configure) | (1 << io_class_opt_flag_required) }, + [io_class_opt_keep_classification] = { + .short_name = 'k', + .long_name = "keep-classification", + .desc = "Prevents reclassification of data in IO classes with IDs matching previous configuration", + .args_count = 0, + .arg = NULL, + .priv = (1 << io_class_opt_subcmd_configure) + }, [io_class_opt_output_format] = { .short_name = 'o', .long_name = "output-format", @@ -1650,6 +1659,7 @@ struct { int io_class_id; int cache_mode; int io_class_prio; + bool reclassify; int output_format; uint32_t min; uint32_t max; @@ -1659,6 +1669,7 @@ struct { .subcmd = io_class_opt_subcmd_unknown, .cache_id = 0, .file = "", + .reclassify = true, .output_format = OUTPUT_FORMAT_DEFAULT }; @@ -1695,6 +1706,10 @@ int io_class_handle_option(char *opt, const char **arg) return FAILURE; io_class_params_options[io_class_opt_output_format].priv |= (1 << io_class_opt_flag_set); + } else if (!strcmp(opt, "keep-classification")) { + io_class_params.reclassify = false; + + io_class_params_options[io_class_opt_keep_classification].priv |= (1 << io_class_opt_flag_set); } return 0; @@ -1751,7 +1766,7 @@ int io_class_handle() { switch (io_class_params.subcmd) { case io_class_opt_subcmd_configure: return partition_setup(io_class_params.cache_id, - io_class_params.file); + io_class_params.reclassify, io_class_params.file); case io_class_opt_subcmd_list: return partition_list(io_class_params.cache_id, io_class_params.output_format); diff --git a/casadm/casadm.8 b/casadm/casadm.8 index 8db731f78..9946ee30f 100644 --- a/casadm/casadm.8 +++ b/casadm/casadm.8 @@ -568,6 +568,14 @@ Identifier of cache instance <1-16384>. .B -f, --file Configuration file containing IO class definition. +.TP +.B -k, --keep-classification +Keep classification of data cached in IO classes present in the new +configuration. By default data of all the IO classes is moved to the +unclassified IO class and gets classified again on next access. Data of IO +classes removed from the configuration is reclassified regardless of this +option. + .SH Options that are valid with --io-class --list (-C -L) are: .TP .B -i, --cache-id diff --git a/modules/cas_cache/layer_cache_management.c b/modules/cas_cache/layer_cache_management.c index cb9715d7a..06fbd805b 100644 --- a/modules/cas_cache/layer_cache_management.c +++ b/modules/cas_cache/layer_cache_management.c @@ -1785,12 +1785,15 @@ int cache_mngt_set_partitions(const char *cache_name, size_t name_len, for (class_id = 0; class_id < OCF_USER_IO_CLASS_MAX; class_id++) { io_class_cfg->config[class_id].class_id = class_id; + /* Data of removed IO classes is always reclassified */ + io_class_cfg->config[class_id].repart = true; if (!cfg->info[class_id].name[0]) { io_class_cfg->config[class_id].class_id = class_id; continue; } + io_class_cfg->config[class_id].repart = cfg->repart_all; io_class_info2cfg(class_id, &cfg->info[class_id], &io_class_cfg->config[class_id]); } diff --git a/modules/include/cas_ioctl_codes.h b/modules/include/cas_ioctl_codes.h index d87cc6920..4ca6a5560 100644 --- a/modules/include/cas_ioctl_codes.h +++ b/modules/include/cas_ioctl_codes.h @@ -258,6 +258,9 @@ struct kcas_io_classes { /** Cache ID */ uint16_t cache_id; + /** Reclassify data of all IO classes, not only of the removed ones */ + bool repart_all; + int ext_err_code; /** IO class info */ diff --git a/ocf b/ocf index aff886fcd..9107f7d9e 160000 --- a/ocf +++ b/ocf @@ -1 +1 @@ -Subproject commit aff886fcde4e0ae32c856ba2fb00529157a8ad2d +Subproject commit 9107f7d9e929680cc450c58ca84915861bf571d2 diff --git a/test/functional/api/cas/cache.py b/test/functional/api/cas/cache.py index 8afa5195d..2484039f6 100644 --- a/test/functional/api/cas/cache.py +++ b/test/functional/api/cas/cache.py @@ -1,6 +1,7 @@ # # Copyright(c) 2019-2021 Intel Corporation # Copyright(c) 2024-2026 Huawei Technologies Co., Ltd. +# Copyright(c) 2026 Unvertical # SPDX-License-Identifier: BSD-3-Clause # @@ -174,8 +175,10 @@ def reset_counters(self) -> Output: def set_cache_mode(self, cache_mode: CacheMode, flush=None) -> Output: return casadm.set_cache_mode(cache_mode, self.cache_id, flush) - def load_io_class(self, file_path: str) -> Output: - return casadm.load_io_classes(self.cache_id, file_path) + def load_io_class(self, file_path: str, keep_classification: bool = False) -> Output: + return casadm.load_io_classes( + self.cache_id, file_path, keep_classification=keep_classification + ) def list_io_classes(self) -> list: return get_io_class_list(self.cache_id) diff --git a/test/functional/api/cas/casadm.py b/test/functional/api/cas/casadm.py index e91e37907..2a8a02880 100644 --- a/test/functional/api/cas/casadm.py +++ b/test/functional/api/cas/casadm.py @@ -1,6 +1,7 @@ # # Copyright(c) 2019-2022 Intel Corporation # Copyright(c) 2024-2026 Huawei Technologies Co., Ltd. +# Copyright(c) 2026 Unvertical # SPDX-License-Identifier: BSD-3-Clause # @@ -589,9 +590,16 @@ def flush_core(cache_id: int, core_id: int, shortcut: bool = False) -> Output: return output -def load_io_classes(cache_id: int, file: str, shortcut: bool = False) -> Output: +def load_io_classes( + cache_id: int, file: str, shortcut: bool = False, keep_classification: bool = False +) -> Output: output = TestRun.executor.run( - load_io_classes_cmd(cache_id=str(cache_id), file=file, shortcut=shortcut) + load_io_classes_cmd( + cache_id=str(cache_id), + file=file, + shortcut=shortcut, + keep_classification=keep_classification, + ) ) if output.exit_code != 0: raise CmdException("Load IO class command failed.", output) diff --git a/test/functional/api/cas/cli.py b/test/functional/api/cas/cli.py index 8b58c6c22..f284743a3 100644 --- a/test/functional/api/cas/cli.py +++ b/test/functional/api/cas/cli.py @@ -411,10 +411,14 @@ def flush_core_cmd(cache_id: str, core_id: str, shortcut: bool = False) -> str: return casadm_bin + command -def load_io_classes_cmd(cache_id: str, file: str, shortcut: bool = False) -> str: +def load_io_classes_cmd( + cache_id: str, file: str, shortcut: bool = False, keep_classification: bool = False +) -> str: command = " -C -C" if shortcut else " --io-class --load-config" command += (" -i " if shortcut else " --cache-id ") + cache_id command += (" -f " if shortcut else " --file ") + file + if keep_classification: + command += (" -k" if shortcut else " --keep-classification") return casadm_bin + command diff --git a/test/functional/api/cas/cli_help_messages.py b/test/functional/api/cas/cli_help_messages.py index f1062b6e9..432cfff22 100644 --- a/test/functional/api/cas/cli_help_messages.py +++ b/test/functional/api/cas/cli_help_messages.py @@ -297,6 +297,7 @@ r"Options that are valid with --load-config \(-C\) are:", r"-i --cache-id \ Identifier of cache instance \<1-16384\>", r"-f --file \ Configuration file containing IO class definition", + r"-k --keep-classification Prevents reclassification of data in IO classes with IDs matching previous configuration", r"Lists currently configured IO classes:", r"Usage: casadm --io-class --list --cache-id \ \[option\.\.\.\]", r"Options that are valid with --list \(-L\) are:", diff --git a/test/functional/api/cas/ioclass_config.py b/test/functional/api/cas/ioclass_config.py index 38a438eea..8c670c863 100644 --- a/test/functional/api/cas/ioclass_config.py +++ b/test/functional/api/cas/ioclass_config.py @@ -160,6 +160,8 @@ def generate_random_ioclass_list(count: int, max_priority: int = MAX_IO_CLASS_PR ), ] for i in range(1, count - 1): + if random.choice([True, False]): + continue random_list.append( IoClass( i, diff --git a/test/functional/tests/io_class/io_class_common.py b/test/functional/tests/io_class/io_class_common.py index 05c93ea58..bb8a182b3 100644 --- a/test/functional/tests/io_class/io_class_common.py +++ b/test/functional/tests/io_class/io_class_common.py @@ -92,10 +92,12 @@ def get_io_class_usage(cache, io_class_id, percent=False): ).usage_stats -def generate_and_load_random_io_class_config(cache): +def generate_and_load_random_io_class_config(cache, keep_classification=False): random_list = IoClass.generate_random_ioclass_list(ioclass_config.MAX_IO_CLASS_ID + 1) IoClass.save_list_to_config_file(random_list, add_default_rule=False) - cache.load_io_class(ioclass_config.default_config_file_path) + cache.load_io_class( + ioclass_config.default_config_file_path, keep_classification=keep_classification + ) return random_list diff --git a/test/functional/tests/io_class/test_io_class_cli.py b/test/functional/tests/io_class/test_io_class_cli.py index 7bf4c212b..f5148f56e 100644 --- a/test/functional/tests/io_class/test_io_class_cli.py +++ b/test/functional/tests/io_class/test_io_class_cli.py @@ -47,7 +47,7 @@ def test_io_class_export_configuration(): ) with TestRun.LOGGER.step( - "Create and load configuration file for 33 IO classes " + "Create and load random configuration file for up to 33 IO classes " "with random names, allocation and priority values" ): random_list = IoClass.generate_random_ioclass_list(33) diff --git a/test/functional/tests/io_class/test_io_class_eviction_pinned_after_unpin.py b/test/functional/tests/io_class/test_io_class_eviction_pinned_after_unpin.py index 6c7d5af55..72d44686c 100644 --- a/test/functional/tests/io_class/test_io_class_eviction_pinned_after_unpin.py +++ b/test/functional/tests/io_class/test_io_class_eviction_pinned_after_unpin.py @@ -65,7 +65,7 @@ def test_io_class_eviction_pinned_after_unpin(): with TestRun.step("Unpin ioclass to and set its priority to be lower than second IO class"): io_classes = [IoclassConfig(1, 11, 1, cores[0]), IoclassConfig(2, 10, 1, cores[1])] - _add_and_load_io_classes(cache.cache_id, io_classes) + _add_and_load_io_classes(cache.cache_id, io_classes, keep_classification=True) with TestRun.step("Run dd on second io ioclass "): run_io_dir(f"{io_classes[1].core.path}", usable_cache_size) @@ -89,7 +89,7 @@ def _refresh_cache(cache): cache.reset_counters() -def _add_and_load_io_classes(cache_id, io_classes: list): +def _add_and_load_io_classes(cache_id, io_classes: list, keep_classification=False): ioclass_config.remove_ioclass_config() ioclass_config.create_ioclass_config(False) @@ -102,7 +102,11 @@ def _add_and_load_io_classes(cache_id, io_classes: list): ioclass_config_path=ioclass_config.default_config_file_path, ) - casadm.load_io_classes(cache_id=cache_id, file=default_config_file_path) + casadm.load_io_classes( + cache_id=cache_id, + file=default_config_file_path, + keep_classification=keep_classification, + ) def prepare(cores_number=1, cache_size=Size(10, Unit.GibiByte), core_size=Size(5, Unit.GibiByte)): diff --git a/test/functional/tests/io_class/test_io_class_occupancy_resize.py b/test/functional/tests/io_class/test_io_class_occupancy_resize.py index 2bc7c88a2..29cfbf359 100644 --- a/test/functional/tests/io_class/test_io_class_occupancy_resize.py +++ b/test/functional/tests/io_class/test_io_class_occupancy_resize.py @@ -138,7 +138,11 @@ def test_ioclass_resize(cache_line_size, new_occupancy): f"{io_class.max_occupancy:0.2f}", ) - casadm.load_io_classes(cache_id=cache.cache_id, file=default_config_file_path) + casadm.load_io_classes( + cache_id=cache.cache_id, + file=default_config_file_path, + keep_classification=True, + ) with TestRun.step("Perform IO with size equal to cache size"): run_io_dir(f"{io_class.dir_path}/tmp_file", cache_size) diff --git a/test/functional/tests/io_class/test_io_class_preserve_config.py b/test/functional/tests/io_class/test_io_class_preserve_config.py index 4813e25b1..d0816a5da 100644 --- a/test/functional/tests/io_class/test_io_class_preserve_config.py +++ b/test/functional/tests/io_class/test_io_class_preserve_config.py @@ -52,7 +52,7 @@ def test_io_class_preserve_configuration(): cache.add_core(core_device) with TestRun.step( - "Create and load configuration file for all IO classes with random names, " + "Create and load random configuration file with random names, " "allocation and priority values." ): generated_io_classes = generate_and_load_random_io_class_config(cache) diff --git a/test/functional/tests/io_class/test_io_class_reclassification.py b/test/functional/tests/io_class/test_io_class_reclassification.py new file mode 100644 index 000000000..26e1b23bd --- /dev/null +++ b/test/functional/tests/io_class/test_io_class_reclassification.py @@ -0,0 +1,202 @@ +# +# Copyright(c) 2026 Unvertical +# SPDX-License-Identifier: BSD-3-Clause +# + +import pytest + +from api.cas import ioclass_config, casadm +from core.test_run import TestRun +from storage_devices.disk import DiskType, DiskTypeSet, DiskTypeLowerThan +from test_tools.dd import Dd +from type_def.size import Size, Unit +from tests.io_class.io_class_common import ( + prepare, + ioclass_config_path, + get_io_class_occupancy, + get_io_class_dirty, +) + +region_size = Size(16, Unit.MebiByte) +region_sectors = int(region_size.get_value(Unit.Blocks512)) + + +def lba_rule(region): + first = region * region_sectors + last = first + region_sectors - 1 + return f"lba:ge:{first}&lba:le:{last}&done" + + +def add_lba_ioclass(ioclass_id, region): + ioclass_config.add_ioclass( + ioclass_id=ioclass_id, + eviction_priority=1, + allocation="1.00", + rule=lba_rule(region), + ioclass_config_path=ioclass_config_path, + ) + + +def run_region_io(core, region, write): + dd = Dd().block_size(Size(1, Unit.MebiByte)).count(int(region_size.get_value(Unit.MebiByte))) + if write: + dd.input("/dev/urandom").output(core.path).seek( + region * int(region_size.get_value(Unit.MebiByte)) + ).oflag("direct") + else: + dd.input(core.path).output("/dev/null").skip( + region * int(region_size.get_value(Unit.MebiByte)) + ).iflag("direct") + dd.run() + + +def regions_md5(core, regions): + output = TestRun.executor.run_expect_success( + f"dd if={core.path} bs=1M count={regions * int(region_size.get_value(Unit.MebiByte))} " + f"iflag=direct | md5sum" + ) + return output.stdout.split()[0] + + +def check_usage(cache, ioclass_id, expected, check_dirty=True): + occupancy = get_io_class_occupancy(cache, ioclass_id) + if occupancy != expected: + TestRun.LOGGER.error( + f"Wrong occupancy of IO class {ioclass_id}. Expected {expected}, got {occupancy}" + ) + if check_dirty: + dirty = get_io_class_dirty(cache, ioclass_id) + if dirty != expected: + TestRun.LOGGER.error( + f"Wrong dirty data of IO class {ioclass_id}. Expected {expected}, got {dirty}" + ) + + +@pytest.mark.require_disk("cache", DiskTypeSet([DiskType.optane, DiskType.nand])) +@pytest.mark.require_disk("core", DiskTypeLowerThan("cache")) +@pytest.mark.parametrize("keep_classification", [False, True]) +def test_io_class_reclassify_on_remove(keep_classification): + """ + title: Test reclassification of data after removing IO class. + description: | + Fill two IO classes with dirty data, remove one of them from the configuration + and load it. Check that data of all the IO classes is moved to the unclassified + IO class, unless the classification is kept, in which case only data of the + removed IO class is moved. Then check that accessed data is classified again. + pass_criteria: + - Data of all the IO classes is reclassified by default. + - With --keep-classification only data of the removed IO class is reclassified. + - Accessed data is classified according to the new configuration. + - Cache occupancy and data stay unchanged. + """ + default_id = ioclass_config.DEFAULT_IO_CLASS_ID + kept_id, removed_id = 1, 2 + zero = Size.zero() + + with TestRun.step("Prepare cache and core"): + cache, core = prepare(default_allocation="1.00") + + with TestRun.step("Load configuration with IO classes for two LBA regions"): + add_lba_ioclass(kept_id, region=0) + add_lba_ioclass(removed_id, region=1) + casadm.load_io_classes(cache_id=cache.cache_id, file=ioclass_config_path) + + with TestRun.step("Write data to both regions"): + run_region_io(core, region=0, write=True) + run_region_io(core, region=1, write=True) + md5_before = regions_md5(core, 2) + check_usage(cache, kept_id, region_size) + check_usage(cache, removed_id, region_size) + check_usage(cache, default_id, zero) + + with TestRun.step("Remove one IO class and load the configuration"): + ioclass_config.remove_ioclass(removed_id, ioclass_config_path) + casadm.load_io_classes( + cache_id=cache.cache_id, + file=ioclass_config_path, + keep_classification=keep_classification, + ) + + with TestRun.step("Check that data was reclassified"): + if keep_classification: + check_usage(cache, kept_id, region_size) + check_usage(cache, default_id, region_size) + else: + check_usage(cache, kept_id, zero) + check_usage(cache, default_id, region_size * 2) + + if cache.get_occupancy() != region_size * 2: + TestRun.LOGGER.error(f"Wrong cache occupancy: {cache.get_occupancy()}") + if cache.get_dirty_blocks() != region_size * 2: + TestRun.LOGGER.error(f"Wrong amount of dirty data: {cache.get_dirty_blocks()}") + + with TestRun.step("Read data of the remaining IO class"): + run_region_io(core, region=0, write=False) + + with TestRun.step("Check that read data was classified again"): + check_usage(cache, kept_id, region_size) + check_usage(cache, default_id, region_size) + + with TestRun.step("Flush cache and check data consistency"): + cache.flush_cache() + if cache.get_dirty_blocks() != zero: + TestRun.LOGGER.error(f"Dirty data left after flush: {cache.get_dirty_blocks()}") + if regions_md5(core, 2) != md5_before: + TestRun.LOGGER.error("Data changed after reclassification") + + +@pytest.mark.require_disk("cache", DiskTypeSet([DiskType.optane, DiskType.nand])) +@pytest.mark.require_disk("core", DiskTypeLowerThan("cache")) +@pytest.mark.parametrize("keep_classification", [False, True]) +def test_io_class_reclassify_on_rule_change(keep_classification): + """ + title: Test reclassification of data after changing IO class rule. + description: | + Fill an IO class with data, change its classification rule and load the + configuration. Check that by default its data is moved to the unclassified + IO class immediately, and with the classification kept it stays in the IO + class until accessed. + pass_criteria: + - Data of the IO class with modified rule is reclassified by default. + - With --keep-classification data stays in the IO class until accessed. + - Accessed data is classified according to the new rule. + """ + default_id = ioclass_config.DEFAULT_IO_CLASS_ID + ioclass_id = 1 + zero = Size.zero() + + with TestRun.step("Prepare cache and core"): + cache, core = prepare(default_allocation="1.00") + + with TestRun.step("Load configuration with IO class for the first LBA region"): + add_lba_ioclass(ioclass_id, region=0) + casadm.load_io_classes(cache_id=cache.cache_id, file=ioclass_config_path) + + with TestRun.step("Write data to the first region"): + run_region_io(core, region=0, write=True) + check_usage(cache, ioclass_id, region_size) + check_usage(cache, default_id, zero) + + with TestRun.step("Change the IO class rule to the second LBA region"): + ioclass_config.remove_ioclass(ioclass_id, ioclass_config_path) + add_lba_ioclass(ioclass_id, region=1) + casadm.load_io_classes( + cache_id=cache.cache_id, + file=ioclass_config_path, + keep_classification=keep_classification, + ) + + with TestRun.step("Check that data was reclassified only if requested"): + if keep_classification: + check_usage(cache, ioclass_id, region_size) + check_usage(cache, default_id, zero) + else: + check_usage(cache, ioclass_id, zero) + check_usage(cache, default_id, region_size) + + with TestRun.step("Read data of the first region"): + run_region_io(core, region=0, write=False) + + with TestRun.step("Check that read data was classified according to the new rule"): + check_usage(cache, ioclass_id, zero) + check_usage(cache, default_id, region_size) diff --git a/test/functional/tests/security/fuzzy/kernel/fuzzy_with_io/fuzzy_io_class/test_fuzzy_io_class_load_config_flags.py b/test/functional/tests/security/fuzzy/kernel/fuzzy_with_io/fuzzy_io_class/test_fuzzy_io_class_load_config_flags.py new file mode 100644 index 000000000..f80145a62 --- /dev/null +++ b/test/functional/tests/security/fuzzy/kernel/fuzzy_with_io/fuzzy_io_class/test_fuzzy_io_class_load_config_flags.py @@ -0,0 +1,95 @@ +# +# Copyright(c) 2025 Huawei Technologies Co., Ltd. +# Copyright(c) 2026 Unvertical +# SPDX-License-Identifier: BSD-3-Clause +# + +import pytest + +from api.cas.cache_config import ( + CacheMode, + CacheLineSize, + CleaningPolicy, + UnalignedIo, + KernelParameters, + UseIoScheduler, +) +from api.cas.cli import load_io_classes_cmd +from core.test_run import TestRun +from storage_devices.disk import DiskType, DiskTypeSet, DiskTypeLowerThan +from test_tools.peach_fuzzer.peach_fuzzer import PeachFuzzer +from tests.security.fuzzy.kernel.common.common import ( + prepare_cas_instance, + get_fuzz_config, + run_cmd_and_validate, +) +from tests.security.fuzzy.kernel.fuzzy_with_io.common.common import ( + get_basic_workload, + mount_point, +) + + +@pytest.mark.require_disk("cache", DiskTypeSet([DiskType.optane, DiskType.nand])) +@pytest.mark.require_disk("core", DiskTypeLowerThan("cache")) +@pytest.mark.parametrizex("cache_mode", CacheMode) +@pytest.mark.parametrizex("cache_line_size", CacheLineSize) +@pytest.mark.parametrizex("cleaning_policy", CleaningPolicy) +@pytest.mark.parametrizex("unaligned_io", UnalignedIo) +@pytest.mark.parametrizex("use_io_scheduler", UseIoScheduler) +def test_fuzzy_io_class_load_config_flags( + cache_mode, cache_line_size, cleaning_policy, unaligned_io, use_io_scheduler +): + """ + title: Fuzzy test for casadm 'load IO class configuration' command – flags. + description: | + Using Peach Fuzzer check Open CAS ability of handling wrong flags in + 'load IO class configuration' command. + pass_criteria: + - System did not crash + - Open CAS still works. + """ + with TestRun.step( + "Start cache with configuration and add core device, make filesystem and mount it" + ): + cache_disk = TestRun.disks["cache"] + core_disk = TestRun.disks["core"] + cache, core = prepare_cas_instance( + cache_device=cache_disk, + core_device=core_disk, + cache_mode=cache_mode, + cache_line_size=cache_line_size, + kernel_params=KernelParameters(unaligned_io, use_io_scheduler), + cleaning_policy=cleaning_policy, + mount_point=mount_point, + ) + + with TestRun.step("Run fio in background"): + fio = get_basic_workload(mount_point) + fio_pid = fio.run_in_background() + if not TestRun.executor.check_if_process_exists(fio_pid): + raise Exception("Fio is not running.") + + with TestRun.step("Prepare PeachFuzzer"): + valid_values = ["", "-k", "--keep-classification"] + valid_values = [v.encode("ascii") for v in valid_values] + PeachFuzzer.generate_config(get_fuzz_config("flags.yml")) + base_cmd = load_io_classes_cmd( + cache_id=str(cache.cache_id), + file="/etc/opencas/ioclass-config.csv", + ) + " {param}" + commands = PeachFuzzer.get_fuzzed_command( + command_template=base_cmd, count=TestRun.usr.fuzzy_iter_count + ) + + for index, cmd in TestRun.iteration( + enumerate(commands), f"Run command {TestRun.usr.fuzzy_iter_count} times" + ): + with TestRun.step(f"Iteration {index + 1}"): + if not TestRun.executor.check_if_process_exists(fio_pid): + raise Exception("Fio is not running.") + + run_cmd_and_validate( + cmd=cmd, + value_name="Flag", + is_valid=cmd.param in valid_values, + ) diff --git a/test/functional/tests/stats/test_ioclass_stats.py b/test/functional/tests/stats/test_ioclass_stats.py index eace7bbbc..6f7a7fe22 100644 --- a/test/functional/tests/stats/test_ioclass_stats.py +++ b/test/functional/tests/stats/test_ioclass_stats.py @@ -307,15 +307,17 @@ def test_ioclass_stats_sections_cache(stat_filter, random_cls): validate_statistics(statistics, stat_filter) with TestRun.step("Load random IO class configuration for each cache"): + io_class_ids = {} for cache in caches: random_list = IoClass.generate_random_ioclass_list(ioclass_config.MAX_IO_CLASS_ID + 1) IoClass.save_list_to_config_file(random_list, add_default_rule=False) cache.load_io_class(ioclass_config.default_config_file_path) + io_class_ids[cache.cache_id] = [io_class.id for io_class in random_list] with TestRun.group("Random IO class config statistics"): for cache in caches: with TestRun.step(f"Cache {cache.cache_id}"): - for class_id in range(ioclass_config.MAX_IO_CLASS_ID + 1): + for class_id in io_class_ids[cache.cache_id]: statistics = get_stats_dict( filter=[stat_filter], cache_id=cache.cache_id, io_class_id=class_id) validate_statistics(statistics, stat_filter) @@ -348,16 +350,18 @@ def test_ioclass_stats_sections_core(stat_filter, random_cls): validate_statistics(statistics, stat_filter) with TestRun.step("Load random IO class configuration for each cache"): + io_class_ids = {} for cache in caches: random_list = IoClass.generate_random_ioclass_list(ioclass_config.MAX_IO_CLASS_ID + 1) IoClass.save_list_to_config_file(random_list, add_default_rule=False) cache.load_io_class(ioclass_config.default_config_file_path) + io_class_ids[cache.cache_id] = [io_class.id for io_class in random_list] with TestRun.group("Random IO class config statistics"): for cache in caches: for core in cache.get_cores(): with TestRun.step(f"Core {core.cache_id}-{core.core_id}"): - for class_id in range(ioclass_config.MAX_IO_CLASS_ID + 1): + for class_id in io_class_ids[cache.cache_id]: statistics = get_stats_dict( [stat_filter], core.cache_id, core.core_id, class_id) validate_statistics(statistics, stat_filter) diff --git a/test/functional/tests/stress/test_stress_change_io_class_config_io.py b/test/functional/tests/stress/test_stress_change_io_class_config_io.py index acfe4b9cd..405f55756 100644 --- a/test/functional/tests/stress/test_stress_change_io_class_config_io.py +++ b/test/functional/tests/stress/test_stress_change_io_class_config_io.py @@ -27,12 +27,14 @@ @pytest.mark.require_disk("cache", DiskTypeSet([DiskType.optane, DiskType.nand])) @pytest.mark.require_disk("core", DiskTypeLowerThan("cache")) -def test_stress_io_class_change_config_during_io_raw(): +@pytest.mark.parametrize("keep_classification", [False, True]) +def test_stress_io_class_change_config_during_io_raw(keep_classification): """ title: Set up IO class configuration from file during IO - stress. description: | Check Open CAS ability to change IO class configuration during running IO - on small cache and core devices. + on small cache and core devices, with and without reclassification of + cached data. pass_criteria: - No system crash - IO class configuration changes successfully @@ -61,11 +63,12 @@ def test_stress_io_class_change_config_during_io_raw(): fio_task = start_async_func(run_io, cores, True) with TestRun.step("In two-second time interval change IO class configuration " - "(using random values in allowed range) and cache mode " - "(between all supported). Check if Open CAS configuration has changed."): + "(using random values in allowed range, dropping random IO classes) " + "and cache mode (between all supported). Check if Open CAS " + "configuration has changed."): change_mode_thread = threading.Thread(target=change_cache_mode, args=[cache, fio_task]) change_io_class_thread = threading.Thread(target=change_io_class_config, - args=[cache, fio_task]) + args=[cache, fio_task, keep_classification]) change_mode_thread.start() sleep(1) change_io_class_thread.start() @@ -80,13 +83,15 @@ def test_stress_io_class_change_config_during_io_raw(): @pytest.mark.require_disk("cache", DiskTypeSet([DiskType.optane, DiskType.nand])) @pytest.mark.require_disk("core", DiskTypeLowerThan("cache")) +@pytest.mark.parametrize("keep_classification", [False, True]) @pytest.mark.asyncio -async def test_stress_io_class_change_config_during_io_fs(): +async def test_stress_io_class_change_config_during_io_fs(keep_classification): """ title: Set up IO class configuration from file for filesystems during IO - stress. description: | Check Intel CAS ability to change IO class configuration for different filesystems - during running IO on small cache and core devices. + during running IO on small cache and core devices, with and without + reclassification of cached data. pass_criteria: - No system crash - IO class configuration changes successfully @@ -121,11 +126,12 @@ async def test_stress_io_class_change_config_during_io_fs(): fio_task = start_async_func(run_io, cores) with TestRun.step("In two-second time interval change IO class configuration " - "(using random values in allowed range) and cache mode " - "(between all supported). Check if Open CAS configuration has changed."): + "(using random values in allowed range, dropping random IO classes) " + "and cache mode (between all supported). Check if Open CAS " + "configuration has changed."): change_mode_thread = threading.Thread(target=change_cache_mode, args=[cache, fio_task]) change_io_class_thread = threading.Thread(target=change_io_class_config, - args=[cache, fio_task]) + args=[cache, fio_task, keep_classification]) change_mode_thread.start() sleep(1) change_io_class_thread.start() @@ -148,10 +154,12 @@ def change_cache_mode(cache, fio_task): cache.set_cache_mode(new_cache_mode, False) -def change_io_class_config(cache, fio_task): +def change_io_class_config(cache, fio_task, keep_classification): while fio_task.done() is False: sleep(2) - generated_io_classes = generate_and_load_random_io_class_config(cache) + generated_io_classes = generate_and_load_random_io_class_config( + cache, keep_classification=keep_classification + ) loaded_io_classes = cache.list_io_classes() if not IoClass.compare_ioclass_lists(generated_io_classes, loaded_io_classes): TestRun.LOGGER.error("IO classes not changed correctly.")