From 17f2d03c2e4d2640002da7a7b92320dc51568ad0 Mon Sep 17 00:00:00 2001 From: Mykola Golub Date: Fri, 23 Oct 2020 11:38:21 +0100 Subject: [PATCH] pybind/rbd: add image async open and close methods Signed-off-by: Mykola Golub (cherry picked from commit b27db87c0391231b39ecd4b5aacf8ac24193c1cf) --- src/pybind/rbd/rbd.pyx | 123 +++++++++++++++++++++++++++++++++++- src/test/pybind/test_rbd.py | 34 ++++++++++ 2 files changed, 156 insertions(+), 1 deletion(-) diff --git a/src/pybind/rbd/rbd.pyx b/src/pybind/rbd/rbd.pyx index be4b94452fcbe..466a2ff292c41 100644 --- a/src/pybind/rbd/rbd.pyx +++ b/src/pybind/rbd/rbd.pyx @@ -466,9 +466,20 @@ cdef extern from "rbd/librbd.h" nogil: rbd_image_t *image, const char *snap_name) int rbd_open_by_id_read_only(rados_ioctx_t io, const char *image_id, rbd_image_t *image, const char *snap_name) + int rbd_aio_open(rados_ioctx_t io, const char *name, rbd_image_t *image, + const char *snap_name, rbd_completion_t c) + int rbd_aio_open_by_id(rados_ioctx_t io, const char *id, rbd_image_t *image, + const char *snap_name, rbd_completion_t c) + int rbd_aio_open_read_only(rados_ioctx_t io, const char *name, + rbd_image_t *image, const char *snap_name, + rbd_completion_t c) + int rbd_aio_open_by_id_read_only(rados_ioctx_t io, const char *id, + rbd_image_t *image, const char *snap_name, + rbd_completion_t c) int rbd_features_to_string(uint64_t features, char *str_features, size_t *size) int rbd_features_from_string(const char *str_features, uint64_t *features) int rbd_close(rbd_image_t image) + int rbd_aio_close(rbd_image_t image, rbd_completion_t c) int rbd_resize2(rbd_image_t image, uint64_t size, bint allow_shrink, librbd_progress_fn_t cb, void *cbdata) int rbd_stat(rbd_image_t image, rbd_image_info_t *info, size_t infosize) @@ -2679,6 +2690,45 @@ class RBD(object): raise make_ex(ret, 'error getting features bitmask from str') return features + def aio_open_image(self, oncomplete, ioctx, name=None, snapshot=None, + read_only=False, image_id=None): + """ + Asynchronously open the image at the given snapshot. + Specify either name or id, otherwise :class:`InvalidArgument` is raised. + + oncomplete will be called with the created Image object as + well as the completion: + + oncomplete(completion, image) + + If a snapshot is specified, the image will be read-only, unless + :func:`Image.set_snap` is called later. + + If read-only mode is used, metadata for the :class:`Image` + object (such as which snapshots exist) may become obsolete. See + the C api for more details. + + To clean up from opening the image, :func:`Image.close` or + :func:`Image.aio_close` should be called. + + :param oncomplete: what to do when open is complete + :type oncomplete: completion + :param ioctx: determines which RADOS pool the image is in + :type ioctx: :class:`rados.Ioctx` + :param name: the name of the image + :type name: str + :param snapshot: which snapshot to read from + :type snaphshot: str + :param read_only: whether to open the image in read-only mode + :type read_only: bool + :param image_id: the id of the image + :type image_id: str + :returns: :class:`Completion` - the completion object + """ + + image = Image(ioctx, name, snapshot, read_only, image_id, oncomplete) + comp, image._open_completion = image._open_completion, None + return comp cdef class MirrorPeerIterator(object): """ @@ -3321,9 +3371,10 @@ cdef class Image(object): cdef object name cdef object ioctx cdef rados_ioctx_t _ioctx + cdef Completion _open_completion def __init__(self, ioctx, name=None, snapshot=None, - read_only=False, image_id=None): + read_only=False, image_id=None, _oncomplete=None): """ Open the image at the given snapshot. Specify either name or id, otherwise :class:`InvalidArgument` is raised. @@ -3369,6 +3420,51 @@ cdef class Image(object): char *_name = opt_str(name) char *_image_id = opt_str(image_id) char *_snapshot = opt_str(snapshot) + cdef Completion completion + + if _oncomplete: + def oncomplete(completion_v): + cdef Completion _completion_v = completion_v + return_value = _completion_v.get_return_value() + if return_value == 0: + self.closed = False + if name is None: + self.name = self.get_name() + return _oncomplete(_completion_v, self) + + completion = self.__get_completion(oncomplete) + try: + completion.__persist() + if read_only: + with nogil: + if name is not None: + ret = rbd_aio_open_read_only( + _ioctx, _name, &self.image, _snapshot, + completion.rbd_comp) + else: + ret = rbd_aio_open_by_id_read_only( + _ioctx, _image_id, &self.image, _snapshot, + completion.rbd_comp) + else: + with nogil: + if name is not None: + ret = rbd_aio_open( + _ioctx, _name, &self.image, _snapshot, + completion.rbd_comp) + else: + ret = rbd_aio_open_by_id( + _ioctx, _image_id, &self.image, _snapshot, + completion.rbd_comp) + if ret != 0: + raise make_ex(ret, 'error opening image %s at snapshot %s' % + (self.name, snapshot)) + except: + completion.__unpersist() + raise + + self._open_completion = completion + return + if read_only: with nogil: if name is not None: @@ -3445,6 +3541,31 @@ cdef class Image(object): raise make_ex(ret, 'error while closing image %s' % ( self.name,)) + @requires_not_closed + def aio_close(self, oncomplete): + """ + Asynchronously close the image. + + After this is called, this object should not be used. + + :param oncomplete: what to do when close is complete + :type oncomplete: completion + :returns: :class:`Completion` - the completion object + """ + cdef Completion completion = self.__get_completion(oncomplete) + self.closed = True + try: + completion.__persist() + with nogil: + ret = rbd_aio_close(self.image, completion.rbd_comp) + if ret < 0: + raise make_ex(ret, 'error while closing image %s' % + self.name) + except: + completion.__unpersist() + raise + return completion + def __dealloc__(self): self.close() diff --git a/src/test/pybind/test_rbd.py b/src/test/pybind/test_rbd.py index 2191007873780..7242e6646dd5a 100644 --- a/src/test/pybind/test_rbd.py +++ b/src/test/pybind/test_rbd.py @@ -322,6 +322,40 @@ def test_open_by_id(): eq(image.get_name(), image_name) RBD().remove(ioctx, image_name) +def test_aio_open(): + with Rados(conffile='') as cluster: + with cluster.open_ioctx(pool_name) as ioctx: + image_name = get_temp_image_name() + order = 20 + RBD().create(ioctx, image_name, IMG_SIZE, order) + + # this is a list so that the open_cb() can modify it + image = [None] + def open_cb(_, image_): + image[0] = image_ + + comp = RBD().aio_open_image(open_cb, ioctx, image_name) + comp.wait_for_complete_and_cb() + eq(comp.get_return_value(), 0) + eq(sys.getrefcount(comp), 2) + assert_not_equal(image[0], None) + + image = image[0] + eq(image.get_name(), image_name) + check_stat(image.stat(), IMG_SIZE, order) + + closed = [False] + def close_cb(_): + closed[0] = True + + comp = image.aio_close(close_cb) + comp.wait_for_complete_and_cb() + eq(comp.get_return_value(), 0) + eq(sys.getrefcount(comp), 2) + eq(closed[0], True) + + RBD().remove(ioctx, image_name) + def test_remove_dne(): assert_raises(ImageNotFound, remove_image) -- 2.39.5