diff --git a/modules/cas_cache/classifier.c b/modules/cas_cache/classifier.c index e2a64a0..54932ea 100644 --- a/modules/cas_cache/classifier.c +++ b/modules/cas_cache/classifier.c @@ -190,14 +190,21 @@ error: static int _cas_cls_string_ctr(struct cas_classifier *cls, struct cas_cls_condition *c, char *data) { + size_t len; struct cas_cls_string *ctx; - if (!data || strlen(data) == 0) { + if (!data) { CAS_CLS_MSG(KERN_ERR, "Missing string specifier\n"); return -EINVAL; } - if (strlen(data) > MAX_STRING_SPECIFIER_LEN) { + len = strlen(data); + if (len == 0) { + CAS_CLS_MSG(KERN_ERR, "String specifier is empty\n"); + return -EINVAL; + } + + if (len > MAX_STRING_SPECIFIER_LEN) { CAS_CLS_MSG(KERN_ERR, "String specifier to long: %s\n", data); return -EINVAL; } @@ -207,6 +214,7 @@ static int _cas_cls_string_ctr(struct cas_classifier *cls, return -ENOMEM; strcpy(ctx->string, data); + ctx->len = len; c->context = ctx; @@ -477,6 +485,41 @@ static cas_cls_eval_t _cas_cls_extension_test( return cas_cls_eval_no; } +/* File name prefix test function */ +static cas_cls_eval_t _cas_cls_file_name_prefix_test( + struct cas_classifier *cls, struct cas_cls_condition *c, + struct cas_cls_io *io, ocf_part_id_t part_id) +{ + struct cas_cls_string *ctx; + struct inode *inode; + struct dentry *dentry; + uint32_t len; + + ctx = c->context; + inode = io->inode; + + if (!inode) + return cas_cls_eval_no; + + /* I/O target inode dentry */ + dentry = _cas_cls_dir_get_inode_dentry(inode); + + /* Check if dentry and its name is valid */ + if (!dentry || !dentry->d_name.name) + return cas_cls_eval_no; + + /* Check if name is not too short, we expect full prefix in name */ + if (dentry->d_name.len < ctx->len) + return cas_cls_eval_no; + + /* Final string comparison check */ + len = min(ctx->len, dentry->d_name.len); + if (strncmp(dentry->d_name.name, ctx->string, len) == 0) + return cas_cls_eval_yes; + + return cas_cls_eval_no; +} + /* LBA test function */ static cas_cls_eval_t _cas_cls_lba_test( struct cas_classifier *cls, struct cas_cls_condition *c, @@ -566,6 +609,8 @@ static struct cas_cls_condition_handler _handlers[] = { _cas_cls_directory_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, + _cas_cls_generic_dtr }, { "lba", _cas_cls_lba_test, _cas_cls_numeric_ctr, _cas_cls_generic_dtr }, { "pid", _cas_cls_pid_test, _cas_cls_numeric_ctr, _cas_cls_generic_dtr }, { "process_name", _cas_cls_process_name_test, _cas_cls_string_ctr, diff --git a/modules/cas_cache/classifier_defs.h b/modules/cas_cache/classifier_defs.h index 52c4a58..ec5b7bb 100644 --- a/modules/cas_cache/classifier_defs.h +++ b/modules/cas_cache/classifier_defs.h @@ -124,6 +124,9 @@ struct cas_cls_numeric { struct cas_cls_string { /* String specifier*/ char string[MAX_STRING_SPECIFIER_LEN]; + + /* String length */ + uint32_t len; }; /* Directory condition context */ diff --git a/test/functional/api/cas/ioclass_config.py b/test/functional/api/cas/ioclass_config.py index 1830304..8ac7b23 100644 --- a/test/functional/api/cas/ioclass_config.py +++ b/test/functional/api/cas/ioclass_config.py @@ -99,8 +99,9 @@ class IoClass: return random_list def set_random_rule(self): - rules = ["metadata", "direct", "file_size", "directory", "io_class", "extension", "lba", - "pid", "process_name", "file_offset", "request_size"] + rules = ["metadata", "direct", "file_size", "directory", "io_class", + "extension", "file_name_prefix", "lba", "pid", "process_name", + "file_offset", "request_size"] if os_utils.get_kernel_version() >= version.Version("4.13"): rules.append("wlth") @@ -117,7 +118,7 @@ class IoClass: rule += f":{Operator(random.randrange(len(Operator))).name}:{random.randrange(1000000)}" elif rule == "io_class": rule += f":{random.randrange(MAX_IO_CLASS_PRIORITY + 1)}" - elif rule in ["extension", "process_name"]: + elif rule in ["extension", "process_name", "file_name_prefix"]: rule += f":{random_string(random.randint(1, 10))}" if random.randrange(2): rule += "&done" diff --git a/test/functional/tests/io_class/test_io_class_file.py b/test/functional/tests/io_class/test_io_class_file.py index d4f0d70..20ee09c 100644 --- a/test/functional/tests/io_class/test_io_class_file.py +++ b/test/functional/tests/io_class/test_io_class_file.py @@ -77,6 +77,84 @@ def test_ioclass_file_extension(): assert stats["dirty"].get_value(Unit.Blocks4096) == 0 +@pytest.mark.require_disk("cache", DiskTypeSet([DiskType.optane, DiskType.nand])) +@pytest.mark.require_disk("core", DiskTypeLowerThan("cache")) +def test_ioclass_file_name_prefix(): + cache, core = prepare() + ioclass_id = 1 + cached_files = ["test", "test.txt", "test1", "test1.txt"] + not_cached_files = ["file1", "file2", "file4", "file5", "tes"] + dd_size = Size(4, Unit.KibiByte) + dd_count = 10 + + ioclass_config.remove_ioclass_config() + ioclass_config.create_ioclass_config(False) + + # Avoid caching anything else than files with specified prefix + ioclass_config.add_ioclass( + ioclass_id=0, + eviction_priority=255, + allocation=False, + rule=f"unclassified", + ioclass_config_path=ioclass_config_path, + ) + # Enables file with specified prefix to be cached + ioclass_config.add_ioclass( + ioclass_id=ioclass_id, + eviction_priority=1, + allocation=True, + rule=f"file_name_prefix:test&done", + ioclass_config_path=ioclass_config_path, + ) + casadm.load_io_classes(cache_id=cache.cache_id, file=ioclass_config_path) + + TestRun.LOGGER.info( + f"Preparing filesystem and mounting {core.system_path} at {mountpoint}" + ) + + previous_occupancy = cache.get_occupancy() + + core.create_filesystem(Filesystem.ext3) + core.mount(mountpoint) + + assert previous_occupancy.get_value() <= cache.get_occupancy().get_value() + + # Filesystem creation caused metadata IO which is not supposed + # to be cached + + # Check if files with proper prefix are cached + TestRun.LOGGER.info(f"Writing files which are supposed to be cached.") + for f in cached_files: + dd = ( + Dd() + .input("/dev/zero") + .output(f"{mountpoint}/{f}") + .count(dd_count) + .block_size(dd_size) + ) + dd.run() + sync() + current_occupancy = cache.get_occupancy() + assert current_occupancy == previous_occupancy + previous_occupancy = current_occupancy + + cache.flush_cache() + + # Check if file with improper extension is not cached + TestRun.LOGGER.info(f"Writing files which are not supposed to be cached.") + for f in not_cached_files: + dd = ( + Dd() + .input("/dev/zero") + .output(f"{mountpoint}/{f}") + .count(dd_count) + .block_size(dd_size) + ) + dd.run() + sync() + current_occupancy = cache.get_occupancy() + assert current_occupancy != previous_occupancy + @pytest.mark.require_disk("cache", DiskTypeSet([DiskType.optane, DiskType.nand])) @pytest.mark.require_disk("core", DiskTypeLowerThan("cache")) def test_ioclass_file_extension_preexisting_filesystem():