From c4f1f82e3bea15db5a612886fde86f1a543df19c Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Wed, 1 Jul 2026 13:30:51 +0200 Subject: [PATCH 1/5] tests: Add recovery unplug tests for metadata error mode Signed-off-by: Robert Baldyga --- .../recovery/test_recovery_unplug.py | 94 ++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/test/functional/tests/lazy_writes/recovery/test_recovery_unplug.py b/test/functional/tests/lazy_writes/recovery/test_recovery_unplug.py index 18c474f5d..465354f51 100644 --- a/test/functional/tests/lazy_writes/recovery/test_recovery_unplug.py +++ b/test/functional/tests/lazy_writes/recovery/test_recovery_unplug.py @@ -12,6 +12,7 @@ from api.cas.cache_config import CacheMode, CacheModeTrait, CacheLineSize from core.test_run import TestRun from storage_devices.disk import DiskTypeSet, DiskType, DiskTypeLowerThan +from test_tools.dd import Dd from test_tools.fs_tools import Filesystem from connection.utils.output import CmdException from type_def.size import Size, Unit @@ -129,7 +130,8 @@ def test_recovery_unplug_cache_raw(cache_mode, cls): title: Test for recovery after cache drive removal - test on raw device. description: | Verify that unflushed data can be safely recovered after, when SSD drive is removed - after write completion - test on raw device. + after write completion - test on raw device. Stop immediately after unplug to verify + flush error handling. pass_criteria: - CAS recovers successfully after cache drive unplug - No data corruption @@ -198,3 +200,93 @@ def test_recovery_unplug_cache_raw(cache_mode, cls): except Exception: # On some OSes files at /tmp location are automatically removed after DUT hard reset pass + + +@pytest.mark.require_disk("cache", DiskTypeSet([DiskType.optane, DiskType.nand])) +@pytest.mark.require_disk("core", DiskTypeLowerThan("cache")) +@pytest.mark.parametrizex("cache_mode", CacheMode.with_traits(CacheModeTrait.LazyWrites)) +@pytest.mark.parametrizex("cls", [CacheLineSize.LINE_4KiB, CacheLineSize.LINE_64KiB]) +@pytest.mark.require_plugin("power_control") +def test_recovery_unplug_cache_raw_metadata_err(cache_mode, cls): + """ + title: Test for recovery after cache drive removal - test on raw device. + description: | + Verify that unflushed data can be safely recovered after, when SSD drive is removed + after write completion - test on raw device. Trigger cache error by write attempt + after unplugging cache, to enter metadata error mode before stop. + pass_criteria: + - CAS recovers successfully after cache drive unplug + - No data corruption + """ + with TestRun.step("Prepare devices"): + cache_disk = TestRun.disks['cache'] + core_disk = TestRun.disks['core'] + cache_disk.create_partitions([Size(2, Unit.GibiByte)]) + core_disk.create_partitions([Size(16, Unit.GibiByte)]) + cache_device = cache_disk.partitions[0] + core_device = core_disk.partitions[0] + + with TestRun.step("Create test files."): + source_file, target_file = create_test_files(test_file_size) + source_file_md5 = source_file.md5sum() + + with TestRun.step("Start cache and add core."): + cache = casadm.start_cache(cache_device, cache_mode, cls, force=True) + core = cache.add_core(core_device) + + with TestRun.step("Copy file to CAS."): + copy_file(source=source_file.full_path, target=core.path, + size=test_file_size, direct="oflag") + TestRun.LOGGER.info(str(core.get_statistics())) + + with TestRun.step("Unplug cache device."): + cache_disk.unplug() + TestRun.LOGGER.info(f"List caches:\n{casadm.list_caches().stdout}") + TestRun.LOGGER.info(f"Dirty blocks on cache: " + f"{cache.get_dirty_blocks().get_value(Unit.Blocks4096)}") + + with TestRun.step("Try to write to CAS."): + Dd().input("/dev/random") \ + .output(core.path) \ + .oflag("direct") \ + .block_size(Size(1, Unit.Blocks4096)) \ + .count(1) \ + .run() + + with TestRun.step("Stop cache."): + try: + cache.stop() + TestRun.fail("Stopping the cache should be aborted without --no-flush flag.") + except CmdException as e: + TestRun.LOGGER.info(str(e.output)) + try: + cache.stop(no_data_flush=True) + TestRun.LOGGER.warning("Expected stopping cache with errors with --no-flush flag.") + except CmdException as e1: + cli_messages.check_stderr_msg(e1.output, cli_messages.stop_cache_errors) + + with TestRun.step("Plug missing cache device."): + TestRun.LOGGER.info(str(casadm.list_caches(by_id_path=False))) + cache_disk.plug_all() + + with TestRun.step("Load cache."): + cache = casadm.load_cache(cache_device) + TestRun.LOGGER.info(f"Dirty blocks on cache: " + f"{cache.get_dirty_blocks().get_value(Unit.Blocks4096)}") + + with TestRun.step("Stop cache with data flush."): + cache.stop() + + with TestRun.step("Copy file from core device and check md5sum."): + copy_file(source=core_device.path, target=target_file.full_path, + size=test_file_size, direct="iflag") + target_file_md5 = target_file.md5sum() + compare_files(source_file_md5, target_file_md5) + + with TestRun.step("Cleanup core device and remove test files."): + try: + target_file.remove() + source_file.remove() + except Exception: + # On some OSes files at /tmp location are automatically removed after DUT hard reset + pass From a1c64e12699f2d77a545f80119b4f243d2419dbe Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Thu, 2 Jul 2026 10:54:29 +0200 Subject: [PATCH 2/5] tests: file_permissions: Fix paths after changes Signed-off-by: Robert Baldyga --- test/functional/tests/misc/test_files_permissions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/functional/tests/misc/test_files_permissions.py b/test/functional/tests/misc/test_files_permissions.py index 53c9e610e..fb39b4854 100644 --- a/test/functional/tests/misc/test_files_permissions.py +++ b/test/functional/tests/misc/test_files_permissions.py @@ -80,10 +80,10 @@ } installed_files_perms_exceptions = { - "lib/opencas/casctl": 755, - "lib/opencas/open-cas-loader.py": 755, - "sbin/casadm": 755, - "sbin/casctl": 755, + "usr/lib/opencas/casctl": 755, + "usr/lib/opencas/open-cas-loader.py": 755, + "usr/sbin/casadm": 755, + "usr/sbin/casctl": 755, "usr/lib/systemd/system-shutdown/open-cas.shutdown": 755, } From 12d2a297d6a415d3f65bacb631f11ab6852489ae Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Thu, 2 Jul 2026 16:33:41 +0200 Subject: [PATCH 3/5] tests: io_class: Use Filesystem.regular() when creating fs Signed-off-by: Robert Baldyga --- .../tests/stress/test_stress_change_io_class_config_io.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 0687b6a9d..acfe4b9cd 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 @@ -1,6 +1,7 @@ # # Copyright(c) 2022 Intel Corporation # Copyright(c) 2024 Huawei Technologies Co., Ltd. +# Copyright(c) 2026 Unvertical # SPDX-License-Identifier: BSD-3-Clause # @@ -91,7 +92,8 @@ async def test_stress_io_class_change_config_during_io_fs(): - IO class configuration changes successfully - No IO errors """ - cores_per_cache = len(list(Filesystem)) + filesystems = Filesystem.regular() + cores_per_cache = len(filesystems) with TestRun.step("Prepare devices."): cache_device = TestRun.disks['cache'] @@ -111,7 +113,7 @@ async def test_stress_io_class_change_config_during_io_fs(): generate_and_load_random_io_class_config(cache) with TestRun.step("Create different filesystem on each CAS device."): - for core, fs in zip(cores, Filesystem): + for core, fs in zip(cores, filesystems): core.create_filesystem(fs) core.mount(os.path.join("/mnt", fs.name)) From e6827452f6c6b359976555394a93cdbd0f068e3e Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Thu, 2 Jul 2026 22:49:21 +0200 Subject: [PATCH 4/5] tests: wlth: Run fio without hint first Signed-off-by: Robert Baldyga --- .../tests/io_class/test_io_class_wlth.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/test/functional/tests/io_class/test_io_class_wlth.py b/test/functional/tests/io_class/test_io_class_wlth.py index 32c3c366d..9b325ad4c 100644 --- a/test/functional/tests/io_class/test_io_class_wlth.py +++ b/test/functional/tests/io_class/test_io_class_wlth.py @@ -66,9 +66,9 @@ def test_ioclass_wlth(): cache.purge_cache() cache.reset_counters() - with TestRun.step("Trigger IO with a write life time hint"): - # Fio adds hints only to direct IO. Even if `write_hint` param isn't provided, direct IO - # has assigned a hint by default + with TestRun.step("Trigger IO without write life time hint"): + # Trigger the un-hinted IO first. On kernels that store the hint on the inode (>= 6.9), + # the hint persists on the /dev/cas1-1 inode until cleared with F_SET_RW_HINT. io_count = 12345 io_size = Size(io_count, Unit.Blocks4096) bs = Size(1, Unit.Blocks4096) @@ -78,14 +78,16 @@ def test_ioclass_wlth(): .io_engine(IoEngine.libaio) .size(io_size) .block_size(bs) - .write_hint("long") .read_write(ReadWrite.write) .target(core.path) - .direct() .run() ) + sync() + drop_caches() - with TestRun.step("Trigger IO without write life time hint"): + with TestRun.step("Trigger IO with a write life time hint"): + # Fio adds hints only to direct IO. Even if `write_hint` param isn't provided, direct IO + # has assigned a hint by default ( Fio() .create_command() @@ -93,12 +95,12 @@ def test_ioclass_wlth(): .size(io_size) .offset(io_size) .block_size(bs) + .write_hint("long") .read_write(ReadWrite.write) .target(core.path) + .direct() .run() ) - sync() - drop_caches() with TestRun.step("Check stats"): default_io_class_stats = core.get_io_class_statistics(io_class_id=0) From 34369343721ab98fb9327162475e7018400fe341 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Fri, 3 Jul 2026 00:28:18 +0200 Subject: [PATCH 5/5] tests: discard: Add missing cache stop Signed-off-by: Robert Baldyga --- test/functional/tests/volumes/test_discard_on_huge_core.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/functional/tests/volumes/test_discard_on_huge_core.py b/test/functional/tests/volumes/test_discard_on_huge_core.py index 64799bbbe..7b7dac93a 100644 --- a/test/functional/tests/volumes/test_discard_on_huge_core.py +++ b/test/functional/tests/volumes/test_discard_on_huge_core.py @@ -54,6 +54,9 @@ def test_discard_on_huge_core(): with TestRun.step("Check dmesg for RCU-sched stall."): check_for_rcu_sched_type_stall() + with TestRun.step("Stop cache."): + cache.stop() + with TestRun.step("Unload scsi_debug module."): scsi_debug.unload()