diff --git a/modules/cas_cache/classifier.c b/modules/cas_cache/classifier.c index 7b2ac5f..dbd3a83 100644 --- a/modules/cas_cache/classifier.c +++ b/modules/cas_cache/classifier.c @@ -448,6 +448,64 @@ static void _cas_cls_directory_dtr(struct cas_classifier *cls, kfree(ctx); } +/* Core id test function */ +static cas_cls_eval_t _cas_cls_core_id_test( + struct cas_classifier *cls, struct cas_cls_condition *c, + struct cas_cls_io *io, ocf_part_id_t part_id) +{ + char *core_id_str; + uint64_t core_id; + struct bio *bio = io->bio; + + core_id_str = strrchr(CAS_BIO_GET_DEV(bio)->disk_name, '-'); + if (!core_id_str) + return cas_cls_eval_no; + + /* First character of @core_id_str is '-', which we don't want to compare */ + core_id_str += 1; + + if (kstrtou64(core_id_str, 10, &core_id)) + return cas_cls_eval_no; + + return _cas_cls_numeric_test_u(c, core_id); +} + +/* Core id condition constructor */ +static int _cas_cls_core_id_ctr(struct cas_classifier *cls, + struct cas_cls_condition *c, char *data) +{ + struct cas_cls_numeric *ctx; + int result; + + result = _cas_cls_numeric_ctr(cls, c, data); + if (result) + return result; + + ctx = c->context; + + if (ctx->v_u64 < OCF_CORE_ID_MIN || ctx->v_u64 > OCF_CORE_ID_MAX) { + CAS_CLS_MSG(KERN_ERR, "Core id have to be within <%u-%u> range\n", + OCF_CORE_ID_MIN, OCF_CORE_ID_MAX); + result = -EINVAL; + goto error; + } + + return 0; + +error: + kfree(c->context); + return result; +} + +/* Core id condition destructor */ +static void _cas_cls_core_id_dtr(struct cas_classifier *cls, + struct cas_cls_condition *c) +{ + if (c->context) + kfree(c->context); + c->context = NULL; +} + /* File extension test function */ static cas_cls_eval_t _cas_cls_extension_test( struct cas_classifier *cls, struct cas_cls_condition *c, @@ -608,6 +666,8 @@ static struct cas_cls_condition_handler _handlers[] = { _cas_cls_generic_dtr }, { "directory", _cas_cls_directory_test, _cas_cls_directory_ctr, _cas_cls_directory_dtr }, + { "core_id", _cas_cls_core_id_test, _cas_cls_core_id_ctr, + _cas_cls_core_id_dtr }, { "extension", _cas_cls_extension_test, _cas_cls_string_ctr, _cas_cls_generic_dtr }, { "file_name_prefix", _cas_cls_file_name_prefix_test, _cas_cls_string_ctr, diff --git a/test/functional/tests/io_class/test_io_class_core_id.py b/test/functional/tests/io_class/test_io_class_core_id.py new file mode 100644 index 0000000..2dbeec3 --- /dev/null +++ b/test/functional/tests/io_class/test_io_class_core_id.py @@ -0,0 +1,193 @@ +# +# Copyright(c) 2019 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause-Clear +# + +import time + +import pytest + +from test_tools.disk_utils import Filesystem +from api.cas import ioclass_config, casadm +from api.cas.cache_config import CacheMode, CleaningPolicy, SeqCutOffPolicy +from storage_devices.disk import DiskType, DiskTypeSet, DiskTypeLowerThan +from test_tools.dd import Dd +from test_utils.os_utils import sync, Udev, drop_caches, DropCachesMode +from test_utils.size import Unit, Size +from core.test_run import TestRun + + +dd_bs = Size(1, Unit.Blocks4096) +dd_count = 1230 +cached_mountpoint = "/tmp/ioclass_core_id_test/cached" +not_cached_mountpoint = "/tmp/ioclass_core_id_test/not_cached" + + +@pytest.mark.require_disk("cache", DiskTypeSet([DiskType.optane, DiskType.nand])) +@pytest.mark.require_disk("core", DiskTypeLowerThan("cache")) +@pytest.mark.parametrize("filesystem", [fs for fs in Filesystem] + [None]) +def test_ioclass_core_id(filesystem): + """ + title: Test for `core_id` classification rule + dsecription: | + Test if IO to core with selective allocation enabled is cached and IO to core with + selective allocation disabled is redirected to pass-through mode + pass_criteria: + - IO to core with enabled selective allocation is cached + - IO to core with disabled selective allocation is not cached + """ + fs_info = f"with {filesystem}" if filesystem else "" + with TestRun.step( + f"Start cache with two cores on created partitions {fs_info}, " + "with NOP, disabled seq cutoff" + ): + cache, cores = prepare(filesystem, 2) + core_1, core_2 = cores[0], cores[1] + + with TestRun.step(f"Add core_id based classification rules"): + cached_ioclass_id = 11 + not_cached_ioclass_id = 12 + + ioclass_config.add_ioclass( + ioclass_id=cached_ioclass_id, + eviction_priority=22, + allocation=True, + rule=f"core_id:eq:{core_1.core_id}&done", + ioclass_config_path=ioclass_config.default_config_file_path, + ) + ioclass_config.add_ioclass( + ioclass_id=not_cached_ioclass_id, + eviction_priority=22, + allocation=False, + rule=f"core_id:eq:{core_2.core_id}&done", + ioclass_config_path=ioclass_config.default_config_file_path, + ) + + with TestRun.step(f"Load ioclass config file"): + casadm.load_io_classes( + cache_id=cache.cache_id, file=ioclass_config.default_config_file_path + ) + + if filesystem: + with TestRun.step(f"Mount cores"): + core_1.mount(cached_mountpoint) + core_2.mount(not_cached_mountpoint) + + with TestRun.step(f"Reset counters"): + sync() + drop_caches() + cache.purge_cache() + cache.reset_counters() + + with TestRun.step(f"Trigger IO to both cores"): + if filesystem: + dd_dst_paths = [cached_mountpoint + "/test_file", not_cached_mountpoint + "/test_file"] + else: + dd_dst_paths = [core_1.system_path, core_2.system_path] + + for path in dd_dst_paths: + dd = ( + Dd() + .input("/dev/zero") + .output(path) + .count(dd_count) + .block_size(dd_bs) + .oflag("sync") + ) + dd.run() + sync() + drop_caches() + + with TestRun.step(f"Check cores occupancy"): + dd_size = (dd_bs * dd_count).set_unit(Unit.Blocks4096) + + core_1_occupancy = core_1.get_statistics().usage_stats.occupancy + core_2_occupancy = core_2.get_statistics().usage_stats.occupancy + + if core_1_occupancy < dd_size: + TestRun.LOGGER.error( + f"First core's occupancy is {core_1_occupancy} " + f"- it is less than {dd_size} - triggerd IO size!" + ) + + if core_2_occupancy.get_value() != 0: + TestRun.LOGGER.error(f"First core's occupancy is {core_2_occupancy} instead of 0!") + + with TestRun.step(f"Check ioclasses occupancy"): + cached_ioclass_occupancy = cache.get_io_class_statistics( + io_class_id=cached_ioclass_id + ).usage_stats.occupancy + not_cached_ioclass_occupancy = cache.get_io_class_statistics( + io_class_id=not_cached_ioclass_id + ).usage_stats.occupancy + + if cached_ioclass_occupancy < dd_size: + TestRun.LOGGER.error( + f"Cached ioclass occupancy is {cached_ioclass_occupancy} " + f"- it is less than {dd_size} - triggerd IO size!" + ) + if not_cached_ioclass_occupancy.get_value() != 0: + TestRun.LOGGER.error( + f"Not cached ioclass occupancy is {not_cached_ioclass_occupancy} instead of 0!" + ) + + with TestRun.step(f"Check number of serviced requests to not cached core"): + core_2_serviced_requests = core_2.get_statistics().request_stats.requests_serviced + if core_2_serviced_requests != 0: + TestRun.LOGGER.error( + f"Second core should have 0 serviced requests " + f"instead of {core_2_serviced_requests}" + ) + + +def prepare(filesystem, cores_number): + ioclass_config.remove_ioclass_config() + cache_device = TestRun.disks["cache"] + core_device = TestRun.disks["core"] + + cache_device.create_partitions([Size(10, Unit.GibiByte)]) + core_device.create_partitions([Size(5, Unit.GibiByte)] * cores_number) + + cache_device = cache_device.partitions[0] + + cache = casadm.start_cache(cache_device, cache_mode=CacheMode.WT, force=True) + + Udev.disable() + casadm.set_param_cleaning(cache_id=cache.cache_id, policy=CleaningPolicy.nop) + + cores = [] + for part in core_device.partitions: + if filesystem: + part.create_filesystem(filesystem) + cores.append(casadm.add_core(cache, core_dev=part)) + + cache.set_seq_cutoff_policy(SeqCutOffPolicy.never) + + ioclass_config.create_ioclass_config( + add_default_rule=False, ioclass_config_path=ioclass_config.default_config_file_path + ) + # To make test more precise all workload except of tested ioclass should be + # put in pass-through mode + ioclass_config.add_ioclass( + ioclass_id=0, + eviction_priority=22, + allocation=True, + rule="unclassified", + ioclass_config_path=ioclass_config.default_config_file_path, + ) + ioclass_config.add_ioclass( + ioclass_id=1, + eviction_priority=22, + allocation=True, + rule="metadata", + ioclass_config_path=ioclass_config.default_config_file_path, + ) + ioclass_config.add_ioclass( + ioclass_id=2, + eviction_priority=22, + allocation=False, + rule="direct", + ioclass_config_path=ioclass_config.default_config_file_path, + ) + + return cache, cores