def test_ioclass_stats_set(prepare_and_cleanup): """Try to retrieve stats for all set ioclasses""" prepare() min_ioclass_id = 1 max_ioclass_id = 11 ioclass_config.create_ioclass_config( add_default_rule=True, ioclass_config_path=ioclass_config_path ) TestProperties.LOGGER.info("Preparing ioclass config file") for i in range(min_ioclass_id, max_ioclass_id): ioclass_config.add_ioclass( ioclass_id=(i + 10), eviction_priority=22, allocation=True, rule=f"file_size:le:{4096*i}&done", ioclass_config_path=ioclass_config_path, ) casadm.load_io_classes(cache_id, file=ioclass_config_path) TestProperties.LOGGER.info("Preparing ioclass config file") for i in range(32): if i != 0 or i not in range(min_ioclass_id, max_ioclass_id): with pytest.raises(Exception): assert casadm_parser.get_statistics( cache_id=cache_id, io_class_id=True, filter=[StatsFilter.conf] )
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="1.00", rule="unclassified", ioclass_config_path=ioclass_config.default_config_file_path, ) ioclass_config.add_ioclass( ioclass_id=1, eviction_priority=22, allocation="0.00", rule="metadata", ioclass_config_path=ioclass_config.default_config_file_path, ) ioclass_config.add_ioclass( ioclass_id=2, eviction_priority=22, allocation="0.00", rule="direct", ioclass_config_path=ioclass_config.default_config_file_path, ) return cache, cores
def load_io_classes_in_permutation_order(rules, permutation, cache): ioclass_config.remove_ioclass_config(ioclass_config_path=ioclass_config_path) ioclass_config.create_ioclass_config( add_default_rule=False, ioclass_config_path=ioclass_config_path ) # To make test more precise all workload except of tested ioclass should be # put in pass-through mode ioclass_list = [IoClass.default(allocation=False)] for n in range(len(rules)): ioclass_list.append(IoClass(class_id=permutation[n], rule=rules[n])) IoClass.save_list_to_config_file(ioclass_list, add_default_rule=False, ioclass_config_path=ioclass_config_path) casadm.load_io_classes(cache.cache_id, file=ioclass_config_path)
def prepare( cache_size=Size(500, Unit.MebiByte), core_size=Size(10, Unit.GibiByte), cache_mode=CacheMode.WB, cache_line_size=CacheLineSize.LINE_4KiB, ): ioclass_config.remove_ioclass_config() cache_device = TestRun.disks["cache"] core_device = TestRun.disks["core"] cache_device.create_partitions([cache_size]) core_device.create_partitions([core_size]) cache_device = cache_device.partitions[0] core_device = core_device.partitions[0] TestRun.LOGGER.info(f"Starting cache") cache = casadm.start_cache(cache_device, cache_mode=cache_mode, cache_line_size=cache_line_size, force=True) Udev.disable() TestRun.LOGGER.info(f"Setting cleaning policy to NOP") casadm.set_param_cleaning(cache_id=cache.cache_id, policy=CleaningPolicy.nop) TestRun.LOGGER.info(f"Adding core device") core = casadm.add_core(cache, core_dev=core_device) TestRun.LOGGER.info(f"Setting seq cutoff policy to never") core.set_seq_cutoff_policy(SeqCutOffPolicy.never) ioclass_config.create_ioclass_config( add_default_rule=False, ioclass_config_path=ioclass_config_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=ioclass_config.DEFAULT_IO_CLASS_ID, eviction_priority=ioclass_config.DEFAULT_IO_CLASS_PRIORITY, allocation="0.00", rule=ioclass_config.DEFAULT_IO_CLASS_RULE, ioclass_config_path=ioclass_config_path, ) output = TestRun.executor.run(f"mkdir -p {mountpoint}") if output.exit_code != 0: raise Exception(f"Failed to create mountpoint") return cache, core
def prepare(): base_prepare() ioclass_config.remove_ioclass_config() cache_device = next( filter(lambda disk: disk.disk_type in [DiskType.optane, DiskType.nand], TestRun.dut.disks)) core_device = next( filter( lambda disk: disk.disk_type.value > cache_device.disk_type.value, TestRun.dut.disks)) cache_device.create_partitions([Size(500, Unit.MebiByte)]) core_device.create_partitions([Size(1, Unit.GibiByte)]) cache_device = cache_device.partitions[0] core_device = core_device.partitions[0] TestRun.LOGGER.info(f"Starting cache") cache = casadm.start_cache(cache_device, cache_mode=CacheMode.WB, force=True) TestRun.LOGGER.info(f"Setting cleaning policy to NOP") casadm.set_param_cleaning(cache_id=cache.cache_id, policy=CleaningPolicy.nop) TestRun.LOGGER.info(f"Adding core device") core = casadm.add_core(cache, core_dev=core_device) ioclass_config.create_ioclass_config( add_default_rule=False, ioclass_config_path=ioclass_config_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=False, rule="unclassified", ioclass_config_path=ioclass_config_path, ) output = TestRun.executor.run(f"mkdir -p {mountpoint}") if output.exit_code != 0: raise Exception(f"Failed to create mountpoint") return cache, core
def prepare(): ioclass_config.remove_ioclass_config() cache_device = TestRun.disks['cache'] core_device = TestRun.disks['core'] cache_device.create_partitions([Size(500, Unit.MebiByte)]) core_device.create_partitions([Size(1, Unit.GibiByte)]) cache_device = cache_device.partitions[0] core_device = core_device.partitions[0] TestRun.LOGGER.info(f"Starting cache") cache = casadm.start_cache(cache_device, cache_mode=CacheMode.WB, force=True) Udev.disable() TestRun.LOGGER.info(f"Setting cleaning policy to NOP") casadm.set_param_cleaning(cache_id=cache.cache_id, policy=CleaningPolicy.nop) TestRun.LOGGER.info(f"Adding core device") core = casadm.add_core(cache, core_dev=core_device) core.set_seq_cutoff_policy(SeqCutOffPolicy.never) ioclass_config.create_ioclass_config( add_default_rule=False, ioclass_config_path=ioclass_config_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=False, rule="unclassified", ioclass_config_path=ioclass_config_path, ) output = TestRun.executor.run(f"mkdir -p {mountpoint}") if output.exit_code != 0: raise Exception(f"Failed to create mountpoint") return cache, core
def test_ioclass_resize(cache_line_size, new_occupancy): """ title: Resize ioclass description: | Add ioclass, fill it with data, change it's size and check if new limit is respected pass_criteria: - Occupancy threshold is respected """ with TestRun.step("Prepare CAS device"): cache, core = prepare(cache_mode=CacheMode.WT, cache_line_size=cache_line_size) cache_size = cache.get_statistics().config_stats.cache_size with TestRun.step("Disable udev"): Udev.disable() with TestRun.step( f"Prepare filesystem and mount {core.path} at {mountpoint}"): filesystem = Filesystem.xfs core.create_filesystem(filesystem) core.mount(mountpoint) sync() with TestRun.step("Prepare test dirs"): IoclassConfig = recordclass("IoclassConfig", "id eviction_prio max_occupancy dir_path") io_class = IoclassConfig(2, 3, 0.10, f"{mountpoint}/A") fs_utils.create_directory(io_class.dir_path, parents=True) with TestRun.step("Remove old ioclass config"): ioclass_config.remove_ioclass_config() ioclass_config.create_ioclass_config(False) with TestRun.step("Add default ioclasses"): ioclass_config.add_ioclass(ioclass_id=1, rule="metadata&done", eviction_priority=1, allocation="1.00", ioclass_config_path=ioclass_config_path) ioclass_config.add_ioclass(*str(IoClass.default( allocation="0.00")).split(",")) with TestRun.step("Add directory for ioclass"): ioclass_config.add_ioclass( io_class.id, f"directory:{io_class.dir_path}&done", io_class.eviction_prio, f"{io_class.max_occupancy:0.2f}", ) casadm.load_io_classes(cache_id=cache.cache_id, file=ioclass_config_path) with TestRun.step("Reset cache stats"): cache.purge_cache() cache.reset_counters() with TestRun.step("Check initial occupancy"): occupancy = get_io_class_occupancy(cache, io_class.id) if occupancy.get_value() != 0: TestRun.LOGGER.error( f"Incorrect inital occupancy for ioclass id: {io_class.id}." f" Expected 0, got: {occupancy}") with TestRun.step(f"Perform IO with size equal to cache size"): run_io_dir(f"{io_class.dir_path}/tmp_file", int((cache_size) / Unit.Blocks4096)) with TestRun.step( "Check if the ioclass did not exceed specified occupancy"): actuall_occupancy = get_io_class_occupancy(cache, io_class.id) occupancy_limit = ((io_class.max_occupancy * cache_size).align_up( Unit.Blocks4096.get_value()).set_unit(Unit.Blocks4096)) # Divergency may be casued be rounding max occupancy if actuall_occupancy > occupancy_limit + Size(100, Unit.Blocks4096): TestRun.LOGGER.error( f"Occupancy for ioclass id exceeded: {io_class.id}. " f"Limit: {occupancy_limit}, actuall: {actuall_occupancy}") with TestRun.step( f"Resize ioclass from {io_class.max_occupancy*100}% to {new_occupancy}%" " cache occupancy"): io_class.max_occupancy = new_occupancy / 100 ioclass_config.remove_ioclass_config() ioclass_config.create_ioclass_config(False) ioclass_config.add_ioclass(*str(IoClass.default( allocation="0.00")).split(",")) ioclass_config.add_ioclass(ioclass_id=1, rule="metadata&done", eviction_priority=1, allocation="1.00", ioclass_config_path=ioclass_config_path) ioclass_config.add_ioclass( io_class.id, f"directory:{io_class.dir_path}&done", io_class.eviction_prio, f"{io_class.max_occupancy:0.2f}", ) casadm.load_io_classes(cache_id=cache.cache_id, file=ioclass_config_path) with TestRun.step(f"Perform IO with size equal to cache size"): run_io_dir(f"{io_class.dir_path}/tmp_file", int((cache_size) / Unit.Blocks4096)) with TestRun.step( "Check if the ioclass did not exceed specified occupancy"): actuall_occupancy = get_io_class_occupancy(cache, io_class.id) occupancy_limit = ((io_class.max_occupancy * cache_size).align_up( Unit.Blocks4096.get_value()).set_unit(Unit.Blocks4096)) # Divergency may be casued be rounding max occupancy if actuall_occupancy > occupancy_limit + Size(100, Unit.Blocks4096): TestRun.LOGGER.error( f"Occupancy for ioclass id exceeded: {io_class.id}. " f"Limit: {occupancy_limit}, actuall: {actuall_occupancy}")
def test_trim_eviction(cache_mode, cache_line_size, filesystem, cleaning): """ title: Test verifying if trim requests do not cause eviction on CAS device. description: | When trim requests enabled and files are being added and removed from CAS device, there is no eviction (no reads from cache). pass_criteria: - Reads from cache device are the same before and after removing test file. """ mount_point = "/mnt" test_file_path = os.path.join(mount_point, "test_file") with TestRun.step("Prepare devices."): cache_disk = TestRun.disks['cache'] cache_disk.create_partitions([Size(1, Unit.GibiByte)]) cache_dev = cache_disk.partitions[0] core_disk = TestRun.disks['core'] core_disk.create_partitions([Size(1, Unit.GibiByte)]) core_dev = core_disk.partitions[0] cache_block_size = disk_utils.get_block_size(cache_disk) with TestRun.step("Start cache on device supporting trim and add core."): cache = casadm.start_cache(cache_dev, cache_mode, cache_line_size, force=True) cache.set_cleaning_policy(cleaning) Udev.disable() core = cache.add_core(core_dev) with TestRun.step("Create filesystem on CAS device and mount it."): core.create_filesystem(filesystem) core.mount(mount_point, ["discard"]) with TestRun.step("Create ioclass config."): ioclass_config.create_ioclass_config() ioclass_config.add_ioclass(ioclass_id=1, eviction_priority=1, allocation="0.00", rule=f"metadata") casadm.load_io_classes(cache_id=cache.cache_id, file=ioclass_config.default_config_file_path) with TestRun.step("Create random file using ddrescue."): test_file = create_file_with_ddrescue(core_dev, test_file_path) os_utils.sync() os_utils.drop_caches() time.sleep(ioclass_config.MAX_CLASSIFICATION_DELAY.seconds) with TestRun.step("Remove file and create a new one."): cache_iostats_before = cache_dev.get_io_stats() data_reads_before = cache.get_io_class_statistics( io_class_id=0).block_stats.cache.reads metadata_reads_before = cache.get_io_class_statistics( io_class_id=1).block_stats.cache.reads test_file.remove() os_utils.sync() os_utils.drop_caches() create_file_with_ddrescue(core_dev, test_file_path) os_utils.sync() os_utils.drop_caches() time.sleep(ioclass_config.MAX_CLASSIFICATION_DELAY.seconds) with TestRun.step( "Check using iostat that reads from cache did not occur."): cache_iostats_after = cache_dev.get_io_stats() data_reads_after = cache.get_io_class_statistics( io_class_id=0).block_stats.cache.reads metadata_reads_after = cache.get_io_class_statistics( io_class_id=1).block_stats.cache.reads reads_before = cache_iostats_before.sectors_read reads_after = cache_iostats_after.sectors_read metadata_reads_diff = metadata_reads_after - metadata_reads_before data_reads_diff = data_reads_after - data_reads_before iostat_diff = (reads_after - reads_before) * cache_block_size if iostat_diff > int(metadata_reads_diff) or int(data_reads_diff) > 0: TestRun.fail( f"Number of reads from cache before and after removing test file " f"differs. Sectors read before: {reads_before}, sectors read after: {reads_after}." f"Data read from cache before {data_reads_before}, after {data_reads_after}." f"Metadata read from cache before {metadata_reads_before}, " f"after {metadata_reads_after}.") else: TestRun.LOGGER.info( "Number of reads from cache before and after removing test file is the same." )
def test_ioclass_occuppancy_load(cache_line_size): """ title: Load cache with occupancy limit specified description: | Load cache and verify if occupancy limits are loaded correctly and if each part has assigned apropriate number of dirty blocks. pass_criteria: - Occupancy thresholds have correct values for each ioclass after load """ with TestRun.step("Prepare CAS device"): cache, core = prepare(cache_mode=CacheMode.WB, cache_line_size=cache_line_size) cache_size = cache.get_statistics().config_stats.cache_size with TestRun.step("Disable udev"): Udev.disable() with TestRun.step( f"Prepare filesystem and mount {core.path} at {mountpoint}"): filesystem = Filesystem.xfs core.create_filesystem(filesystem) core.mount(mountpoint) sync() with TestRun.step("Prepare test dirs"): IoclassConfig = namedtuple("IoclassConfig", "id eviction_prio max_occupancy dir_path") io_classes = [ IoclassConfig(1, 3, 0.30, f"{mountpoint}/A"), IoclassConfig(2, 3, 0.30, f"{mountpoint}/B"), IoclassConfig(3, 3, 0.30, f"{mountpoint}/C"), ] for io_class in io_classes: fs_utils.create_directory(io_class.dir_path, parents=True) with TestRun.step("Remove old ioclass config"): ioclass_config.remove_ioclass_config() ioclass_config.create_ioclass_config(False) with TestRun.step("Add default ioclasses"): ioclass_config.add_ioclass(*str(IoClass.default( allocation="0.00")).split(",")) with TestRun.step("Add ioclasses for all dirs"): for io_class in io_classes: ioclass_config.add_ioclass( io_class.id, f"directory:{io_class.dir_path}&done", io_class.eviction_prio, f"{io_class.max_occupancy:0.2f}", ) casadm.load_io_classes(cache_id=cache.cache_id, file=ioclass_config_path) with TestRun.step("Reset cache stats"): cache.purge_cache() cache.reset_counters() with TestRun.step("Check initial occupancy"): for io_class in io_classes: occupancy = get_io_class_occupancy(cache, io_class.id) if occupancy.get_value() != 0: TestRun.LOGGER.error( f"Incorrect inital occupancy for ioclass id: {io_class.id}." f" Expected 0, got: {occupancy}") with TestRun.step(f"Perform IO with size equal to cache size"): for io_class in io_classes: run_io_dir(f"{io_class.dir_path}/tmp_file", int((cache_size) / Unit.Blocks4096)) with TestRun.step( "Check if the ioclass did not exceed specified occupancy"): for io_class in io_classes: actuall_dirty = get_io_class_dirty(cache, io_class.id) dirty_limit = ((io_class.max_occupancy * cache_size).align_down( Unit.Blocks4096.get_value()).set_unit(Unit.Blocks4096)) if not isclose(actuall_dirty.get_value(), dirty_limit.get_value(), rel_tol=0.1): TestRun.LOGGER.error( f"Dirty for ioclass id: {io_class.id} doesn't match expected." f"Expected: {dirty_limit}, actuall: {actuall_dirty}") with TestRun.step("Stop cache without flushing the data"): original_usage_stats = {} for io_class in io_classes: original_usage_stats[io_class.id] = get_io_class_usage( cache, io_class.id) original_ioclass_list = cache.list_io_classes() cache_disk_path = cache.cache_device.path core.unmount() cache.stop(no_data_flush=True) with TestRun.step("Load cache"): cache = casadm.start_cache(Device(cache_disk_path), load=True) with TestRun.step( "Check if the ioclass did not exceed specified occupancy"): for io_class in io_classes: actuall_dirty = get_io_class_dirty(cache, io_class.id) dirty_limit = ((io_class.max_occupancy * cache_size).align_down( Unit.Blocks4096.get_value()).set_unit(Unit.Blocks4096)) if not isclose(actuall_dirty.get_value(), dirty_limit.get_value(), rel_tol=0.1): TestRun.LOGGER.error( f"Dirty for ioclass id: {io_class.id} doesn't match expected." f"Expected: {dirty_limit}, actuall: {actuall_dirty}") with TestRun.step("Compare ioclass configs"): ioclass_list_after_load = cache.list_io_classes() if len(ioclass_list_after_load) != len(original_ioclass_list): TestRun.LOGGER.error( f"Ioclass occupancy limit doesn't match. Original list size: " f"{len(original_ioclass_list)}, loaded list size: " f"{len(ioclass_list_after_load)}") original_sorted = sorted(original_ioclass_list, key=lambda k: k["id"]) loaded_sorted = sorted(ioclass_list_after_load, key=lambda k: k["id"]) for original, loaded in zip(original_sorted, loaded_sorted): original_allocation = original["allocation"] loaded_allocation = loaded["allocation"] ioclass_id = original["id"] if original_allocation != loaded_allocation: TestRun.LOGGER.error( f"Occupancy limit doesn't match for ioclass {ioclass_id}: " f"Original: {original_allocation}, loaded: {loaded_allocation}" ) with TestRun.step("Compare usage stats before and after the load"): for io_class in io_classes: actuall_usage_stats = get_io_class_usage(cache, io_class.id) if original_usage_stats[io_class.id] != actuall_usage_stats: TestRun.LOGGER.error( f"Usage stats doesn't match for ioclass {io_class.id}. " f"Original: {original_usage_stats[io_class.id]}, " f"loaded: {actuall_usage_stats}")
def test_ioclass_file_name_prefix(): """ title: Test IO classification by file name prefix. description: Test if file name prefix classification works properly. pass_criteria: - No kernel bug. - IO is classified properly based on IO class rule with file name prefix. """ 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 with TestRun.step("Prepare cache and core."): cache, core = prepare() with TestRun.step("Create and load IO class config."): 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="0.00", 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="1.00", 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) with TestRun.step( f"Prepare filesystem and mount {core.path} at {mountpoint}"): previous_occupancy = cache.get_occupancy() core.create_filesystem(Filesystem.ext3) core.mount(mountpoint) current_occupancy = cache.get_occupancy() if previous_occupancy.get_value() > current_occupancy.get_value(): TestRun.fail( f"Current occupancy ({str(current_occupancy)}) is lower " f"than before ({str(previous_occupancy)}).") # Filesystem creation caused metadata IO which is not supposed # to be cached # Check if files with proper prefix are cached with TestRun.step(f"Write files which are supposed to be cached and check " f"if they are 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() expected_occupancy = previous_occupancy + (dd_size * dd_count) if current_occupancy != expected_occupancy: TestRun.fail(f"Current occupancy value is not valid. " f"(Expected: {str(expected_occupancy)}, " f"actual: {str(current_occupancy)})") previous_occupancy = current_occupancy with TestRun.step("Flush cache."): cache.flush_cache() # Check if file with improper extension is not cached with TestRun.step( f"Write files which are not supposed to be cached and check if " f"they are not 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() if current_occupancy != previous_occupancy: TestRun.fail(f"Current occupancy value is not valid. " f"(Expected: {str(previous_occupancy)}, " f"actual: {str(current_occupancy)})")
def test_ioclass_file_size(filesystem): """ title: Test IO classification by file size. description: Test if file size classification works properly. pass_criteria: - No kernel bug. - IO is classified properly based on IO class rule with file size. """ # File size IO class rules are configured in a way that each tested file size is unambiguously # classified. # Firstly write operations are tested (creation of files), secondly read operations. base_size = Size(random.randint(50, 1000) * 2, Unit.Blocks4096) size_to_class = { base_size: 1, base_size - Unit.Blocks4096: 2, base_size + Unit.Blocks4096: 3, base_size / 2: 4, base_size / 2 - Unit.Blocks4096: 4, base_size / 2 + Unit.Blocks4096: 2, base_size * 2: 5, base_size * 2 - Unit.Blocks4096: 3, base_size * 2 + Unit.Blocks4096: 5, } with TestRun.step("Prepare cache and core."): cache, core = prepare(default_allocation="1.00") with TestRun.step("Prepare and load IO class config."): load_file_size_io_classes(cache, base_size) with TestRun.step( f"Prepare {filesystem.name} filesystem and mount {core.path} " f"at {mountpoint}."): core.create_filesystem(filesystem) core.mount(mountpoint) sync() with TestRun.step( "Create files belonging to different IO classes (classification by writes)." ): test_files = [] for size, ioclass_id in size_to_class.items(): occupancy_before = cache.get_io_class_statistics( io_class_id=ioclass_id).usage_stats.occupancy file_path = f"{mountpoint}/test_file_{size.get_value()}" Dd().input("/dev/zero").output(file_path).oflag("sync").block_size( size).count(1).run() sync() drop_caches(DropCachesMode.ALL) occupancy_after = cache.get_io_class_statistics( io_class_id=ioclass_id).usage_stats.occupancy if occupancy_after != occupancy_before + size: TestRun.fail("File not cached properly!\n" f"Expected {occupancy_before + size}\n" f"Actual {occupancy_after}") test_files.append(File(file_path).refresh_item()) sync() drop_caches(DropCachesMode.ALL) with TestRun.step("Move all files to 'unclassified' IO class."): ioclass_config.remove_ioclass_config( ioclass_config_path=ioclass_config_path) ioclass_config.create_ioclass_config( add_default_rule=False, ioclass_config_path=ioclass_config_path) ioclass_config.add_ioclass( ioclass_id=0, eviction_priority=22, allocation="1.00", rule="unclassified", ioclass_config_path=ioclass_config_path, ) ioclass_config.add_ioclass( ioclass_id=6, eviction_priority=1, allocation="0.00", rule=f"metadata", ioclass_config_path=ioclass_config_path, ) casadm.load_io_classes(cache_id=cache.cache_id, file=ioclass_config_path) occupancy_before = cache.get_io_class_statistics( io_class_id=0).usage_stats.occupancy for file in test_files: Dd().input(file.full_path).output("/dev/null").block_size( file.size).run() sync() drop_caches(DropCachesMode.ALL) occupancy_after = cache.get_io_class_statistics( io_class_id=0).usage_stats.occupancy occupancy_expected = occupancy_before + file.size if occupancy_after != occupancy_expected: TestRun.fail("File not reclassified properly!\n" f"Expected {occupancy_expected}\n" f"Actual {occupancy_after}") occupancy_before = occupancy_after sync() drop_caches(DropCachesMode.ALL) with TestRun.step("Restore IO class configuration."): ioclass_config.remove_ioclass_config( ioclass_config_path=ioclass_config_path) ioclass_config.create_ioclass_config( add_default_rule=False, ioclass_config_path=ioclass_config_path) ioclass_config.add_ioclass( ioclass_id=0, eviction_priority=22, allocation="1.00", rule="unclassified", ioclass_config_path=ioclass_config_path, ) load_file_size_io_classes(cache, base_size) with TestRun.step( "Read files belonging to different IO classes (classification by reads)." ): # CAS device should be unmounted and mounted because data can be sometimes still cached by # OS cache so occupancy statistics will not match core.unmount() core.mount(mountpoint) for file in test_files: ioclass_id = size_to_class[file.size] occupancy_before = cache.get_io_class_statistics( io_class_id=ioclass_id).usage_stats.occupancy Dd().input(file.full_path).output("/dev/null").block_size( file.size).run() sync() drop_caches(DropCachesMode.ALL) occupancy_after = cache.get_io_class_statistics( io_class_id=ioclass_id).usage_stats.occupancy actual_blocks = occupancy_after.get_value(Unit.Blocks4096) expected_blocks = (occupancy_before + file.size).get_value( Unit.Blocks4096) if actual_blocks != expected_blocks: TestRun.fail("File not reclassified properly!\n" f"Expected {occupancy_before + file.size}\n" f"Actual {occupancy_after}") sync() drop_caches(DropCachesMode.ALL)
def test_ioclass_occupancy_sum_cache(): """ title: Test for ioclasses occupancy sum description: | Create ioclass for 3 different directories, each with different max cache occupancy configured. Trigger IO to each ioclass and check if sum of their Usage stats is equal to cache Usage stats. pass_criteria: - Max occupancy is set correctly for each ioclass - Sum of ioclassess stats is equal to cache stats """ with TestRun.step("Prepare CAS device"): cache, core = prepare() cache_size = cache.get_statistics().config_stats.cache_size with TestRun.step("Disable udev"): Udev.disable() with TestRun.step( f"Prepare filesystem and mount {core.path} at {mountpoint}"): filesystem = Filesystem.xfs core.create_filesystem(filesystem) core.mount(mountpoint) sync() with TestRun.step("Prepare test dirs"): default_ioclass_id = 0 IoclassConfig = namedtuple("IoclassConfig", "id eviction_prio max_occupancy dir_path") io_classes = [ IoclassConfig(1, 3, 0.10, f"{mountpoint}/A"), IoclassConfig(2, 4, 0.20, f"{mountpoint}/B"), IoclassConfig(3, 5, 0.30, f"{mountpoint}/C"), ] for io_class in io_classes: fs_utils.create_directory(io_class.dir_path, parents=True) with TestRun.step("Remove old ioclass config"): ioclass_config.remove_ioclass_config() ioclass_config.create_ioclass_config(False) with TestRun.step("Add default ioclasses"): ioclass_config.add_ioclass(*str(IoClass.default( allocation="0.00")).split(",")) with TestRun.step("Add ioclasses for all dirs"): for io_class in io_classes: ioclass_config.add_ioclass( io_class.id, f"directory:{io_class.dir_path}&done", io_class.eviction_prio, f"{io_class.max_occupancy:0.2f}", ) casadm.load_io_classes(cache_id=cache.cache_id, file=ioclass_config_path) with TestRun.step("Purge cache"): cache.purge_cache() with TestRun.step("Verify stats before IO"): usage_stats_sum = IoClassUsageStats(Size(0), Size(0), Size(0)) for i in io_classes: usage_stats_sum += get_io_class_usage(cache, i.id) usage_stats_sum += get_io_class_usage(cache, default_ioclass_id) cache_stats = cache.get_statistics().usage_stats cache_stats.free = Size(0) if (cache_stats.occupancy != usage_stats_sum.occupancy or cache_stats.clean != usage_stats_sum.clean or cache_stats.dirty != usage_stats_sum.dirty): TestRun.LOGGER.error( "Initial cache usage stats doesn't match sum of ioclasses stats\n" f"cache stats: {cache_stats}, sumed up stats {usage_stats_sum}\n" f"particular stats {[get_io_class_usage(cache, i.id) for i in io_classes]}" ) with TestRun.step(f"Trigger IO to each directory"): for io_class in io_classes: run_io_dir( f"{io_class.dir_path}/tmp_file", int((io_class.max_occupancy * cache_size) / Unit.Blocks4096), ) with TestRun.step("Verify stats after IO"): usage_stats_sum = IoClassUsageStats(Size(0), Size(0), Size(0)) for i in io_classes: usage_stats_sum += get_io_class_usage(cache, i.id) usage_stats_sum += get_io_class_usage(cache, default_ioclass_id) cache_stats = cache.get_statistics().usage_stats cache_stats.free = Size(0) if (cache_stats.occupancy != usage_stats_sum.occupancy or cache_stats.clean != usage_stats_sum.clean or cache_stats.dirty != usage_stats_sum.dirty): TestRun.LOGGER.error( "Cache usage stats doesn't match sum of ioclasses stats\n" f"cache stats: {cache_stats}, sumed up stats {usage_stats_sum}\n" f"particular stats {[get_io_class_usage(cache, i.id) for i in io_classes]}" )
def test_ioclass_occupancy_directory_write(io_size_multiplication, cache_mode, cache_line_size): """ title: Test for max occupancy set for ioclass based on directory description: | Create ioclass for 3 different directories, each with different max cache occupancy configured. Run IO against each directory and see if occupancy limit is repected. pass_criteria: - Max occupancy is set correctly for each ioclass - Each ioclass does not exceed max occupancy """ with TestRun.step("Prepare CAS device"): cache, core = prepare(cache_mode=cache_mode, cache_line_size=cache_line_size) cache_size = cache.get_statistics().config_stats.cache_size with TestRun.step("Disable udev"): Udev.disable() with TestRun.step( f"Prepare filesystem and mount {core.path} at {mountpoint}"): filesystem = Filesystem.xfs core.create_filesystem(filesystem) core.mount(mountpoint) sync() with TestRun.step("Prepare test dirs"): IoclassConfig = namedtuple("IoclassConfig", "id eviction_prio max_occupancy dir_path") io_classes = [ IoclassConfig(1, 3, 0.10, f"{mountpoint}/A"), IoclassConfig(2, 4, 0.20, f"{mountpoint}/B"), IoclassConfig(3, 5, 0.30, f"{mountpoint}/C"), ] for io_class in io_classes: fs_utils.create_directory(io_class.dir_path, parents=True) with TestRun.step("Remove old ioclass config"): ioclass_config.remove_ioclass_config() ioclass_config.create_ioclass_config(False) with TestRun.step("Add default ioclasses"): ioclass_config.add_ioclass(*str(IoClass.default( allocation="0.00")).split(",")) with TestRun.step("Add ioclasses for all dirs"): for io_class in io_classes: ioclass_config.add_ioclass( io_class.id, f"directory:{io_class.dir_path}&done", io_class.eviction_prio, f"{io_class.max_occupancy:0.2f}", ) casadm.load_io_classes(cache_id=cache.cache_id, file=ioclass_config_path) with TestRun.step("Reset cache stats"): cache.purge_cache() cache.reset_counters() with TestRun.step("Check initial occupancy"): for io_class in io_classes: occupancy = get_io_class_occupancy(cache, io_class.id) if occupancy.get_value() != 0: TestRun.LOGGER.error( f"Incorrect inital occupancy for ioclass id: {io_class.id}." f" Expected 0, got: {occupancy}") with TestRun.step( f"To each directory perform IO with size of {io_size_multiplication} max io_class occupancy" ): for io_class in io_classes: original_occupancies = {} tmp_io_class_list = [i for i in io_classes if i != io_class] for i in tmp_io_class_list: original_occupancies[i.id] = get_io_class_occupancy( cache, i.id) io_count = get_io_count(io_class, cache_size, cache_line_size, io_size_multiplication) run_io_dir(f"{io_class.dir_path}/tmp_file", io_count) actual_occupancy = get_io_class_occupancy(cache, io_class.id) expected_occupancy = io_class.max_occupancy * cache_size if io_size_multiplication < 1: expected_occupancy *= io_size_multiplication expected_occupancy = expected_occupancy.align_down( cache_line_size.value.value) expected_occupancy.set_unit(Unit.Blocks4096) if not isclose(expected_occupancy.value, actual_occupancy.value, rel_tol=0.1): TestRun.LOGGER.error( f"Occupancy for ioclass {io_class.id} should be equal {expected_occupancy} " f"but is {actual_occupancy} instead!") for i in tmp_io_class_list: actual_occupancy = get_io_class_occupancy(cache, i.id) io_count = get_io_count(i, cache_size, cache_line_size, io_size_multiplication) if (original_occupancies[i.id] != actual_occupancy and io_count * Unit.Blocks4096.value < actual_occupancy.value): TestRun.LOGGER.error( f"Occupancy for ioclass {i.id} should not change " f"during IO to ioclass {io_class.id}. Original value: " f"{original_occupancies[i.id]}, actual: {actual_occupancy}" ) with TestRun.step( "Check if none of ioclasses did not exceed specified occupancy"): for io_class in io_classes: actual_occupancy = get_io_class_occupancy(cache, io_class.id) occupancy_limit = ((io_class.max_occupancy * cache_size).align_up( Unit.Blocks4096.get_value()).set_unit(Unit.Blocks4096)) # Divergency may be caused by rounding max occupancy if actual_occupancy > occupancy_limit * 1.01: TestRun.LOGGER.error( f"Occupancy for ioclass id exceeded: {io_class.id}. " f"Limit: {occupancy_limit}, actual: {actual_occupancy}")
def test_ioclass_stats_sum(prepare_and_cleanup): """Check if stats for all set ioclasses sum up to cache stats""" cache, core = prepare() min_ioclass_id = 1 max_ioclass_id = 11 file_size_base = Unit.KibiByte.value * 4 TestProperties.LOGGER.info("Preparing ioclass config file") ioclass_config.create_ioclass_config( add_default_rule=True, ioclass_config_path=ioclass_config_path ) for i in range(min_ioclass_id, max_ioclass_id): ioclass_config.add_ioclass( ioclass_id=i, eviction_priority=22, allocation=True, rule=f"file_size:le:{file_size_base*i}&done", ioclass_config_path=ioclass_config_path, ) cache.load_io_class(ioclass_config_path) TestProperties.LOGGER.info("Generating files with particular sizes") files_list = [] for i in range(min_ioclass_id, max_ioclass_id): path = f"/tmp/test_file_{file_size_base*i}" File.create_file(path) f = File(path) f.padding(Size(file_size_base * i, Unit.Byte)) files_list.append(f) core.create_filesystem(Filesystem.ext4) cache.reset_counters() # Name of stats, which should not be compared not_compare_stats = ["clean", "occupancy"] ioclass_id_list = list(range(min_ioclass_id, max_ioclass_id)) # Append default ioclass id ioclass_id_list.append(0) TestProperties.LOGGER.info("Copying files to mounted core and stats check") for f in files_list: # To prevent stats pollution by filesystem requests, umount core device # after file is copied core.mount(mountpoint) f.copy(mountpoint) sync() core.unmount() sync() cache_stats = cache.get_cache_statistics( stat_filter=[StatsFilter.usage, StatsFilter.req, StatsFilter.blk] ) for ioclass_id in ioclass_id_list: ioclass_stats = cache.get_cache_statistics( stat_filter=[StatsFilter.usage, StatsFilter.req, StatsFilter.blk], io_class_id=ioclass_id, ) for stat_name in cache_stats: if stat_name in not_compare_stats: continue cache_stats[stat_name] -= ioclass_stats[stat_name] for stat_name in cache_stats: if stat_name in not_compare_stats: continue stat_val = ( cache_stats[stat_name].get_value() if isinstance(cache_stats[stat_name], Size) else cache_stats[stat_name] ) assert stat_val == 0, f"{stat_name} diverged!\n" # Test cleanup for f in files_list: f.remove()
def test_ioclass_usage_sum(): """ title: Test for ioclass stats after purge description: | Create ioclasses for 3 different directories. Run IO against each directory, check usage stats correctness before and after purge pass_criteria: - Usage stats are consistent on each test step - Usage stats don't exceed cache size """ with TestRun.step("Prepare disks"): cache, core = prepare() cache_size = cache.get_statistics().config_stats.cache_size with TestRun.step("Disable udev"): Udev.disable() with TestRun.step( f"Prepare filesystem and mount {core.path} at {mountpoint}"): filesystem = Filesystem.xfs core.create_filesystem(filesystem) core.mount(mountpoint) sync() with TestRun.step("Prepare test dirs"): default_ioclass_id = 0 IoclassConfig = namedtuple("IoclassConfig", "id eviction_prio dir_path io_size") io_classes = [ IoclassConfig(1, 3, f"{mountpoint}/A", cache_size * 0.25), IoclassConfig(2, 4, f"{mountpoint}/B", cache_size * 0.35), IoclassConfig(3, 5, f"{mountpoint}/C", cache_size * 0.1), ] for io_class in io_classes: fs_utils.create_directory(io_class.dir_path, parents=True) with TestRun.step("Add ioclasses for all dirs"): ioclass_config.remove_ioclass_config() ioclass_config.create_ioclass_config(True) for io_class in io_classes: add_io_class( io_class.id, io_class.eviction_prio, f"directory:{io_class.dir_path}&done", ) casadm.load_io_classes(cache_id=cache.cache_id, file=ioclass_config_path) # Since default ioclass is already present in cache and no directory should be # created, it is added to ioclasses list after setup is done io_classes.append( IoclassConfig(default_ioclass_id, 22, f"{mountpoint}", cache_size * 0.2)) with TestRun.step("Verify stats of newly started cache device"): sync() drop_caches(DropCachesMode.ALL) verify_ioclass_usage_stats(cache, [i.id for i in io_classes]) with TestRun.step("Trigger IO to each partition and verify stats"): for io_class in io_classes: run_io_dir(io_class.dir_path, int((io_class.io_size) / Unit.Blocks4096)) verify_ioclass_usage_stats(cache, [i.id for i in io_classes]) with TestRun.step("Purge cache and verify stats"): cache.purge_cache() verify_ioclass_usage_stats(cache, [i.id for i in io_classes]) with TestRun.step( "Trigger IO to each partition for the second time and verify stats" ): for io_class in io_classes: run_io_dir(io_class.dir_path, int((io_class.io_size) / Unit.Blocks4096)) verify_ioclass_usage_stats(cache, [i.id for i in io_classes])
def test_ioclass_repart(cache_mode, cache_line_size, ioclass_size_multiplicatior): """ title: Check whether occupancy limit is respected during repart description: | Create ioclass for 3 different directories, each with different max occupancy threshold. Create 3 files classified on default ioclass. Move files to directories created earlier and force repart by reading theirs contents. pass_criteria: - Partitions are evicted in specified order """ with TestRun.step("Prepare CAS device"): cache, core = prepare(cache_mode=cache_mode, cache_line_size=cache_line_size) cache_size = cache.get_statistics().config_stats.cache_size with TestRun.step("Disable udev"): Udev.disable() with TestRun.step( f"Prepare filesystem and mount {core.path} at {mountpoint}"): filesystem = Filesystem.xfs core.create_filesystem(filesystem) core.mount(mountpoint) sync() with TestRun.step("Prepare test dirs"): IoclassConfig = namedtuple("IoclassConfig", "id eviction_prio max_occupancy dir_path") io_classes = [ IoclassConfig(1, 3, 0.40, f"{mountpoint}/A"), IoclassConfig(2, 4, 0.30, f"{mountpoint}/B"), IoclassConfig(3, 5, 0.30, f"{mountpoint}/C"), ] for io_class in io_classes: fs_utils.create_directory(io_class.dir_path, parents=True) with TestRun.step("Remove old ioclass config"): ioclass_config.remove_ioclass_config() ioclass_config.create_ioclass_config(False) with TestRun.step("Add default ioclasses"): ioclass_config.add_ioclass(*str(IoClass.default( allocation="1.00")).split(",")) ioclass_config.add_ioclass(ioclass_id=5, rule="metadata", eviction_priority=1, allocation="1.00", ioclass_config_path=ioclass_config_path) with TestRun.step("Add ioclasses for all dirs"): for io_class in io_classes: ioclass_config.add_ioclass( io_class.id, f"directory:{io_class.dir_path}&done", io_class.eviction_prio, f"{io_class.max_occupancy*ioclass_size_multiplicatior:0.2f}", ) casadm.load_io_classes(cache_id=cache.cache_id, file=ioclass_config_path) with TestRun.step("Reset cache stats"): cache.purge_cache() cache.reset_counters() with TestRun.step(f"Create 3 files classified in default ioclass"): for i, io_class in enumerate(io_classes[0:3]): run_io_dir( f"{mountpoint}/{i}", int((io_class.max_occupancy * cache_size) / Unit.Blocks4096)) if not isclose( get_io_class_occupancy( cache, ioclass_config.DEFAULT_IO_CLASS_ID).value, cache_size.value, rel_tol=0.1, ): TestRun.fail(f"Failed to populte default ioclass") with TestRun.step("Check initial occupancy"): for io_class in io_classes: occupancy = get_io_class_occupancy(cache, io_class.id) if occupancy.get_value() != 0: TestRun.LOGGER.error( f"Incorrect inital occupancy for ioclass id: {io_class.id}." f" Expected 0, got: {occupancy}") with TestRun.step( "Force repart - move files to created directories and read theirs contents" ): for i, io_class in enumerate(io_classes): fs_utils.move(source=f"{mountpoint}/{i}", destination=io_class.dir_path) run_io_dir_read(f"{io_class.dir_path}/{i}") with TestRun.step("Check if each ioclass reached it's occupancy limit"): for io_class in io_classes[0:3]: actuall_occupancy = get_io_class_occupancy(cache, io_class.id) occupancy_limit = ((io_class.max_occupancy * cache_size).align_down( Unit.Blocks4096.get_value()).set_unit( Unit.Blocks4096)) if not isclose(actuall_occupancy.value, occupancy_limit.value, rel_tol=0.1): TestRun.LOGGER.error( f"Occupancy for ioclass {io_class.id} does not match. " f"Limit: {occupancy_limit}, actuall: {actuall_occupancy}")
def test_ioclass_occupancy_directory_read(io_size_multiplication, cache_line_size, cache_mode): """ title: Test for max occupancy set for ioclass based on directory - read description: | Set cache mode to pass-through and create files on mounted core device. Swtich cache to write through, and load ioclasses applaying to different files. Read files and check if occupancy threshold is respected. pass_criteria: - Max occupancy is set correctly for each ioclass - Each ioclass does not exceed max occupancy """ with TestRun.step("Prepare CAS device"): cache, core = prepare(cache_mode=cache_mode, cache_line_size=cache_line_size) cache_size = cache.get_statistics().config_stats.cache_size with TestRun.step("Disable udev"): Udev.disable() with TestRun.step( f"Prepare filesystem and mount {core.path} at {mountpoint}"): filesystem = Filesystem.xfs core.create_filesystem(filesystem) core.mount(mountpoint) sync() with TestRun.step("Prepare test dirs"): IoclassConfig = namedtuple("IoclassConfig", "id eviction_prio max_occupancy dir_path") io_classes = [ IoclassConfig(1, 3, 0.10, f"{mountpoint}/A"), IoclassConfig(2, 4, 0.20, f"{mountpoint}/B"), IoclassConfig(3, 5, 0.30, f"{mountpoint}/C"), ] for io_class in io_classes: fs_utils.create_directory(io_class.dir_path, parents=True) with TestRun.step( f"In each directory create file with size of {io_size_multiplication} " f"max io_class occupancy for future read"): for io_class in io_classes: run_io_dir( f"{io_class.dir_path}/tmp_file", int((io_class.max_occupancy * cache_size) / Unit.Blocks4096 * io_size_multiplication), ) with TestRun.step("Remove old ioclass config"): ioclass_config.remove_ioclass_config() ioclass_config.create_ioclass_config(False) with TestRun.step("Add default ioclasses"): ioclass_config.add_ioclass(*str(IoClass.default( allocation="0.00")).split(",")) with TestRun.step("Add ioclasses for all dirs"): for io_class in io_classes: ioclass_config.add_ioclass( io_class.id, f"directory:{io_class.dir_path}&done", io_class.eviction_prio, f"{io_class.max_occupancy:0.2f}", ) casadm.load_io_classes(cache_id=cache.cache_id, file=ioclass_config_path) with TestRun.step("Reset cache stats"): cache.purge_cache() cache.reset_counters() with TestRun.step("Check initial occupancy"): for io_class in io_classes: occupancy = get_io_class_occupancy(cache, io_class.id) if occupancy.get_value() != 0: TestRun.LOGGER.error( f"Incorrect inital occupancy for ioclass id: {io_class.id}." f" Expected 0, got: {occupancy}") with TestRun.step( f"Read each file and check if data was inserted to appropriate ioclass" ): for io_class in io_classes: original_occupacies = {} tmp_io_class_list = [i for i in io_classes if i != io_class] for i in tmp_io_class_list: original_occupacies[i.id] = get_io_class_occupancy(cache, i.id) run_io_dir_read(f"{io_class.dir_path}/tmp_file") actuall_occupancy = get_io_class_occupancy(cache, io_class.id) io_size = io_class.max_occupancy * cache_size if io_size_multiplication < 1: io_size *= io_size_multiplication io_size.set_unit(Unit.Blocks4096) if not isclose(io_size.value, actuall_occupancy.value, rel_tol=0.1): TestRun.LOGGER.error( f"Occupancy for ioclass {i.id} should be equal {io_size} " f"but is {actuall_occupancy} instead!") for i in tmp_io_class_list: actuall_occupancy = get_io_class_occupancy(cache, i.id) if original_occupacies[i.id] != actuall_occupancy: TestRun.LOGGER.error( f"Occupancy for ioclass {i.id} should not change " f"during IO to ioclass {io_class.id}. Original value: " f"{original_occupacies[i.id]}, actuall: {actuall_occupancy}" ) with TestRun.step( "Check if none of ioclasses did not exceed specified occupancy"): for io_class in io_classes: actuall_occupancy = get_io_class_occupancy(cache, io_class.id) occupancy_limit = ((io_class.max_occupancy * cache_size).align_up( Unit.Blocks4096.get_value()).set_unit(Unit.Blocks4096)) # Divergency may be casued be rounding max occupancy if actuall_occupancy > occupancy_limit + Size( 100, Unit.Blocks4096): TestRun.LOGGER.error( f"Occupancy for ioclass id exceeded: {io_class.id}. " f"Limit: {occupancy_limit}, actuall: {actuall_occupancy}")