diff --git a/tests/functional/tests/eviction/test_eviction.py b/tests/functional/tests/eviction/test_eviction.py index a55ce14..7a004c9 100644 --- a/tests/functional/tests/eviction/test_eviction.py +++ b/tests/functional/tests/eviction/test_eviction.py @@ -4,6 +4,7 @@ # import logging +from math import ceil, isclose from ctypes import c_int import pytest @@ -12,7 +13,7 @@ from pyocf.types.cache import Cache, CacheMode from pyocf.types.core import Core from pyocf.types.data import Data from pyocf.types.io import IoDir -from pyocf.types.shared import OcfCompletion, CacheLineSize, SeqCutOffPolicy +from pyocf.types.shared import OcfCompletion, CacheLineSize, SeqCutOffPolicy, CacheLines from pyocf.types.volume import Volume from pyocf.utils import Size @@ -74,10 +75,89 @@ def test_write_size_greater_than_cache(pyocf_ctx, mode: CacheMode, cls: CacheLin io_size_bigger_than_cache.blocks_4k -def send_io(exported_obj: Core, data: Data, addr: int = 0): +@pytest.mark.parametrize("cls", CacheLineSize) +def test_evict_overflown_pinned(pyocf_ctx, cls: CacheLineSize): + """ Verify if overflown pinned ioclass is evicted """ + cache_device = Volume(Size.from_MiB(35)) + core_device = Volume(Size.from_MiB(100)) + cache = Cache.start_on_device( + cache_device, cache_mode=CacheMode.WT, cache_line_size=cls + ) + core = Core.using_device(core_device) + cache.add_core(core) + + test_ioclass_id = 1 + pinned_ioclass_id = 2 + pinned_ioclass_max_occupancy = 10 + + cache.configure_partition( + part_id=test_ioclass_id, + name="default_ioclass", + min_size=0, + max_size=100, + priority=1, + ) + cache.configure_partition( + part_id=pinned_ioclass_id, + name="pinned_ioclass", + min_size=0, + max_size=pinned_ioclass_max_occupancy, + priority=-1, + ) + + cache.set_seq_cut_off_policy(SeqCutOffPolicy.NEVER) + + cache_size = cache.get_stats()["conf"]["size"] + + data = Data(4096) + + # Populate cache with data + for i in range(cache_size.blocks_4k): + send_io(core, data, i * 4096, test_ioclass_id) + + part_current_size = CacheLines( + cache.get_partition_info(part_id=test_ioclass_id)["_curr_size"], cls + ) + assert isclose( + part_current_size.blocks_4k, cache_size.blocks_4k, abs_tol=Size(cls).blocks_4k + ), "Failed to populate the default partition" + + # Repart - force overflow of second partition occupancy limit + pinned_double_size = ceil( + (cache_size.blocks_4k * pinned_ioclass_max_occupancy * 2) / 100 + ) + for i in range(pinned_double_size): + send_io(core, data, i * 4096, pinned_ioclass_id) + + part_current_size = CacheLines( + cache.get_partition_info(part_id=pinned_ioclass_id)["_curr_size"], cls + ) + assert isclose( + part_current_size.blocks_4k, pinned_double_size, abs_tol=Size(cls).blocks_4k + ), "Occupancy of pinned ioclass doesn't match expected value" + + # Trigger IO to the default ioclass - force eviction from overlown ioclass + for i in range(cache_size.blocks_4k): + send_io(core, data, (cache_size.blocks_4k + i) * 4096, test_ioclass_id) + + part_current_size = CacheLines( + cache.get_partition_info(part_id=pinned_ioclass_id)["_curr_size"], cls + ) + assert isclose( + part_current_size.blocks_4k, + ceil(cache_size.blocks_4k * 0.1), + abs_tol=Size(cls).blocks_4k, + ), "Overflown part has not been evicted" + + +def send_io(exported_obj: Core, data: Data, addr: int = 0, target_ioclass: int = 0): io = exported_obj.new_io( exported_obj.cache.get_default_queue(), - addr, data.size, IoDir.WRITE, 0, 0 + addr, + data.size, + IoDir.WRITE, + target_ioclass, + 0, ) io.set_data(data)