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 <josh.durgin@inktank.com>
This commit is contained in:
Josh Durgin 2013-03-31 07:48:49 -07:00
parent e83fd3b937
commit c0e3f642b1
4 changed files with 108 additions and 6 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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):
"""

View File

@ -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):