diff --git a/inc/ocf_composite_volume.h b/inc/ocf_composite_volume.h index 7ae5a3a..9c09eb4 100644 --- a/inc/ocf_composite_volume.h +++ b/inc/ocf_composite_volume.h @@ -16,6 +16,8 @@ #include "ocf_err.h" #include "ocf_volume.h" +#define OCF_VOLUME_TYPE_COMPOSITE 10 + /** * @brief handle to object designating composite volume */ diff --git a/src/ocf_composite_volume.c b/src/ocf_composite_volume.c index 5d7b95e..a6bf91f 100644 --- a/src/ocf_composite_volume.c +++ b/src/ocf_composite_volume.c @@ -152,6 +152,11 @@ static void ocf_composite_volume_on_deinit(ocf_volume_t cvolume) struct ocf_composite_volume *composite = ocf_volume_get_priv(cvolume); int i; + /* priv can be NULL if this volume had been moved from. In this case + * it's the owner responsibility to deinit member volumes. */ + if (!composite) + return; + for (i = 0; i < composite->members_cnt; i++) ocf_volume_deinit(&composite->member[i].volume); } @@ -282,7 +287,7 @@ static void *ocf_composite_io_allocator_new(ocf_io_allocator_t allocator, member_addr = cur_addr - (i > 0 ? composite->end_addr[i-1] : 0); member_bytes = OCF_MIN(cur_addr + cur_bytes, composite->end_addr[i]) - - member_addr; + - cur_addr; cio->member_io[i] = ocf_io_new(&composite->member[i].volume, queue, member_addr, member_bytes, dir, 0, 0); diff --git a/src/ocf_ctx_priv.h b/src/ocf_ctx_priv.h index 3ce457f..c3f0882 100644 --- a/src/ocf_ctx_priv.h +++ b/src/ocf_ctx_priv.h @@ -8,6 +8,7 @@ #include "ocf_env.h" #include "ocf/ocf_ctx.h" +#include "ocf/ocf_composite_volume.h" #include "ocf_logger_priv.h" #include "ocf_volume_priv.h" @@ -19,7 +20,11 @@ #define OCF_VOLUME_TYPE_CORE (OCF_VOLUME_TYPE_MAX_USER + 0) #define OCF_VOLUME_TYPE_CACHE (OCF_VOLUME_TYPE_MAX_USER + 1) -#define OCF_VOLUME_TYPE_COMPOSITE (OCF_VOLUME_TYPE_MAX_USER + 2) +#define OCF_VOLUME_TYPE_COMPOSITE_PLACEHOLDER (OCF_VOLUME_TYPE_MAX_USER + 2) + +#if OCF_VOLUME_TYPE_COMPOSITE_PLACEHOLDER != OCF_VOLUME_TYPE_COMPOSITE +#error "composite volume id mismatch" +#endif /** * @brief OCF main control structure diff --git a/src/ocf_volume.c b/src/ocf_volume.c index f335ece..4ae8321 100644 --- a/src/ocf_volume.c +++ b/src/ocf_volume.c @@ -327,8 +327,10 @@ int ocf_volume_open(ocf_volume_t volume, void *volume_params) { int ret; + if (volume->opened) + return -OCF_ERR_NOT_OPEN_EXC; + ENV_BUG_ON(!volume->type->properties->ops.open); - ENV_BUG_ON(volume->opened); ret = volume->type->properties->ops.open(volume, volume_params); if (ret) @@ -352,7 +354,9 @@ void ocf_volume_close(ocf_volume_t volume) env_completion cmpl; ENV_BUG_ON(!volume->type->properties->ops.close); - ENV_BUG_ON(!volume->opened); + + if (!volume->opened) + return; env_completion_init(&cmpl); ocf_refcnt_freeze(&volume->refcnt); diff --git a/tests/functional/pyocf/c/helpers/volume_type.c b/tests/functional/pyocf/c/helpers/volume_type.c new file mode 100644 index 0000000..08815c4 --- /dev/null +++ b/tests/functional/pyocf/c/helpers/volume_type.c @@ -0,0 +1,13 @@ +/* + * Copyright(c) 2022-2022 Intel Corporation + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "ocf/ocf_composite_volume.h" + +int ocf_get_composite_volume_type_id_helper() +{ + return OCF_VOLUME_TYPE_COMPOSITE; +} + + diff --git a/tests/functional/pyocf/helpers.py b/tests/functional/pyocf/helpers.py index 96dd90f..d807c87 100644 --- a/tests/functional/pyocf/helpers.py +++ b/tests/functional/pyocf/helpers.py @@ -34,3 +34,8 @@ def get_metadata_segment_elem_size(cache, segment): def get_metadata_segment_is_flapped(cache, segment): lib = OcfLib.getInstance() return bool(lib.ocf_get_metadata_segment_is_flapped(cache, segment)) + + +def get_composite_volume_type_id(): + lib = OcfLib.getInstance() + return int(lib.ocf_get_composite_volume_type_id_helper()) diff --git a/tests/functional/pyocf/types/cache.py b/tests/functional/pyocf/types/cache.py index ab32f33..cebaba2 100644 --- a/tests/functional/pyocf/types/cache.py +++ b/tests/functional/pyocf/types/cache.py @@ -299,7 +299,7 @@ class Cache: raise OcfError("Failed to detach failover cache device", c.results["error"]) def standby_activate(self, device, open_cores=True): - device_cfg = self.generate_device_config(device) + device_cfg = self.alloc_device_config(device) activate_cfg = CacheStandbyActivateConfig(_device=device_cfg, _open_cores=open_cores,) @@ -311,6 +311,8 @@ class Cache: c.wait() self.write_unlock() + self.free_device_config(device_cfg) + if c.results["error"]: raise OcfError("Failed to activate standby cache", c.results["error"]) @@ -499,21 +501,25 @@ class Cache: if status: raise OcfError("Error adding partition to cache", status) - def generate_device_config(self, device, perform_test=True): - uuid = Uuid( - _data=cast(create_string_buffer(device.uuid.encode("ascii")), c_char_p), - _size=len(device.uuid) + 1, - ) - volume = c_void_p() + def alloc_device_config(self, device, perform_test=True): + if not device.handle: + uuid = Uuid( + _data=cast(create_string_buffer(device.uuid.encode("ascii")), c_char_p), + _size=len(device.uuid) + 1, + ) + volume = c_void_p() - lib = OcfLib.getInstance() - result = lib.ocf_volume_create( - byref(volume), - self.owner.ocf_volume_type[type(device)], - byref(uuid) - ) - if result != 0: - raise OcfError("Cache volume initialization failed", result) + lib = OcfLib.getInstance() + result = lib.ocf_volume_create( + byref(volume), + self.owner.ocf_volume_type[type(device)], + byref(uuid) + ) + + if result != 0: + raise OcfError("Cache volume initialization failed", result) + else: + volume = device.handle device_config = CacheDeviceConfig( _volume=volume, @@ -523,13 +529,16 @@ class Cache: return device_config + def free_device_config(self, cfg): + lib = OcfLib.getInstance().ocf_volume_destroy(cfg._volume) + def attach_device( self, device, force=False, perform_test=False, cache_line_size=None, open_cores=False, ): self.device = device self.device_name = device.uuid - device_config = self.generate_device_config(device, perform_test=perform_test) + device_config = self.alloc_device_config(device, perform_test=perform_test) attach_cfg = CacheAttachConfig( _device=device_config, @@ -548,6 +557,8 @@ class Cache: self.write_unlock() + self.free_device_config(device_config) + if c.results["error"]: raise OcfError( f"Attaching cache device failed", c.results["error"], @@ -557,7 +568,7 @@ class Cache: self.device = device self.device_name = device.uuid - device_config = self.generate_device_config(device, perform_test=False) + device_config = self.alloc_device_config(device, perform_test=False) attach_cfg = CacheAttachConfig( _device=device_config, @@ -576,6 +587,8 @@ class Cache: self.write_unlock() + self.free_device_config(device_config) + if c.results["error"]: raise OcfError( f"Attaching to standby cache failed", c.results["error"], @@ -585,7 +598,7 @@ class Cache: self.device = device self.device_name = device.uuid - device_config = self.generate_device_config(device, perform_test=perform_test) + device_config = self.alloc_device_config(device, perform_test=perform_test) attach_cfg = CacheAttachConfig( _device=device_config, @@ -601,6 +614,8 @@ class Cache: c.wait() self.write_unlock() + self.free_device_config(device_config) + if c.results["error"]: raise OcfError("Loading standby cache device failed", c.results["error"]) @@ -622,7 +637,7 @@ class Cache: self.device = device self.device_name = device.uuid - device_config = self.generate_device_config(device) + device_config = self.alloc_device_config(device) attach_cfg = CacheAttachConfig( _device=device_config, @@ -638,6 +653,8 @@ class Cache: c.wait() self.write_unlock() + self.free_device_config(device_config) + if c.results["error"]: raise OcfError("Loading cache device failed", c.results["error"]) @@ -759,10 +776,16 @@ class Cache: self.cores.remove(core) def get_front_volume(self): - return Volume.get_instance(lib.ocf_cache_get_front_volume(self.cache_handle)) + return Volume.get_instance(self.get_c_front_volume()) + + def get_c_front_volume(self): + return lib.ocf_cache_get_front_volume(self.cache_handle) def get_volume(self): - return Volume.get_instance(lib.ocf_cache_get_volume(self.cache_handle)) + return Volume.get_instance(self.get_c_volume()) + + def get_c_volume(self): + return lib.ocf_cache_get_volume(self.cache_handle) def get_conf(self): cache_info = CacheInfo() @@ -971,3 +994,4 @@ lib.ocf_mngt_cache_io_classes_configure.restype = c_int lib.ocf_mngt_cache_io_classes_configure.argtypes = [c_void_p, c_void_p] lib.ocf_volume_create.restype = c_int lib.ocf_volume_create.argtypes = [c_void_p, c_void_p, c_void_p] +lib.ocf_volume_destroy.argtypes = [c_void_p] diff --git a/tests/functional/pyocf/types/core.py b/tests/functional/pyocf/types/core.py index 7f5c93b..0ea11e4 100644 --- a/tests/functional/pyocf/types/core.py +++ b/tests/functional/pyocf/types/core.py @@ -97,10 +97,16 @@ class Core: return self.handle def get_front_volume(self): - return Volume.get_instance(lib.ocf_core_get_front_volume(self.handle)) + return Volume.get_instance(self.get_c_front_volume()) + + def get_c_front_volume(self): + return lib.ocf_core_get_front_volume(self.handle) def get_volume(self): - return Volume.get_instance(lib.ocf_core_get_volume(self.handle)) + return Volume.get_instance(self.get_c_volume()) + + def get_c_volume(self): + return lib.ocf_core_get_volume(self.handle) def get_default_queue(self): return self.cache.get_default_queue() diff --git a/tests/functional/pyocf/types/ctx.py b/tests/functional/pyocf/types/ctx.py index dc3934f..e6fa60a 100644 --- a/tests/functional/pyocf/types/ctx.py +++ b/tests/functional/pyocf/types/ctx.py @@ -66,7 +66,18 @@ class OcfCtx: return cls.default() + def register_internal_volume_type_id(self, volume_type, volume_type_id): + if volume_type_id in self.volume_types: + raise RuntimeError(f"volume type id {volume_type_id} already used") + self.volume_types[volume_type_id] = volume_type + volume_type.internal = True + def register_volume_type(self, volume_type): + if self.volume_types_count in self.volume_types: + raise RuntimeError( + f"volume type id slot already used by internal volume " + f"{self.volume_types[self.volume_types_count]}" + ) self.volume_types[self.volume_types_count] = volume_type volume_type.type_id = self.volume_types_count @@ -79,10 +90,11 @@ class OcfCtx: raise OcfError("Volume type registration failed", result) self.ocf_volume_type[volume_type] = self.lib.ocf_ctx_get_volume_type( - self.ctx_handle, - volume_type.type_id + self.ctx_handle, volume_type.type_id ) + volume_type.internal = False + self.volume_types_count += 1 def unregister_volume_type(self, vol_type): @@ -96,7 +108,7 @@ class OcfCtx: def cleanup_volume_types(self): for k, vol_type in list(self.volume_types.items()): - if vol_type: + if vol_type and not vol_type.internal: self.unregister_volume_type(vol_type) def stop_caches(self): diff --git a/tests/functional/pyocf/types/cvolume.py b/tests/functional/pyocf/types/cvolume.py new file mode 100644 index 0000000..b55d8c7 --- /dev/null +++ b/tests/functional/pyocf/types/cvolume.py @@ -0,0 +1,78 @@ +# +# Copyright(c) 2022 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# + +from ctypes import ( + c_int, + c_uint32, + c_uint64, + c_void_p, + c_char_p, + byref, + cast, + create_string_buffer, +) + +from ..ocf import OcfLib +from .ctx import OcfCtx +from .io import Io, IoDir +from .queue import Queue +from .shared import OcfError, Uuid +from .volume_exp_obj import OcfInternalVolume + + +class CVolume(OcfInternalVolume): + def __init__(self, ctx): + super().__init__(None) + self.ctx = ctx + self.lib = ctx.lib + + self.cvol = c_void_p() + ret = lib.ocf_composite_volume_create(byref(self.cvol), self.ctx.ctx_handle) + + if ret != 0: + raise OcfError("Composite volume creation failed", ret) + + self.handle = self.cvol.value + + def destroy(self): + self.lib.ocf_composite_volume_destroy(self.cvol) + self.cvol = None + self.handle = 0 + + def add(self, vol): + uuid = Uuid( + _data=cast(create_string_buffer(vol.uuid.encode("ascii")), c_char_p), + _size=len(vol.uuid) + 1, + ) + + volume = c_void_p() + ocf_vol_type = self.ctx.ocf_volume_type[type(vol)] + + ret = self.lib.ocf_composite_volume_add(self.cvol, ocf_vol_type, byref(uuid), c_void_p()) + + if ret != 0: + raise OcfError("Failed to add volume to a composite volume", ret) + + def get_c_handle(self): + return self.cvol.value + + def do_open(self): + ret = self.lib.ocf_volume_open(self.cvol, c_void_p()) + if ret != 0: + raise OcfError("openning composite volume failed", ret) + + def do_close(self): + self.lib.ocf_volume_close(self.cvol) + + +lib = OcfLib.getInstance() +lib.ocf_composite_volume_create.restype = c_int +lib.ocf_composite_volume_create.argtypes = [c_void_p, c_void_p] +lib.ocf_composite_volume_destroy.argtypes = [c_void_p] +lib.ocf_composite_volume_add.argtypes = [c_void_p, c_void_p, c_void_p, c_void_p] +lib.ocf_composite_volume_add.restype = c_int +lib.ocf_volume_open.restype = c_int +lib.ocf_volume_open.argtypes = [c_void_p, c_void_p] +lib.ocf_volume_close.argtypes = [c_void_p] diff --git a/tests/functional/pyocf/types/volume.py b/tests/functional/pyocf/types/volume.py index f6124b1..09d5224 100644 --- a/tests/functional/pyocf/types/volume.py +++ b/tests/functional/pyocf/types/volume.py @@ -147,13 +147,12 @@ class Volume: print("{}".format(Volume._uuid_)) return -1 - return Volume.open(ref, volume) + return Volume.s_open(ref, volume) @VolumeOps.CLOSE def _close(ref): volume = Volume.get_instance(ref) - volume.close() - volume.opened = False + Volume.s_close(volume) @VolumeOps.GET_MAX_IO_SIZE def _get_max_io_size(ref): @@ -180,14 +179,29 @@ class Volume: return Volume._ops_[cls] @staticmethod - def open(ref, volume): + def s_open(ref, volume): if volume.opened: return -OcfErrorCode.OCF_ERR_NOT_OPEN_EXC Volume._instances_[ref] = volume volume.handle = ref - return volume.do_open() + ret = volume.do_open() + if ret == 0: + volume.opened = True + + return ret + + @staticmethod + def s_close(volume): + if not volume.opened: + return + + volume.do_close() + volume.opened = False + + del Volume._instances_[volume.handle] + volume.handle = None @classmethod def get_io_ops(cls): @@ -253,13 +267,13 @@ class Volume: self.reset_stats() self.is_online = True self.opened = False + self.handle = None def do_open(self): - self.opened = True return 0 - def close(self): - self.opened = False + def do_close(self): + pass def get_length(self): raise NotImplementedError @@ -324,7 +338,13 @@ class Volume: ): lib = OcfLib.getInstance() io = lib.ocf_volume_new_io( - self.handle, queue.handle, addr, length, direction, io_class, flags + self.handle, + queue.handle if queue else c_void_p(), + addr, + length, + direction, + io_class, + flags, ) return Io.from_pointer(io) @@ -518,15 +538,18 @@ class TraceDevice(Volume): if submit: self.vol.do_submit_flush(io) + def do_submit_discard(self, io): + submit = self._trace(io, TraceDevice.IoType.Discard) + + if submit: + self.vol.do_submit_discard(io) + def get_length(self): return self.vol.get_length() def get_max_io_size(self): return self.vol.get_max_io_size() - def do_submit_discard(self, discard): - return self.vol.do_submit_discard(discard) - def dump(self, offset=0, size=0, ignore=VOLUME_POISON, **kwargs): return self.vol.dump(offset, size, ignore=ignore, **kwargs) diff --git a/tests/functional/pyocf/types/volume_cache.py b/tests/functional/pyocf/types/volume_cache.py index 7c6199e..5faa834 100644 --- a/tests/functional/pyocf/types/volume_cache.py +++ b/tests/functional/pyocf/types/volume_cache.py @@ -8,11 +8,11 @@ from ctypes import cast, POINTER from .cache import Cache from .io import Io from .io import IoDir -from .volume_exp_obj import ExpObjVolume +from .volume_exp_obj import OcfInternalVolume from .volume import Volume -class CacheVolume(ExpObjVolume): +class CacheVolume(OcfInternalVolume): def __init__(self, cache, open=False, uuid=None): super().__init__(cache, uuid) self.cache = cache @@ -20,8 +20,8 @@ class CacheVolume(ExpObjVolume): if open: self.open() - def open(self): - return Volume.open(self.lib.ocf_cache_get_front_volume(self.cache.cache_handle), self) + def get_c_handle(self): + return self.cache.get_c_front_volume() def md5(self): out = self.cache.get_conf() diff --git a/tests/functional/pyocf/types/volume_core.py b/tests/functional/pyocf/types/volume_core.py index 6381de1..71c9999 100644 --- a/tests/functional/pyocf/types/volume_core.py +++ b/tests/functional/pyocf/types/volume_core.py @@ -4,12 +4,12 @@ # from .core import Core -from .volume_exp_obj import ExpObjVolume +from .volume_exp_obj import OcfInternalVolume from .io import IoDir from .volume import Volume -class CoreVolume(ExpObjVolume): +class CoreVolume(OcfInternalVolume): def __init__(self, core, open=False, uuid=None): super().__init__(core, uuid) self.core = core @@ -17,8 +17,8 @@ class CoreVolume(ExpObjVolume): if open: self.open() - def open(self): - return Volume.open(self.lib.ocf_core_get_front_volume(self.core.handle), self) + def get_c_handle(self): + return self.core.get_c_front_volume() def md5(self): return self._exp_obj_md5(4096) diff --git a/tests/functional/pyocf/types/volume_exp_obj.py b/tests/functional/pyocf/types/volume_exp_obj.py index 984e5b3..b2838ae 100644 --- a/tests/functional/pyocf/types/volume_exp_obj.py +++ b/tests/functional/pyocf/types/volume_exp_obj.py @@ -14,7 +14,7 @@ from pyocf.types.io import IoDir, Io from pyocf.types.shared import OcfCompletion -class ExpObjVolume(Volume): +class OcfInternalVolume(Volume): def __init__(self, parent, uuid=None): super().__init__(uuid) self.parent = parent @@ -113,6 +113,13 @@ class ExpObjVolume(Volume): return read_buffer_all.md5() + def open(self): + handle = self.get_c_handle() + return Volume.s_open(handle, self) + + def close(self): + return Volume.s_close(self) + lib = OcfLib.getInstance() lib.ocf_volume_get_max_io_size.argtypes = [c_void_p] diff --git a/tests/functional/tests/conftest.py b/tests/functional/tests/conftest.py index 2570769..f61e187 100644 --- a/tests/functional/tests/conftest.py +++ b/tests/functional/tests/conftest.py @@ -14,7 +14,9 @@ from pyocf.types.volume import RamVolume, ErrorDevice from pyocf.types.volume_cache import CacheVolume from pyocf.types.volume_core import CoreVolume from pyocf.types.volume_replicated import ReplicatedVolume +from pyocf.types.cvolume import CVolume from pyocf.types.ctx import OcfCtx +from pyocf.helpers import get_composite_volume_type_id default_registered_volumes = [RamVolume, ErrorDevice, CacheVolume, CoreVolume, ReplicatedVolume] @@ -28,6 +30,7 @@ def pyocf_ctx(): c = OcfCtx.with_defaults(DefaultLogger(LogLevel.WARN)) for vol_type in default_registered_volumes: c.register_volume_type(vol_type) + c.register_internal_volume_type_id(CVolume, get_composite_volume_type_id()) yield c c.exit() gc.collect() @@ -39,6 +42,7 @@ def pyocf_ctx_log_buffer(): c = OcfCtx.with_defaults(logger) for vol_type in default_registered_volumes: c.register_volume_type(vol_type) + c.register_internal_volume_type_id(CVolume, get_composite_volume_type_id()) yield logger c.exit() gc.collect() @@ -51,6 +55,8 @@ def pyocf_2_ctx(): for vol_type in default_registered_volumes: c1.register_volume_type(vol_type) c2.register_volume_type(vol_type) + c1.register_internal_volume_type_id(CVolume, get_composite_volume_type_id()) + c2.register_internal_volume_type_id(CVolume, get_composite_volume_type_id()) yield [c1, c2] c1.exit() c2.exit() diff --git a/tests/functional/tests/failover/test_standby_io.py b/tests/functional/tests/failover/test_standby_io.py index d02f804..329deec 100644 --- a/tests/functional/tests/failover/test_standby_io.py +++ b/tests/functional/tests/failover/test_standby_io.py @@ -34,10 +34,11 @@ def test_test_standby_io(pyocf_ctx, cacheline_size): cache.add_io_queue(f"io-queue-{i}") cache.standby_attach(cache_vol) + cache_vol = CacheVolume(cache, open=True) r = ( Rio() - .target(cache) + .target(cache_vol) .njobs(num_jobs) .readwrite(ReadWrite.RANDWRITE) .size(vol_size) diff --git a/tests/functional/tests/management/test_composite_volume.py b/tests/functional/tests/management/test_composite_volume.py index eae42d3..19ee523 100644 --- a/tests/functional/tests/management/test_composite_volume.py +++ b/tests/functional/tests/management/test_composite_volume.py @@ -4,9 +4,16 @@ # import pytest +from ctypes import c_int + +from pyocf.types.volume import RamVolume, ErrorDevice, TraceDevice, IoFlags +from pyocf.types.cvolume import CVolume +from pyocf.types.data import Data +from pyocf.types.io import IoDir +from pyocf.types.shared import OcfError, OcfCompletion +from pyocf.utils import Size as S -@pytest.mark.skip(reason="not implemented") def test_create_composite_volume(pyocf_ctx): """ title: Create composite volume. @@ -26,10 +33,12 @@ def test_create_composite_volume(pyocf_ctx): - composite_volume::creation - composite_volume::adding_component_volume """ - pass + cvol = CVolume(pyocf_ctx) + vol = RamVolume(S.from_MiB(1)) + cvol.add(vol) + cvol.destroy() -@pytest.mark.skip(reason="not implemented") def test_add_subvolumes_of_different_types(pyocf_ctx): """ title: Add subvolumes of different types. @@ -49,10 +58,16 @@ def test_add_subvolumes_of_different_types(pyocf_ctx): requirements: - composite_volume::component_volume_types """ - pass + vol1 = RamVolume(S.from_MiB(1)) + vol2_backend = RamVolume(S.from_MiB(1)) + vol2 = ErrorDevice(vol2_backend) + + cvol = CVolume(pyocf_ctx) + cvol.add(vol1) + cvol.add(vol2) + cvol.destroy() -@pytest.mark.skip(reason="not implemented") def test_add_max_subvolumes(pyocf_ctx): """ title: Add maximum number of subvolumes. @@ -69,10 +84,36 @@ def test_add_max_subvolumes(pyocf_ctx): requirements: - composite_volume::max_composite_volumes """ - pass + + cvol = CVolume(pyocf_ctx) + + for i in range(16): + vol = RamVolume(S.from_MiB(1)) + cvol.add(vol) + + vol = RamVolume(S.from_MiB(1)) + with pytest.raises(OcfError): + cvol.add(vol) + + cvol.destroy() + + +def _cvol_io(cvol, addr, size, func, flags=0): + io = cvol.new_io( + queue=None, addr=addr, length=size, direction=IoDir.WRITE, io_class=0, flags=flags, + ) + completion = OcfCompletion([("err", c_int)]) + io.callback = completion.callback + data = Data(byte_count=size) + io.set_data(data, 0) + + submit_fn = getattr(io, func) + submit_fn() + completion.wait() + + return int(completion.results["err"]) -@pytest.mark.skip(reason="not implemented") def test_basic_volume_operations(pyocf_ctx): """ title: Perform basic volume operations. @@ -93,7 +134,56 @@ def test_basic_volume_operations(pyocf_ctx): - composite_volume::volume_api - composite_volume::io_request_passing """ - pass + count = {"flush": 0, "discard": 0, "io": 0} + expected = {"flush": 0, "discard": 0, "io": 0} + + pyocf_ctx.register_volume_type(TraceDevice) + + addr = S.from_KiB(512).B + size = S.from_KiB(4).B + + def trace(vol, io, io_type): + if io_type == TraceDevice.IoType.Flush or int(io.contents._flags) & IoFlags.FLUSH: + count["flush"] += 1 + elif io_type == TraceDevice.IoType.Discard: + count["discard"] += 1 + else: + assert io_type == TraceDevice.IoType.Data + count["io"] += 1 + assert io.contents._dir == IoDir.WRITE + assert io.contents._addr == addr + assert io.contents._bytes == size + + return True + + backend = RamVolume(S.from_MiB(1)) + trace_dev = TraceDevice(backend, trace_fcn=trace) + + cvol = CVolume(pyocf_ctx) + + cvol.add(trace_dev) + cvol.open() + + # verify data properly propagated + ret = _cvol_io(cvol, addr, size, "submit") + assert ret == 0 + expected["io"] += 1 + assert expected == count + + # verify flush properly propagated + ret = _cvol_io(cvol, addr, size, "submit_flush", IoFlags.FLUSH) + assert ret == 0 + expected["flush"] += 1 + assert expected == count + + # verify discard properly propagated + ret = _cvol_io(cvol, addr, size, "submit_discard") + assert ret == 0 + expected["discard"] += 1 + assert expected == count + + cvol.close() + cvol.destroy() @pytest.mark.skip(reason="not implemented") diff --git a/tests/functional/tests/management/test_start_stop.py b/tests/functional/tests/management/test_start_stop.py index 539ad6e..81b5bf9 100644 --- a/tests/functional/tests/management/test_start_stop.py +++ b/tests/functional/tests/management/test_start_stop.py @@ -4,7 +4,15 @@ # import logging -from ctypes import c_int, c_void_p, byref, c_uint32 +from ctypes import ( + c_int, + c_void_p, + byref, + c_uint32, + cast, + create_string_buffer, + c_char_p, +) from random import randrange from itertools import count @@ -19,13 +27,22 @@ from pyocf.types.cache import ( CacheConfig, PromotionPolicy, Backfill, + CacheDeviceConfig, + CacheAttachConfig, ) from pyocf.types.core import Core from pyocf.types.ctx import OcfCtx from pyocf.types.data import Data from pyocf.types.io import IoDir from pyocf.types.queue import Queue -from pyocf.types.shared import OcfError, OcfCompletion, CacheLineSize, SeqCutOffPolicy +from pyocf.types.shared import ( + Uuid, + OcfError, + OcfErrorCode, + OcfCompletion, + CacheLineSize, + SeqCutOffPolicy, +) from pyocf.types.volume import Volume, RamVolume from pyocf.types.volume_core import CoreVolume from pyocf.utils import Size @@ -234,7 +251,7 @@ def test_start_stop_multiple(pyocf_ctx): cache_line_size = CacheLineSize(size) cache = Cache.start_on_device( - cache_device, name=cache_name, cache_mode=cache_mode, cache_line_size=cache_line_size + cache_device, name=cache_name, cache_mode=cache_mode, cache_line_size=cache_line_size, ) caches.append(cache) stats = cache.get_stats() @@ -264,7 +281,7 @@ def test_100_start_stop(pyocf_ctx): cache_line_size = CacheLineSize(size) cache = Cache.start_on_device( - cache_device, name=cache_name, cache_mode=cache_mode, cache_line_size=cache_line_size + cache_device, name=cache_name, cache_mode=cache_mode, cache_line_size=cache_line_size, ) stats = cache.get_stats() assert stats["conf"]["cache_mode"] == cache_mode, "Cache mode" @@ -378,18 +395,72 @@ def test_start_cache_huge_device(pyocf_ctx_log_buffer, cls): @pytest.mark.parametrize("cls", CacheLineSize) def test_start_cache_same_device(pyocf_ctx, mode, cls): """Adding two caches using the same cache device - Check that OCF does not allow for 2 caches using the same cache device to be started + Check that OCF does not allow for 2 caches using the same cache device to be started. + Low level OCF API is used for attach instead of Cache::attach_device as the latter operates + on pyocf Volume objects and this test requires explicit construction of two volumes with + identical UUID. Pyocf does not allow for two Volume objects with the same UUID, as these + represent a resource that should be uniquely identified by UUID. So we need to create + two distinct OCF volumes with identical UUID and pass them to OCF cache attach method. """ + _uuid = "cache_dev" - cache_device = RamVolume(Size.from_MiB(50)) - cache = Cache.start_on_device(cache_device, cache_mode=mode, cache_line_size=cls, name="cache1") - cache.get_stats() + cache_device = RamVolume(Size.from_MiB(50), uuid=_uuid) - with pytest.raises(OcfError, match="OCF_ERR_NOT_OPEN_EXC"): - cache = Cache.start_on_device( - cache_device, cache_mode=mode, cache_line_size=cls, name="cache2" - ) - cache.get_stats() + uuid = Uuid( + _data=cast(create_string_buffer(_uuid.encode("ascii")), c_char_p), _size=len(_uuid) + 1, + ) + + lib = OcfLib.getInstance() + + vol1 = c_void_p() + vol2 = c_void_p() + + result = lib.ocf_volume_create(byref(vol1), pyocf_ctx.ocf_volume_type[RamVolume], byref(uuid)) + assert result == 0 + result = lib.ocf_volume_create(byref(vol2), pyocf_ctx.ocf_volume_type[RamVolume], byref(uuid)) + assert result == 0 + + dev_cfg = CacheDeviceConfig(_volume=vol1, _perform_test=False, _volume_params=None) + + attach_cfg = CacheAttachConfig( + _device=dev_cfg, + _cache_line_size=cls, + _open_cores=True, + _force=False, + _discard_on_start=False, + ) + + # start first cache instance + cache1 = Cache(pyocf_ctx, cache_mode=mode, cache_line_size=cls, name="cache1") + cache1.start_cache() + cache1.write_lock() + c = OcfCompletion([("cache", c_void_p), ("priv", c_void_p), ("error", c_int)]) + lib.ocf_mngt_cache_attach(cache1.cache_handle, byref(attach_cfg), c, None) + c.wait() + cache1.write_unlock() + assert not c.results["error"] + + # attempt to start second cache instance on a volume with the same UUID + attach_cfg._device._volume = vol2 + cache2 = Cache(pyocf_ctx, cache_mode=mode, cache_line_size=cls, name="cache2") + cache2.start_cache() + cache2.write_lock() + c = OcfCompletion([("cache", c_void_p), ("priv", c_void_p), ("error", c_int)]) + lib.ocf_mngt_cache_attach(cache2.cache_handle, byref(attach_cfg), c, None) + c.wait() + cache2.write_unlock() + + assert c.results["error"] + error_code = OcfErrorCode(abs(c.results["error"])) + assert error_code == OcfErrorCode.OCF_ERR_NOT_OPEN_EXC + + cache1.stop() + cache2.stop() + + lib = OcfLib.getInstance().ocf_volume_destroy(vol1) + lib = OcfLib.getInstance().ocf_volume_destroy(vol2) + + del cache_device @pytest.mark.parametrize("mode", CacheMode)