From: Josh Durgin Date: Sun, 31 Mar 2013 14:48:49 +0000 (-0700) Subject: librbd: add C and python bindings for diff_iterate X-Git-Tag: v0.62~118^2~4 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=c0e3f642b14e560209764743400c0a3f0e38f671;p=ceph.git librbd: add C and python bindings for diff_iterate The python interface is a bit awkward since it maps directly to the C interface, but it'll work well enough and not use tons of memory. Signed-off-by: Josh Durgin --- diff --git a/src/include/rbd/librbd.h b/src/include/rbd/librbd.h index 155cfff66a9f..f7ba2e4cb54b 100644 --- a/src/include/rbd/librbd.h +++ b/src/include/rbd/librbd.h @@ -313,6 +313,29 @@ typedef void (*rbd_callback_t)(rbd_completion_t cb, void *arg); ssize_t rbd_read(rbd_image_t image, uint64_t ofs, size_t len, char *buf); int64_t rbd_read_iterate(rbd_image_t image, uint64_t ofs, size_t len, int (*cb)(uint64_t, size_t, const char *, void *), void *arg); +/** + * get difference between two versions of an image + * + * This will return the differences between two versions of an image + * via a callback, which gets the offset and length and a flag + * indicating whether the extent exists (1), or is known/defined to + * be zeros (a hole, 0). If the source snapshot name is NULL, we + * interpret that as the beginning of time and return all allocated + * regions of the image. The end version is whatever is currently + * selected for the image handle (either a snapshot or the writeable + * head). + * + * @param fromsnapname start snapshot name, or NULL + * @param ofs start offset + * @param len len in bytes of region to report on + * @param cb callback to call for each allocated region + * @param arg argument to pass to the callback + * @returns 0 on success, or negative error code on error + */ +int rbd_diff_iterate(rbd_image_t image, + const char *fromsnapname, + uint64_t ofs, size_t len, + int (*cb)(uint64_t, size_t, int, void *), void *arg); ssize_t rbd_write(rbd_image_t image, uint64_t ofs, size_t len, const char *buf); int rbd_discard(rbd_image_t image, uint64_t ofs, uint64_t len); int rbd_aio_write(rbd_image_t image, uint64_t off, size_t len, const char *buf, rbd_completion_t c); diff --git a/src/librbd/librbd.cc b/src/librbd/librbd.cc index 20dbf62393b4..f5f102f1370e 100644 --- a/src/librbd/librbd.cc +++ b/src/librbd/librbd.cc @@ -1032,11 +1032,11 @@ extern "C" int64_t rbd_read_iterate(rbd_image_t image, uint64_t ofs, size_t len, return librbd::read_iterate(ictx, ofs, len, cb, arg); } -extern "C" int diff_iterate(rbd_image_t image, - const char *fromsnapname, - uint64_t ofs, uint64_t len, - int (*cb)(uint64_t, size_t, int, void *), - void *arg) +extern "C" int rbd_diff_iterate(rbd_image_t image, + const char *fromsnapname, + uint64_t ofs, uint64_t len, + int (*cb)(uint64_t, size_t, int, void *), + void *arg) { librbd::ImageCtx *ictx = (librbd::ImageCtx *)image; return librbd::diff_iterate(ictx, fromsnapname, ofs, len, cb, arg); diff --git a/src/pybind/rbd.py b/src/pybind/rbd.py index 4ce1a0e819b2..b542b12efc53 100644 --- a/src/pybind/rbd.py +++ b/src/pybind/rbd.py @@ -16,7 +16,8 @@ methods, a :class:`TypeError` will be raised. """ # Copyright 2011 Josh Durgin from ctypes import CDLL, c_char, c_char_p, c_size_t, c_void_p, c_int, \ - create_string_buffer, byref, Structure, c_uint64, c_int64, c_uint8 + create_string_buffer, byref, Structure, c_uint64, c_int64, c_uint8, \ + CFUNCTYPE, pointer import ctypes import errno @@ -628,6 +629,58 @@ class Image(object): raise make_ex(ret, 'error reading %s %ld~%ld' % (self.image, offset, length)) return ctypes.string_at(ret_buf, ret) + def diff_iterate(self, offset, length, from_snapshot, iterate_cb): + """ + Iterate over the changed extents of an image. + + This will call iterate_cb with three arguments: + + (offset, length, exists) + + where the changed extent starts at offset bytes, continues for + length bytes, and is full of data (if exists is True) or zeroes + (if exists is False). + + If from_snapshot is None, it is interpreted as the beginning + of time and this generates all allocated extents. + + The end version is whatever is currently selected (via set_snap) + for the image. + + Raises :class:`InvalidArgument` if from_snapshot is after + the currently set snapshot. + + Raises :class:`ImageNotFound` if from_snapshot is not the name + of a snapshot of the image. + + :param offset: start offset in bytes + :type offset: int + :param length: size of region to report on, in bytes + :type length: int + :param from_snapshot: starting snapshot name, or None + :type from_snapshot: str or None + :param iterate_cb: function to call for each extent + :type iterate_cb: function acception arguments for offset, + length, and exists + :raises: :class:`InvalidArgument`, :class:`IOError`, + :class:`ImageNotFound` + """ + if from_snapshot is not None and not isinstance(from_snapshot, str): + raise TypeError('client must be a string') + + RBD_DIFF_CB = CFUNCTYPE(c_int, c_uint64, c_size_t, c_int, c_void_p) + cb_holder = DiffIterateCB(iterate_cb) + cb = RBD_DIFF_CB(cb_holder.callback) + ret = self.librbd.diff_iterate(self.image, + c_char_p(from_snapshot), + c_uint64(offset), + c_uint64(length), + cb, + c_void_p(None)) + if ret < 0: + msg = 'error generating diff from snapshot %s' % from_snapshot + raise make_ex(ret, msg) + def write(self, data, offset): """ Write data to the image. Raises :class:`InvalidArgument` if @@ -794,6 +847,13 @@ written." % (self.name, ret, length)) if ret < 0: raise make_ex(ret, 'error unlocking image') +class DiffIterateCB(object): + def __init__(self, cb): + self.cb = cb + + def callback(self, offset, length, exists, unused): + self.cb(offset, length, exists == 1) + return 0 class SnapIterator(object): """ diff --git a/src/test/pybind/test_rbd.py b/src/test/pybind/test_rbd.py index 821d721ebf17..9c59f218b468 100644 --- a/src/test/pybind/test_rbd.py +++ b/src/test/pybind/test_rbd.py @@ -454,6 +454,25 @@ class TestImage(object): self.image.unlock(str(i)) eq([], self.image.list_lockers()) + def test_diff_iterate(self): + check_diff(self.image, 0, IMG_SIZE, None, []) + self.image.write('a' * 256, 0) + check_diff(self.image, 0, IMG_SIZE, None, [(0, 256, True)]) + self.image.write('b' * 256, 256) + check_diff(self.image, 0, IMG_SIZE, None, [(0, 512, True)]) + + +def check_diff(image, offset, length, from_snapshot, expected): + cb_holder = DiffExtents() + image.diff_iterate(0, IMG_SIZE, None, cb_holder.callback) + eq(cb_holder.extents, expected) + +class DiffExtents(object): + def __init__(self): + self.extents = [] + + def callback(self, offset, length, exists): + self.extents.append((offset, length, exists)) class TestClone(object):