]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
Move pybind rbd module into it own directory
authorMehdi Abaakouk <sileht@redhat.com>
Thu, 11 Feb 2016 08:30:52 +0000 (09:30 +0100)
committerMehdi Abaakouk <sileht@redhat.com>
Tue, 16 Feb 2016 17:41:09 +0000 (18:41 +0100)
To allow to create a autonomous rados module with cython.
We move the current librbdpy to the rbd sub directory.

Signed-off-by: Mehdi Abaakouk <sileht@redhat.com>
src/pybind/Makefile.am
src/pybind/rbd.pyx [deleted file]
src/pybind/rbd/Makefile.am [new file with mode: 0644]
src/pybind/rbd/rbd.pyx [new file with mode: 0644]
src/pybind/rbd/setup.py [new file with mode: 0755]
src/pybind/setup.py [deleted file]

index fd23fcb52f48a3cf560374716510a2bbad5cb16f..78bc558e91628f2c27c76384a8d63a20333c6110 100644 (file)
@@ -1,4 +1,3 @@
-EXTRA_DIST += $(srcdir)/pybind/setup.py $(srcdir)/pybind/rbd.pyx
 
 if ENABLE_CLIENT
 if WITH_RADOS
@@ -12,38 +11,7 @@ PY_DISTUTILS = \
        CYTHON_BUILD_DIR="$(shell readlink -f $(builddir))/build" \
        ${PYTHON} ./setup.py
 
-pybind-all: librbd.la ${srcdir}/ceph_ver.h
-       cd $(srcdir)/pybind; $(PY_DISTUTILS) build \
-       --build-base $(shell readlink -f $(builddir))/build \
-       --verbose
-
-pybind-clean: ${srcdir}/ceph_ver.h
-       cd $(srcdir)/pybind; $(PY_DISTUTILS) clean \
-       --build-base $(shell readlink -f $(builddir))/build \
-       --verbose
-
-pybind-install-exec: ${srcdir}/ceph_ver.h
-       if test "$(DESTDIR)" ; then \
-               if lsb_release -si | grep --quiet 'Ubuntu\|Debian\|Devuan' ; then \
-                       options=--install-layout=deb ; \
-               else \
-                       options=--prefix=/usr ; \
-               fi ; \
-               root="--root=$(DESTDIR)" ; \
-       else \
-               options=--prefix=$(prefix) ; \
-       fi ; \
-       cd $(srcdir)/pybind; $(PY_DISTUTILS) build \
-       --build-base $(shell readlink -f $(builddir))/build \
-       install \
-       $$options $$root \
-       --single-version-externally-managed \
-       --record /dev/null \
-       --verbose
-
-LOCAL_ALL += pybind-all
-LOCAL_CLEAN += pybind-clean
-LOCAL_INSTALLEXEC += pybind-install-exec
+include pybind/rbd/Makefile.am
 
 endif
 endif
diff --git a/src/pybind/rbd.pyx b/src/pybind/rbd.pyx
deleted file mode 100644 (file)
index a03d975..0000000
+++ /dev/null
@@ -1,1442 +0,0 @@
-# cython: embedsignature=True
-"""
-This module is a thin wrapper around librbd.
-
-It currently provides all the synchronous methods of librbd that do
-not use callbacks.
-
-Error codes from librbd are turned into exceptions that subclass
-:class:`Error`. Almost all methods may raise :class:`Error`
-(the base class of all rbd exceptions), :class:`PermissionError`
-and :class:`IOError`, in addition to those documented for the
-method.
-"""
-# Copyright 2011 Josh Durgin
-# Copyright 2015 Hector Martin <marcan@marcan.st>
-
-from cpython cimport PyObject, ref, exc
-from libc cimport errno
-from libc.stdint cimport *
-from libc.stdlib cimport realloc, free
-
-from collections import Iterable
-
-cdef extern from "Python.h":
-    # These are in cpython/string.pxd, but use "object" types instead of
-    # PyObject*, which invokes assumptions in cpython that we need to
-    # legitimately break to implement zero-copy string buffers in Image.read().
-    # This is valid use of the Python API and documented as a special case.
-    PyObject *PyBytes_FromStringAndSize(char *v, Py_ssize_t len) except NULL
-    char* PyBytes_AsString(PyObject *string) except NULL
-    int _PyBytes_Resize(PyObject **string, Py_ssize_t newsize) except -1
-
-ctypedef int (*librbd_progress_fn_t)(uint64_t offset, uint64_t total, void* ptr)
-
-cdef extern from "rbd/librbd.h" nogil:
-    enum:
-        _RBD_FEATURE_LAYERING "RBD_FEATURE_LAYERING"
-        _RBD_FEATURE_STRIPINGV2 "RBD_FEATURE_STRIPINGV2"
-        _RBD_FEATURE_EXCLUSIVE_LOCK "RBD_FEATURE_EXCLUSIVE_LOCK"
-        _RBD_FEATURE_OBJECT_MAP "RBD_FEATURE_OBJECT_MAP"
-        _RBD_FEATURE_FAST_DIFF "RBD_FEATURE_FAST_DIFF"
-        _RBD_FEATURE_DEEP_FLATTEN "RBD_FEATURE_DEEP_FLATTEN"
-        _RBD_FEATURE_JOURNALING "RBD_FEATURE_JOURNALING"
-
-        _RBD_FEATURES_INCOMPATIBLE "RBD_FEATURES_INCOMPATIBLE"
-        _RBD_FEATURES_RW_INCOMPATIBLE "RBD_FEATURES_RW_INCOMPATIBLE"
-        _RBD_FEATURES_MUTABLE "RBD_FEATURES_MUTABLE"
-        _RBD_FEATURES_SINGLE_CLIENT "RBD_FEATURES_SINGLE_CLIENT"
-        _RBD_FEATURES_ALL "RBD_FEATURES_ALL"
-
-        _RBD_FLAG_OBJECT_MAP_INVALID "RBD_FLAG_OBJECT_MAP_INVALID"
-        _RBD_FLAG_FAST_DIFF_INVALID "RBD_FLAG_FAST_DIFF_INVALID"
-
-        _RBD_IMAGE_OPTION_FORMAT "RBD_IMAGE_OPTION_FORMAT"
-        _RBD_IMAGE_OPTION_FEATURES "RBD_IMAGE_OPTION_FEATURES"
-        _RBD_IMAGE_OPTION_ORDER "RBD_IMAGE_OPTION_ORDER"
-        _RBD_IMAGE_OPTION_STRIPE_UNIT "RBD_IMAGE_OPTION_STRIPE_UNIT"
-        _RBD_IMAGE_OPTION_STRIPE_COUNT "RBD_IMAGE_OPTION_STRIPE_COUNT"
-
-        RBD_MAX_BLOCK_NAME_SIZE
-        RBD_MAX_IMAGE_NAME_SIZE
-
-    ctypedef void* rados_ioctx_t
-    ctypedef void* rbd_image_t
-    ctypedef void* rbd_image_options_t
-
-    ctypedef struct rbd_image_info_t:
-        uint64_t size
-        uint64_t obj_size
-        uint64_t num_objs
-        int order
-        char block_name_prefix[RBD_MAX_BLOCK_NAME_SIZE]
-        uint64_t parent_pool
-        char parent_name[RBD_MAX_IMAGE_NAME_SIZE]
-
-    ctypedef struct rbd_snap_info_t:
-        uint64_t id
-        uint64_t size
-        char *name
-
-    void rbd_version(int *major, int *minor, int *extra)
-
-    void rbd_image_options_create(rbd_image_options_t* opts)
-    void rbd_image_options_destroy(rbd_image_options_t opts)
-    int rbd_image_options_set_string(rbd_image_options_t opts, int optname,
-                                     const char* optval)
-    int rbd_image_options_set_uint64(rbd_image_options_t opts, int optname,
-                                     uint64_t optval)
-    int rbd_image_options_get_string(rbd_image_options_t opts, int optname,
-                                     char* optval, size_t maxlen)
-    int rbd_image_options_get_uint64(rbd_image_options_t opts, int optname,
-                                     uint64_t* optval)
-    int rbd_image_options_unset(rbd_image_options_t opts, int optname)
-    void rbd_image_options_clear(rbd_image_options_t opts)
-    int rbd_image_options_is_empty(rbd_image_options_t opts)
-
-    int rbd_list(rados_ioctx_t io, char *names, size_t *size)
-    int rbd_create(rados_ioctx_t io, const char *name, uint64_t size,
-                   int *order)
-    int rbd_create4(rados_ioctx_t io, const char *name, uint64_t size,
-                    rbd_image_options_t opts)
-    int rbd_clone3(rados_ioctx_t p_ioctx, const char *p_name,
-                   const char *p_snapname, rados_ioctx_t c_ioctx,
-                   const char *c_name, rbd_image_options_t c_opts)
-    int rbd_remove(rados_ioctx_t io, const char *name)
-    int rbd_rename(rados_ioctx_t src_io_ctx, const char *srcname,
-                   const char *destname)
-    int rbd_open(rados_ioctx_t io, const char *name,
-                 rbd_image_t *image, const char *snap_name)
-    int rbd_open_read_only(rados_ioctx_t io, const char *name,
-                           rbd_image_t *image, const char *snap_name)
-    int rbd_close(rbd_image_t image)
-    int rbd_resize(rbd_image_t image, uint64_t size)
-    int rbd_stat(rbd_image_t image, rbd_image_info_t *info, size_t infosize)
-    int rbd_get_old_format(rbd_image_t image, uint8_t *old)
-    int rbd_get_size(rbd_image_t image, uint64_t *size)
-    int rbd_get_features(rbd_image_t image, uint64_t *features)
-    int rbd_update_features(rbd_image_t image, uint64_t features,
-                            uint8_t enabled)
-    int rbd_get_stripe_unit(rbd_image_t image, uint64_t *stripe_unit)
-    int rbd_get_stripe_count(rbd_image_t image, uint64_t *stripe_count)
-    int rbd_get_overlap(rbd_image_t image, uint64_t *overlap)
-    int rbd_get_parent_info(rbd_image_t image,
-                            char *parent_poolname, size_t ppoolnamelen,
-                            char *parent_name, size_t pnamelen,
-                            char *parent_snapname, size_t psnapnamelen)
-    int rbd_get_flags(rbd_image_t image, uint64_t *flags)
-    int rbd_is_exclusive_lock_owner(rbd_image_t image, int *is_owner)
-    ssize_t rbd_read2(rbd_image_t image, uint64_t ofs, size_t len,
-                      char *buf, int op_flags)
-    ssize_t rbd_write2(rbd_image_t image, uint64_t ofs, size_t len,
-                       const char *buf, int op_flags)
-    int rbd_discard(rbd_image_t image, uint64_t ofs, uint64_t len)
-    int rbd_copy3(rbd_image_t src, rados_ioctx_t dest_io_ctx,
-                  const char *destname, rbd_image_options_t dest_opts)
-    int rbd_snap_list(rbd_image_t image, rbd_snap_info_t *snaps,
-                      int *max_snaps)
-    void rbd_snap_list_end(rbd_snap_info_t *snaps)
-    int rbd_snap_create(rbd_image_t image, const char *snapname)
-    int rbd_snap_remove(rbd_image_t image, const char *snapname)
-    int rbd_snap_rollback(rbd_image_t image, const char *snapname)
-    int rbd_snap_rename(rbd_image_t image, const char *snapname,
-                        const char* dstsnapsname)
-    int rbd_snap_protect(rbd_image_t image, const char *snap_name)
-    int rbd_snap_unprotect(rbd_image_t image, const char *snap_name)
-    int rbd_snap_is_protected(rbd_image_t image, const char *snap_name,
-                              int *is_protected)
-    int rbd_snap_set(rbd_image_t image, const char *snapname)
-    int rbd_flatten(rbd_image_t image)
-    int rbd_rebuild_object_map(rbd_image_t image, librbd_progress_fn_t cb,
-                               void *cbdata)
-    ssize_t rbd_list_children(rbd_image_t image, char *pools, size_t *pools_len,
-                              char *images, size_t *images_len)
-    ssize_t rbd_list_lockers(rbd_image_t image, int *exclusive,
-                             char *tag, size_t *tag_len,
-                             char *clients, size_t *clients_len,
-                             char *cookies, size_t *cookies_len,
-                             char *addrs, size_t *addrs_len)
-    int rbd_lock_exclusive(rbd_image_t image, const char *cookie)
-    int rbd_lock_shared(rbd_image_t image, const char *cookie,
-                        const char *tag)
-    int rbd_unlock(rbd_image_t image, const char *cookie)
-    int rbd_break_lock(rbd_image_t image, const char *client,
-                       const char *cookie)
-
-    # We use -9000 to propagate Python exceptions. We use except? to make sure
-    # things still work as intended if -9000 happens to be a valid errno value
-    # somewhere.
-    int rbd_diff_iterate2(rbd_image_t image, const char *fromsnapname,
-                         uint64_t ofs, uint64_t len,
-                         uint8_t include_parent, uint8_t whole_object,
-                         int (*cb)(uint64_t, size_t, int, void *)
-                             nogil except? -9000,
-                         void *arg) except? -9000
-
-    int rbd_flush(rbd_image_t image)
-    int rbd_invalidate_cache(rbd_image_t image)
-
-
-RBD_FEATURE_LAYERING = _RBD_FEATURE_LAYERING
-RBD_FEATURE_STRIPINGV2 = _RBD_FEATURE_STRIPINGV2
-RBD_FEATURE_EXCLUSIVE_LOCK = _RBD_FEATURE_EXCLUSIVE_LOCK
-RBD_FEATURE_OBJECT_MAP = _RBD_FEATURE_OBJECT_MAP
-RBD_FEATURE_FAST_DIFF = _RBD_FEATURE_FAST_DIFF
-RBD_FEATURE_DEEP_FLATTEN = _RBD_FEATURE_DEEP_FLATTEN
-RBD_FEATURE_JOURNALING = _RBD_FEATURE_JOURNALING
-
-RBD_FEATURES_INCOMPATIBLE = _RBD_FEATURES_INCOMPATIBLE
-RBD_FEATURES_RW_INCOMPATIBLE = _RBD_FEATURES_RW_INCOMPATIBLE
-RBD_FEATURES_MUTABLE = _RBD_FEATURES_MUTABLE
-RBD_FEATURES_SINGLE_CLIENT = _RBD_FEATURES_SINGLE_CLIENT
-RBD_FEATURES_ALL = _RBD_FEATURES_ALL
-
-RBD_FLAG_OBJECT_MAP_INVALID = _RBD_FLAG_OBJECT_MAP_INVALID
-
-RBD_IMAGE_OPTION_FORMAT = _RBD_IMAGE_OPTION_FORMAT
-RBD_IMAGE_OPTION_FEATURES = _RBD_IMAGE_OPTION_FEATURES
-RBD_IMAGE_OPTION_ORDER = _RBD_IMAGE_OPTION_ORDER
-RBD_IMAGE_OPTION_STRIPE_UNIT = _RBD_IMAGE_OPTION_STRIPE_UNIT
-RBD_IMAGE_OPTION_STRIPE_COUNT = _RBD_IMAGE_OPTION_STRIPE_COUNT
-
-
-class Error(Exception):
-    pass
-
-
-class PermissionError(Error):
-    pass
-
-
-class ImageNotFound(Error):
-    pass
-
-
-class ImageExists(Error):
-    pass
-
-
-class IOError(Error):
-    pass
-
-
-class NoSpace(Error):
-    pass
-
-
-class IncompleteWriteError(Error):
-    pass
-
-
-class InvalidArgument(Error):
-    pass
-
-
-class LogicError(Error):
-    pass
-
-
-class ReadOnlyImage(Error):
-    pass
-
-
-class ImageBusy(Error):
-    pass
-
-
-class ImageHasSnapshots(Error):
-    pass
-
-
-class FunctionNotSupported(Error):
-    pass
-
-
-class ArgumentOutOfRange(Error):
-    pass
-
-
-class ConnectionShutdown(Error):
-    pass
-
-
-class Timeout(Error):
-    pass
-
-
-cdef errno_to_exception = {
-    errno.EPERM     : PermissionError,
-    errno.ENOENT    : ImageNotFound,
-    errno.EIO       : IOError,
-    errno.ENOSPC    : NoSpace,
-    errno.EEXIST    : ImageExists,
-    errno.EINVAL    : InvalidArgument,
-    errno.EROFS     : ReadOnlyImage,
-    errno.EBUSY     : ImageBusy,
-    errno.ENOTEMPTY : ImageHasSnapshots,
-    errno.ENOSYS    : FunctionNotSupported,
-    errno.EDOM      : ArgumentOutOfRange,
-    errno.ESHUTDOWN : ConnectionShutdown,
-    errno.ETIMEDOUT : Timeout,
-}
-
-cdef make_ex(ret, msg):
-    """
-    Translate a librbd return code into an exception.
-
-    :param ret: the return code
-    :type ret: int
-    :param msg: the error message to use
-    :type msg: str
-    :returns: a subclass of :class:`Error`
-    """
-    ret = abs(ret)
-    if ret in errno_to_exception:
-        return errno_to_exception[ret](msg)
-    else:
-        return Error(msg + (": error code %d" % ret))
-
-cdef rados_ioctx_t convert_ioctx(ioctx) except? NULL:
-    return <rados_ioctx_t><uintptr_t>ioctx.io.value
-
-cdef int no_op_progress_callback(uint64_t offset, uint64_t total, void* ptr):
-    return 0
-
-def cstr(val, name, encoding="utf-8", opt=False):
-    """
-    Create a byte string from a Python string
-
-    :param basestring val: Python string
-    :param str name: Name of the string parameter, for exceptions
-    :param str encoding: Encoding to use
-    :param bool opt: If True, None is allowed
-    :rtype: bytes
-    :raises: :class:`InvalidArgument`
-    """
-    if opt and val is None:
-        return None
-    if isinstance(val, bytes):
-        return val
-    elif isinstance(val, unicode):
-        return val.encode(encoding)
-    else:
-        raise InvalidArgument('%s must be a string' % name)
-
-def decode_cstr(val, encoding="utf-8"):
-    """
-    Decode a byte string into a Python string.
-
-    :param bytes val: byte string
-    :rtype: unicode or None
-    """
-    if val is None:
-        return None
-
-    return val.decode(encoding)
-
-
-cdef char* opt_str(s) except? NULL:
-    if s is None:
-        return NULL
-    return s
-
-cdef void* realloc_chk(void* ptr, size_t size) except NULL:
-    cdef void *ret = realloc(ptr, size)
-    if ret == NULL:
-        raise MemoryError("realloc failed")
-    return ret
-
-class RBD(object):
-    """
-    This class wraps librbd CRUD functions.
-    """
-    def version(self):
-        """
-        Get the version number of the ``librbd`` C library.
-
-        :returns: a tuple of ``(major, minor, extra)`` components of the
-                  librbd version
-        """
-        cdef int major = 0
-        cdef int minor = 0
-        cdef int extra = 0
-        rbd_version(&major, &minor, &extra)
-        return (major, minor, extra)
-
-    def create(self, ioctx, name, size, order=None, old_format=True,
-               features=0, stripe_unit=0, stripe_count=0):
-        """
-        Create an rbd image.
-
-        :param ioctx: the context in which to create the image
-        :type ioctx: :class:`rados.Ioctx`
-        :param name: what the image is called
-        :type name: str
-        :param size: how big the image is in bytes
-        :type size: int
-        :param order: the image is split into (2**order) byte objects
-        :type order: int
-        :param old_format: whether to create an old-style image that
-                           is accessible by old clients, but can't
-                           use more advanced features like layering.
-        :type old_format: bool
-        :param features: bitmask of features to enable
-        :type features: int
-        :param stripe_unit: stripe unit in bytes (default 0 for object size)
-        :type stripe_unit: int
-        :param stripe_count: objects to stripe over before looping
-        :type stripe_count: int
-        :raises: :class:`ImageExists`
-        :raises: :class:`TypeError`
-        :raises: :class:`InvalidArgument`
-        :raises: :class:`FunctionNotSupported`
-        """
-        name = cstr(name, 'name')
-        cdef:
-            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
-            char *_name = name
-            uint64_t _size = size
-            int _order = 0
-            rbd_image_options_t opts
-        if order is not None:
-            _order = order
-        if old_format:
-            if features != 0 or stripe_unit != 0 or stripe_count != 0:
-                raise InvalidArgument('format 1 images do not support feature'
-                                      ' masks or non-default striping')
-            with nogil:
-                ret = rbd_create(_ioctx, _name, _size, &_order)
-        else:
-            rbd_image_options_create(&opts)
-            try:
-                rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FORMAT,
-                                             1 if old_format else 2)
-                rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FEATURES,
-                                             features)
-                rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_ORDER,
-                                             _order)
-                rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_STRIPE_UNIT,
-                                             stripe_unit)
-                rbd_image_options_set_uint64(opts,
-                                             RBD_IMAGE_OPTION_STRIPE_COUNT,
-                                             stripe_count)
-                with nogil:
-                    ret = rbd_create4(_ioctx, _name, _size, opts)
-            finally:
-                rbd_image_options_destroy(opts)
-        if ret < 0:
-            raise make_ex(ret, 'error creating image')
-
-    def clone(self, p_ioctx, p_name, p_snapname, c_ioctx, c_name,
-              features=0, order=None, stripe_unit=0, stripe_count=0):
-        """
-        Clone a parent rbd snapshot into a COW sparse child.
-
-        :param p_ioctx: the parent context that represents the parent snap
-        :type ioctx: :class:`rados.Ioctx`
-        :param p_name: the parent image name
-        :type name: str
-        :param p_snapname: the parent image snapshot name
-        :type name: str
-        :param c_ioctx: the child context that represents the new clone
-        :type ioctx: :class:`rados.Ioctx`
-        :param c_name: the clone (child) name
-        :type name: str
-        :param features: bitmask of features to enable; if set, must include layering
-        :type features: int
-        :param order: the image is split into (2**order) byte objects
-        :type order: int
-        :param stripe_unit: stripe unit in bytes (default 0 for object size)
-        :type stripe_unit: int
-        :param stripe_count: objects to stripe over before looping
-        :type stripe_count: int
-        :raises: :class:`TypeError`
-        :raises: :class:`InvalidArgument`
-        :raises: :class:`ImageExists`
-        :raises: :class:`FunctionNotSupported`
-        :raises: :class:`ArgumentOutOfRange`
-        """
-        p_snapname = cstr(p_snapname, 'p_snapname')
-        p_name = cstr(p_name, 'p_name')
-        c_name = cstr(c_name, 'c_name')
-        cdef:
-            rados_ioctx_t _p_ioctx = convert_ioctx(p_ioctx)
-            rados_ioctx_t _c_ioctx = convert_ioctx(c_ioctx)
-            char *_p_name = p_name
-            char *_p_snapname = p_snapname
-            char *_c_name = c_name
-            rbd_image_options_t opts
-        if order is None:
-            order = 0
-
-        rbd_image_options_create(&opts)
-        try:
-            rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FEATURES,
-                                         features)
-            rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_ORDER,
-                                         order)
-            rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_STRIPE_UNIT,
-                                         stripe_unit)
-            rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_STRIPE_COUNT,
-                                         stripe_count)
-            with nogil:
-                ret = rbd_clone3(_p_ioctx, _p_name, _p_snapname,
-                                 _c_ioctx, _c_name, opts)
-        finally:
-            rbd_image_options_destroy(opts)
-        if ret < 0:
-            raise make_ex(ret, 'error creating clone')
-
-    def list(self, ioctx):
-        """
-        List image names.
-
-        :param ioctx: determines which RADOS pool is read
-        :type ioctx: :class:`rados.Ioctx`
-        :returns: list -- a list of image names
-        """
-        cdef:
-            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
-            size_t size = 512
-            char *c_names = NULL
-        try:
-            while True:
-                c_names = <char *>realloc_chk(c_names, size)
-                with nogil:
-                    ret = rbd_list(_ioctx, c_names, &size)
-                if ret >= 0:
-                    break
-                elif ret != -errno.ERANGE:
-                    raise make_ex(ret, 'error listing images')
-            return [decode_cstr(name) for name in c_names[:ret].split('\0')
-                    if name]
-        finally:
-            free(c_names)
-
-    def remove(self, ioctx, name):
-        """
-        Delete an RBD image. This may take a long time, since it does
-        not return until every object that comprises the image has
-        been deleted. Note that all snapshots must be deleted before
-        the image can be removed. If there are snapshots left,
-        :class:`ImageHasSnapshots` is raised. If the image is still
-        open, or the watch from a crashed client has not expired,
-        :class:`ImageBusy` is raised.
-
-        :param ioctx: determines which RADOS pool the image is in
-        :type ioctx: :class:`rados.Ioctx`
-        :param name: the name of the image to remove
-        :type name: str
-        :raises: :class:`ImageNotFound`, :class:`ImageBusy`,
-                 :class:`ImageHasSnapshots`
-        """
-        name = cstr(name, 'name')
-        cdef:
-            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
-            char *_name = name
-        with nogil:
-            ret = rbd_remove(_ioctx, _name)
-        if ret != 0:
-            raise make_ex(ret, 'error removing image')
-
-    def rename(self, ioctx, src, dest):
-        """
-        Rename an RBD image.
-
-        :param ioctx: determines which RADOS pool the image is in
-        :type ioctx: :class:`rados.Ioctx`
-        :param src: the current name of the image
-        :type src: str
-        :param dest: the new name of the image
-        :type dest: str
-        :raises: :class:`ImageNotFound`, :class:`ImageExists`
-        """
-        src = cstr(src, 'src')
-        dest = cstr(dest, 'dest')
-        cdef:
-            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
-            char *_src = src
-            char *_dest = dest
-        with nogil:
-            ret = rbd_rename(_ioctx, _src, _dest)
-        if ret != 0:
-            raise make_ex(ret, 'error renaming image')
-
-
-cdef int diff_iterate_cb(uint64_t offset, size_t length, int write, void *cb) \
-    except? -9000 with gil:
-    # Make sure that if we wound up with an exception from a previous callback,
-    # we stop calling back (just in case librbd ever fails to bail out on the
-    # first negative return, as older versions did)
-    if exc.PyErr_Occurred():
-        return -9000
-    ret = (<object>cb)(offset, length, bool(write))
-    if ret is None:
-        return 0
-    return ret
-
-
-cdef class Image(object):
-    """
-    This class represents an RBD image. It is used to perform I/O on
-    the image and interact with snapshots.
-
-    **Note**: Any method of this class may raise :class:`ImageNotFound`
-    if the image has been deleted.
-    """
-    cdef rbd_image_t image
-    cdef bint closed
-    cdef object name
-    cdef object ioctx
-    cdef rados_ioctx_t _ioctx
-
-    def __init__(self, ioctx, name, snapshot=None, read_only=False):
-        """
-        Open the image at the given snapshot.
-        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` should
-        be called.  For ease of use, this is done automatically when
-        an :class:`Image` is used as a context manager (see :pep:`343`).
-
-        :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
-        """
-        name = cstr(name, 'name')
-        snapshot = cstr(snapshot, 'snapshot', opt=True)
-        self.closed = True
-        self.name = name
-        # Keep around a reference to the ioctx, so it won't get deleted
-        self.ioctx = ioctx
-        cdef:
-            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
-            char *_name = name
-            char *_snapshot = opt_str(snapshot)
-        if read_only:
-            with nogil:
-                ret = rbd_open_read_only(_ioctx, _name, &self.image, _snapshot)
-        else:
-            with nogil:
-                ret = rbd_open(_ioctx, _name, &self.image, _snapshot)
-        if ret != 0:
-            raise make_ex(ret, 'error opening image %s at snapshot %s' % (name, snapshot))
-        self.closed = False
-
-    def __enter__(self):
-        return self
-
-    def __exit__(self, type_, value, traceback):
-        """
-        Closes the image. See :func:`close`
-        """
-        self.close()
-        return False
-
-    def close(self):
-        """
-        Release the resources used by this image object.
-
-        After this is called, this object should not be used.
-        """
-        if not self.closed:
-            with nogil:
-                ret = rbd_close(self.image)
-            if ret < 0:
-                raise make_ex(ret, 'error while closing image %s' % (
-                              self.name,))
-            self.closed = True
-
-    def __del__(self):
-        self.close()
-
-    def __repr__(self):
-        return "rbd.Image(ioctx, %r)" % self.name
-
-    def resize(self, size):
-        """
-        Change the size of the image.
-
-        :param size: the new size of the image
-        :type size: int
-        """
-        cdef uint64_t _size = size
-        with nogil:
-            ret = rbd_resize(self.image, _size)
-        if ret < 0:
-            raise make_ex(ret, 'error resizing image %s' % (self.name,))
-
-    def stat(self):
-        """
-        Get information about the image. Currently parent pool and
-        parent name are always -1 and ''.
-
-        :returns: dict - contains the following keys:
-
-            * ``size`` (int) - the size of the image in bytes
-
-            * ``obj_size`` (int) - the size of each object that comprises the
-              image
-
-            * ``num_objs`` (int) - the number of objects in the image
-
-            * ``order`` (int) - log_2(object_size)
-
-            * ``block_name_prefix`` (str) - the prefix of the RADOS objects used
-              to store the image
-
-            * ``parent_pool`` (int) - deprecated
-
-            * ``parent_name``  (str) - deprecated
-
-            See also :meth:`format` and :meth:`features`.
-
-        """
-        cdef rbd_image_info_t info
-        with nogil:
-            ret = rbd_stat(self.image, &info, sizeof(info))
-        if ret != 0:
-            raise make_ex(ret, 'error getting info for image %s' % (self.name,))
-        return {
-            'size'              : info.size,
-            'obj_size'          : info.obj_size,
-            'num_objs'          : info.num_objs,
-            'order'             : info.order,
-            'block_name_prefix' : decode_cstr(info.block_name_prefix),
-            'parent_pool'       : info.parent_pool,
-            'parent_name'       : info.parent_name
-            }
-
-    def parent_info(self):
-        """
-        Get information about a cloned image's parent (if any)
-
-        :returns: tuple - ``(pool name, image name, snapshot name)`` components
-                  of the parent image
-        :raises: :class:`ImageNotFound` if the image doesn't have a parent
-        """
-        cdef:
-            int ret = -errno.ERANGE
-            size_t size = 8
-            char *pool = NULL
-            char *name = NULL
-            char *snapname = NULL
-        try:
-            while ret == -errno.ERANGE and size <= 4096:
-                pool = <char *>realloc_chk(pool, size)
-                name = <char *>realloc_chk(name, size)
-                snapname = <char *>realloc_chk(snapname, size)
-                with nogil:
-                    ret = rbd_get_parent_info(self.image, pool, size, name,
-                                              size, snapname, size)
-                if ret == -errno.ERANGE:
-                    size *= 2
-
-            if ret != 0:
-                raise make_ex(ret, 'error getting parent info for image %s' % (self.name,))
-            return (decode_cstr(pool), decode_cstr(name), decode_cstr(snapname))
-        finally:
-            free(pool)
-            free(name)
-            free(snapname)
-
-    def old_format(self):
-        """
-        Find out whether the image uses the old RBD format.
-
-        :returns: bool - whether the image uses the old RBD format
-        """
-        cdef uint8_t old
-        with nogil:
-            ret = rbd_get_old_format(self.image, &old)
-        if ret != 0:
-            raise make_ex(ret, 'error getting old_format for image' % (self.name))
-        return old != 0
-
-    def size(self):
-        """
-        Get the size of the image. If open to a snapshot, returns the
-        size of that snapshot.
-
-        :returns: the size of the image in bytes
-        """
-        cdef uint64_t image_size
-        with nogil:
-            ret = rbd_get_size(self.image, &image_size)
-        if ret != 0:
-            raise make_ex(ret, 'error getting size for image' % (self.name))
-        return image_size
-
-    def features(self):
-        """
-        Gets the features bitmask of the image.
-
-        :returns: int - the features bitmask of the image
-        """
-        cdef uint64_t features
-        with nogil:
-            ret = rbd_get_features(self.image, &features)
-        if ret != 0:
-            raise make_ex(ret, 'error getting features for image' % (self.name))
-        return features
-
-    def update_features(self, features, enabled):
-        """
-        Updates the features bitmask of the image by enabling/disabling
-        a single feature.  The feature must support the ability to be
-        dynamically enabled/disabled.
-
-        :param features: feature bitmask to enable/disable
-        :type features: int
-        :param enabled: whether to enable/disable the feature
-        :type enabled: bool
-        :raises: :class:`InvalidArgument`
-        """
-        cdef:
-            uint64_t _features = features
-            uint8_t _enabled = bool(enabled)
-        with nogil:
-            ret = rbd_update_features(self.image, _features, _enabled)
-        if ret != 0:
-            raise make_ex(ret, 'error updating features for image %s' %
-                               (self.name))
-
-    def overlap(self):
-        """
-        Gets the number of overlapping bytes between the image and its parent
-        image. If open to a snapshot, returns the overlap between the snapshot
-        and the parent image.
-
-        :returns: int - the overlap in bytes
-        :raises: :class:`ImageNotFound` if the image doesn't have a parent
-        """
-        cdef uint64_t overlap
-        with nogil:
-            ret = rbd_get_overlap(self.image, &overlap)
-        if ret != 0:
-            raise make_ex(ret, 'error getting overlap for image' % (self.name))
-        return overlap
-
-    def flags(self):
-        """
-        Gets the flags bitmask of the image.
-
-        :returns: int - the flags bitmask of the image
-        """
-        cdef uint64_t flags
-        with nogil:
-            ret = rbd_get_flags(self.image, &flags)
-        if ret != 0:
-            raise make_ex(ret, 'error getting flags for image' % (self.name))
-        return flags
-
-    def is_exclusive_lock_owner(self):
-        """
-        Gets the status of the image exclusive lock.
-
-        :returns: bool - true if the image is exclusively locked
-        """
-        cdef int owner
-        with nogil:
-            ret = rbd_is_exclusive_lock_owner(self.image, &owner)
-        if ret != 0:
-            raise make_ex(ret, 'error getting lock status for image' % (self.name))
-        return owner == 1
-
-    def copy(self, dest_ioctx, dest_name, features=0, order=None, stripe_unit=0,
-             stripe_count=0):
-        """
-        Copy the image to another location.
-
-        :param dest_ioctx: determines which pool to copy into
-        :type dest_ioctx: :class:`rados.Ioctx`
-        :param dest_name: the name of the copy
-        :type dest_name: str
-        :param features: bitmask of features to enable; if set, must include layering
-        :type features: int
-        :param order: the image is split into (2**order) byte objects
-        :type order: int
-        :param stripe_unit: stripe unit in bytes (default 0 for object size)
-        :type stripe_unit: int
-        :param stripe_count: objects to stripe over before looping
-        :type stripe_count: int
-        :raises: :class:`TypeError`
-        :raises: :class:`InvalidArgument`
-        :raises: :class:`ImageExists`
-        :raises: :class:`FunctionNotSupported`
-        :raises: :class:`ArgumentOutOfRange`
-        """
-        if order is None:
-            order = 0
-        dest_name = cstr(dest_name, 'dest_name')
-        cdef:
-            rados_ioctx_t _dest_ioctx = convert_ioctx(dest_ioctx)
-            char *_dest_name = dest_name
-            rbd_image_options_t opts
-
-        rbd_image_options_create(&opts)
-        try:
-            rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FEATURES,
-                                         features)
-            rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_ORDER,
-                                         order)
-            rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_STRIPE_UNIT,
-                                         stripe_unit)
-            rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_STRIPE_COUNT,
-                                         stripe_count)
-            with nogil:
-                ret = rbd_copy3(self.image, _dest_ioctx, _dest_name, opts)
-        finally:
-            rbd_image_options_destroy(opts)
-        if ret < 0:
-            raise make_ex(ret, 'error copying image %s to %s' % (self.name, dest_name))
-
-    def list_snaps(self):
-        """
-        Iterate over the snapshots of an image.
-
-        :returns: :class:`SnapIterator`
-        """
-        return SnapIterator(self)
-
-    def create_snap(self, name):
-        """
-        Create a snapshot of the image.
-
-        :param name: the name of the snapshot
-        :type name: str
-        :raises: :class:`ImageExists`
-        """
-        name = cstr(name, 'name')
-        cdef char *_name = name
-        with nogil:
-            ret = rbd_snap_create(self.image, _name)
-        if ret != 0:
-            raise make_ex(ret, 'error creating snapshot %s from %s' % (name, self.name))
-
-    def rename_snap(self, srcname, dstname):
-        """
-        rename a snapshot of the image.
-
-        :param srcname: the src name of the snapshot
-        :type srcname: str
-        :param dstname: the dst name of the snapshot
-        :type dstname: str
-        :raises: :class:`ImageExists`
-        """
-        srcname = cstr(srcname, 'srcname')
-        dstname = cstr(dstname, 'dstname')
-        cdef:
-            char *_srcname = srcname
-            char *_dstname = dstname
-        with nogil:
-            ret = rbd_snap_rename(self.image, _srcname, _dstname)
-        if ret != 0:
-            raise make_ex(ret, 'error renaming snapshot of %s from %s to %s' % (self.name, srcname, dstname))
-
-    def remove_snap(self, name):
-        """
-        Delete a snapshot of the image.
-
-        :param name: the name of the snapshot
-        :type name: str
-        :raises: :class:`IOError`, :class:`ImageBusy`
-        """
-        name = cstr(name, 'name')
-        cdef char *_name = name
-        with nogil:
-            ret = rbd_snap_remove(self.image, _name)
-        if ret != 0:
-            raise make_ex(ret, 'error removing snapshot %s from %s' % (name, self.name))
-
-    def rollback_to_snap(self, name):
-        """
-        Revert the image to its contents at a snapshot. This is a
-        potentially expensive operation, since it rolls back each
-        object individually.
-
-        :param name: the snapshot to rollback to
-        :type name: str
-        :raises: :class:`IOError`
-        """
-        name = cstr(name, 'name')
-        cdef char *_name = name
-        with nogil:
-            ret = rbd_snap_rollback(self.image, _name)
-        if ret != 0:
-            raise make_ex(ret, 'error rolling back image %s to snapshot %s' % (self.name, name))
-
-    def protect_snap(self, name):
-        """
-        Mark a snapshot as protected. This means it can't be deleted
-        until it is unprotected.
-
-        :param name: the snapshot to protect
-        :type name: str
-        :raises: :class:`IOError`, :class:`ImageNotFound`
-        """
-        name = cstr(name, 'name')
-        cdef char *_name = name
-        with nogil:
-            ret = rbd_snap_protect(self.image, _name)
-        if ret != 0:
-            raise make_ex(ret, 'error protecting snapshot %s@%s' % (self.name, name))
-
-    def unprotect_snap(self, name):
-        """
-        Mark a snapshot unprotected. This allows it to be deleted if
-        it was protected.
-
-        :param name: the snapshot to unprotect
-        :type name: str
-        :raises: :class:`IOError`, :class:`ImageNotFound`
-        """
-        name = cstr(name, 'name')
-        cdef char *_name = name
-        with nogil:
-            ret = rbd_snap_unprotect(self.image, _name)
-        if ret != 0:
-            raise make_ex(ret, 'error unprotecting snapshot %s@%s' % (self.name, name))
-
-    def is_protected_snap(self, name):
-        """
-        Find out whether a snapshot is protected from deletion.
-
-        :param name: the snapshot to check
-        :type name: str
-        :returns: bool - whether the snapshot is protected
-        :raises: :class:`IOError`, :class:`ImageNotFound`
-        """
-        name = cstr(name, 'name')
-        cdef:
-            char *_name = name
-            int is_protected
-        with nogil:
-            ret = rbd_snap_is_protected(self.image, _name, &is_protected)
-        if ret != 0:
-            raise make_ex(ret, 'error checking if snapshot %s@%s is protected' % (self.name, name))
-        return is_protected == 1
-
-    def set_snap(self, name):
-        """
-        Set the snapshot to read from. Writes will raise ReadOnlyImage
-        while a snapshot is set. Pass None to unset the snapshot
-        (reads come from the current image) , and allow writing again.
-
-        :param name: the snapshot to read from, or None to unset the snapshot
-        :type name: str or None
-        """
-        name = cstr(name, 'name', opt=True)
-        cdef char *_name = opt_str(name)
-        with nogil:
-            ret = rbd_snap_set(self.image, _name)
-        if ret != 0:
-            raise make_ex(ret, 'error setting image %s to snapshot %s' % (self.name, name))
-
-    def read(self, offset, length, fadvise_flags=0):
-        """
-        Read data from the image. Raises :class:`InvalidArgument` if
-        part of the range specified is outside the image.
-
-        :param offset: the offset to start reading at
-        :type offset: int
-        :param length: how many bytes to read
-        :type length: int
-        :param fadvise_flags: fadvise flags for this read
-        :type fadvise_flags: int
-        :returns: str - the data read
-        :raises: :class:`InvalidArgument`, :class:`IOError`
-        """
-
-        # This usage of the Python API allows us to construct a string
-        # that librbd directly reads into, avoiding an extra copy. Although
-        # strings are normally immutable, this usage is explicitly supported
-        # for freshly created string objects.
-        cdef:
-            char *ret_buf
-            uint64_t _offset = offset
-            size_t _length = length
-            int _fadvise_flags = fadvise_flags
-            PyObject* ret_s = NULL
-        ret_s = PyBytes_FromStringAndSize(NULL, length)
-        try:
-            ret_buf = PyBytes_AsString(ret_s)
-            with nogil:
-                ret = rbd_read2(self.image, _offset, _length, ret_buf,
-                                _fadvise_flags)
-            if ret < 0:
-                raise make_ex(ret, 'error reading %s %ld~%ld' % (self.name, offset, length))
-
-            if ret != length:
-                _PyBytes_Resize(&ret_s, ret)
-
-            return <object>ret_s
-        finally:
-            # We DECREF unconditionally: the cast to object above will have
-            # INCREFed if necessary. This also takes care of exceptions,
-            # including if _PyString_Resize fails (that will free the string
-            # itself and set ret_s to NULL, hence XDECREF).
-            ref.Py_XDECREF(ret_s)
-
-    def diff_iterate(self, offset, length, from_snapshot, iterate_cb,
-                     include_parent = True, whole_object = False):
-        """
-        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.
-
-        iterate_cb may raise an exception, which will abort the diff and will be
-        propagated to the caller.
-
-        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
-        :param include_parent: True if full history diff should include parent
-        :type include_parent: bool
-        :param whole_object: True if diff extents should cover whole object
-        :type whole_object: bool
-        :raises: :class:`InvalidArgument`, :class:`IOError`,
-                 :class:`ImageNotFound`
-        """
-        from_snapshot = cstr(from_snapshot, 'from_snapshot', opt=True)
-        cdef:
-            char *_from_snapshot = opt_str(from_snapshot)
-            uint64_t _offset = offset, _length = length
-            uint8_t _include_parent = include_parent
-            uint8_t _whole_object = whole_object
-        with nogil:
-            ret = rbd_diff_iterate2(self.image, _from_snapshot, _offset,
-                                    _length, _include_parent, _whole_object,
-                                    &diff_iterate_cb, <void *>iterate_cb)
-        if ret < 0:
-            msg = 'error generating diff from snapshot %s' % from_snapshot
-            raise make_ex(ret, msg)
-
-    def write(self, data, offset, fadvise_flags=0):
-        """
-        Write data to the image. Raises :class:`InvalidArgument` if
-        part of the write would fall outside the image.
-
-        :param data: the data to be written
-        :type data: bytes
-        :param offset: where to start writing data
-        :type offset: int
-        :param fadvise_flags: fadvise flags for this write
-        :type fadvise_flags: int
-        :returns: int - the number of bytes written
-        :raises: :class:`IncompleteWriteError`, :class:`LogicError`,
-                 :class:`InvalidArgument`, :class:`IOError`
-        """
-        if not isinstance(data, bytes):
-            raise TypeError('data must be a byte string')
-        cdef:
-            uint64_t _offset = offset, length = len(data)
-            char *_data = data
-            int _fadvise_flags = fadvise_flags
-        with nogil:
-            ret = rbd_write2(self.image, _offset, length, _data, _fadvise_flags)
-
-        if ret == length:
-            return ret
-        elif ret < 0:
-            raise make_ex(ret, "error writing to %s" % (self.name,))
-        elif ret < length:
-            raise IncompleteWriteError("Wrote only %ld out of %ld bytes" % (ret, length))
-        else:
-            raise LogicError("logic error: rbd_write(%s) \
-returned %d, but %d was the maximum number of bytes it could have \
-written." % (self.name, ret, length))
-
-    def discard(self, offset, length):
-        """
-        Trim the range from the image. It will be logically filled
-        with zeroes.
-        """
-        cdef uint64_t _offset = offset, _length = length
-        with nogil:
-            ret = rbd_discard(self.image, _offset, _length)
-        if ret < 0:
-            msg = 'error discarding region %d~%d' % (offset, length)
-            raise make_ex(ret, msg)
-
-    def flush(self):
-        """
-        Block until all writes are fully flushed if caching is enabled.
-        """
-        with nogil:
-            ret = rbd_flush(self.image)
-        if ret < 0:
-            raise make_ex(ret, 'error flushing image')
-
-    def invalidate_cache(self):
-        """
-        Drop any cached data for the image.
-        """
-        with nogil:
-            ret = rbd_invalidate_cache(self.image)
-        if ret < 0:
-            raise make_ex(ret, 'error invalidating cache')
-
-    def stripe_unit(self):
-        """
-        Returns the stripe unit used for the image.
-        """
-        cdef uint64_t stripe_unit
-        with nogil:
-            ret = rbd_get_stripe_unit(self.image, &stripe_unit)
-        if ret != 0:
-            raise make_ex(ret, 'error getting stripe unit for image' % (self.name))
-        return stripe_unit
-
-    def stripe_count(self):
-        """
-        Returns the stripe count used for the image.
-        """
-        cdef uint64_t stripe_count
-        with nogil:
-            ret = rbd_get_stripe_count(self.image, &stripe_count)
-        if ret != 0:
-            raise make_ex(ret, 'error getting stripe count for image' % (self.name))
-        return stripe_count
-
-    def flatten(self):
-        """
-        Flatten clone image (copy all blocks from parent to child)
-        """
-        with nogil:
-            ret = rbd_flatten(self.image)
-        if ret < 0:
-            raise make_ex(ret, "error flattening %s" % self.name)
-
-    def rebuild_object_map(self):
-        """
-        Rebuilds the object map for the image HEAD or currently set snapshot
-        """
-        cdef librbd_progress_fn_t prog_cb = &no_op_progress_callback
-        with nogil:
-            ret = rbd_rebuild_object_map(self.image, prog_cb, NULL)
-        if ret < 0:
-            raise make_ex(ret, "error rebuilding object map %s" % self.name)
-
-    def list_children(self):
-        """
-        List children of the currently set snapshot (set via set_snap()).
-
-        :returns: list - a list of (pool name, image name) tuples
-        """
-        cdef:
-            size_t pools_size = 512, images_size = 512
-            char *c_pools = NULL
-            char *c_images = NULL
-        try:
-            while True:
-                c_pools = <char *>realloc_chk(c_pools, pools_size)
-                c_images = <char *>realloc_chk(c_images, images_size)
-                with nogil:
-                    ret = rbd_list_children(self.image, c_pools, &pools_size,
-                                            c_images, &images_size)
-                if ret >= 0:
-                    break
-                elif ret != -errno.ERANGE:
-                    raise make_ex(ret, 'error listing images')
-            if ret == 0:
-                return []
-            pools = map(decode_cstr, c_pools[:pools_size - 1].split('\0'))
-            images = map(decode_cstr, c_images[:images_size - 1].split('\0'))
-            return list(zip(pools, images))
-        finally:
-            free(c_pools)
-            free(c_images)
-
-    def list_lockers(self):
-        """
-        List clients that have locked the image and information
-        about the lock.
-
-        :returns: dict - contains the following keys:
-
-                  * ``tag`` - the tag associated with the lock (every
-                    additional locker must use the same tag)
-                  * ``exclusive`` - boolean indicating whether the
-                     lock is exclusive or shared
-                  * ``lockers`` - a list of (client, cookie, address)
-                    tuples
-        """
-        cdef:
-            size_t clients_size = 512, cookies_size = 512
-            size_t addrs_size = 512, tag_size = 512
-            int exclusive = 0
-            char *c_clients = NULL
-            char *c_cookies = NULL
-            char *c_addrs = NULL
-            char *c_tag = NULL
-
-        try:
-            while True:
-                c_clients = <char *>realloc_chk(c_clients, clients_size)
-                c_cookies = <char *>realloc_chk(c_cookies, cookies_size)
-                c_addrs = <char *>realloc_chk(c_addrs, addrs_size)
-                c_tag = <char *>realloc_chk(c_tag, tag_size)
-                with nogil:
-                    ret = rbd_list_lockers(self.image, &exclusive,
-                                           c_tag, &tag_size,
-                                           c_clients, &clients_size,
-                                           c_cookies, &cookies_size,
-                                           c_addrs, &addrs_size)
-                if ret >= 0:
-                    break
-                elif ret != -errno.ERANGE:
-                    raise make_ex(ret, 'error listing images')
-            if ret == 0:
-                return []
-            clients = map(decode_cstr, c_clients[:clients_size - 1].split('\0'))
-            cookies = map(decode_cstr, c_cookies[:cookies_size - 1].split('\0'))
-            addrs = map(decode_cstr, c_addrs[:addrs_size - 1].split('\0'))
-            return {
-                'tag'       : decode_cstr(c_tag),
-                'exclusive' : exclusive == 1,
-                'lockers'   : list(zip(clients, cookies, addrs)),
-                }
-        finally:
-            free(c_clients)
-            free(c_cookies)
-            free(c_addrs)
-            free(c_tag)
-
-    def lock_exclusive(self, cookie):
-        """
-        Take an exclusive lock on the image.
-
-        :raises: :class:`ImageBusy` if a different client or cookie locked it
-                 :class:`ImageExists` if the same client and cookie locked it
-        """
-        cookie = cstr(cookie, 'cookie')
-        cdef char *_cookie = cookie
-        with nogil:
-            ret = rbd_lock_exclusive(self.image, _cookie)
-        if ret < 0:
-            raise make_ex(ret, 'error acquiring exclusive lock on image')
-
-    def lock_shared(self, cookie, tag):
-        """
-        Take a shared lock on the image. The tag must match
-        that of the existing lockers, if any.
-
-        :raises: :class:`ImageBusy` if a different client or cookie locked it
-                 :class:`ImageExists` if the same client and cookie locked it
-        """
-        cookie = cstr(cookie, 'cookie')
-        tag = cstr(tag, 'tag')
-        cdef:
-            char *_cookie = cookie
-            char *_tag = tag
-        with nogil:
-            ret = rbd_lock_shared(self.image, _cookie, _tag)
-        if ret < 0:
-            raise make_ex(ret, 'error acquiring shared lock on image')
-
-    def unlock(self, cookie):
-        """
-        Release a lock on the image that was locked by this rados client.
-        """
-        cookie = cstr(cookie, 'cookie')
-        cdef char *_cookie = cookie
-        with nogil:
-            ret = rbd_unlock(self.image, _cookie)
-        if ret < 0:
-            raise make_ex(ret, 'error unlocking image')
-
-    def break_lock(self, client, cookie):
-        """
-        Release a lock held by another rados client.
-        """
-        client = cstr(client, 'client')
-        cookie = cstr(cookie, 'cookie')
-        cdef:
-            char *_client = client
-            char *_cookie = cookie
-        with nogil:
-            ret = rbd_break_lock(self.image, _client, _cookie)
-        if ret < 0:
-            raise make_ex(ret, 'error unlocking image')
-
-
-cdef class SnapIterator(object):
-    """
-    Iterator over snapshot info for an image.
-
-    Yields a dictionary containing information about a snapshot.
-
-    Keys are:
-
-    * ``id`` (int) - numeric identifier of the snapshot
-
-    * ``size`` (int) - size of the image at the time of snapshot (in bytes)
-
-    * ``name`` (str) - name of the snapshot
-    """
-
-    cdef rbd_snap_info_t *snaps
-    cdef int num_snaps
-
-    def __init__(self, Image image):
-        self.snaps = NULL
-        self.num_snaps = 10
-        while True:
-            self.snaps = <rbd_snap_info_t*>realloc_chk(self.snaps,
-                                                   self.num_snaps *
-                                                   sizeof(rbd_snap_info_t))
-            with nogil:
-                ret = rbd_snap_list(image.image, self.snaps, &self.num_snaps)
-            if ret >= 0:
-                self.num_snaps = ret
-                break
-            elif ret != -errno.ERANGE:
-                raise make_ex(ret, 'error listing snapshots for image %s' % (image.name,))
-
-    def __iter__(self):
-        for i in range(self.num_snaps):
-            yield {
-                'id'   : self.snaps[i].id,
-                'size' : self.snaps[i].size,
-                'name' : decode_cstr(self.snaps[i].name),
-                }
-
-    def __del__(self):
-        if self.snaps:
-            rbd_snap_list_end(self.snaps)
-            free(self.snaps)
diff --git a/src/pybind/rbd/Makefile.am b/src/pybind/rbd/Makefile.am
new file mode 100644 (file)
index 0000000..affd2de
--- /dev/null
@@ -0,0 +1,34 @@
+EXTRA_DIST += $(srcdir)/pybind/rbd/setup.py $(srcdir)/pybind/rbd/rbd.pyx
+
+rbd-pybind-all: librbd.la ${srcdir}/ceph_ver.h
+       cd $(srcdir)/pybind/rbd; $(PY_DISTUTILS) build \
+       --build-base $(shell readlink -f $(builddir))/build \
+       --verbose
+
+rbd-pybind-clean: ${srcdir}/ceph_ver.h
+       cd $(srcdir)/pybind/rbd; $(PY_DISTUTILS) clean \
+       --build-base $(shell readlink -f $(builddir))/build \
+       --verbose
+
+rbd-pybind-install-exec: ${srcdir}/ceph_ver.h
+       if test "$(DESTDIR)" ; then \
+               if lsb_release -si | grep --quiet 'Ubuntu\|Debian\|Devuan' ; then \
+                       options=--install-layout=deb ; \
+               else \
+                       options=--prefix=/usr ; \
+               fi ; \
+               root="--root=$(DESTDIR)" ; \
+       else \
+               options=--prefix=$(prefix) ; \
+       fi ; \
+       cd $(srcdir)/pybind/rbd; $(PY_DISTUTILS) build \
+       --build-base $(shell readlink -f $(builddir))/build \
+       install \
+       $$options $$root \
+       --single-version-externally-managed \
+       --record /dev/null \
+       --verbose
+
+LOCAL_ALL += rbd-pybind-all
+LOCAL_CLEAN += rbd-pybind-clean
+LOCAL_INSTALLEXEC += rbd-pybind-install-exec
diff --git a/src/pybind/rbd/rbd.pyx b/src/pybind/rbd/rbd.pyx
new file mode 100644 (file)
index 0000000..a03d975
--- /dev/null
@@ -0,0 +1,1442 @@
+# cython: embedsignature=True
+"""
+This module is a thin wrapper around librbd.
+
+It currently provides all the synchronous methods of librbd that do
+not use callbacks.
+
+Error codes from librbd are turned into exceptions that subclass
+:class:`Error`. Almost all methods may raise :class:`Error`
+(the base class of all rbd exceptions), :class:`PermissionError`
+and :class:`IOError`, in addition to those documented for the
+method.
+"""
+# Copyright 2011 Josh Durgin
+# Copyright 2015 Hector Martin <marcan@marcan.st>
+
+from cpython cimport PyObject, ref, exc
+from libc cimport errno
+from libc.stdint cimport *
+from libc.stdlib cimport realloc, free
+
+from collections import Iterable
+
+cdef extern from "Python.h":
+    # These are in cpython/string.pxd, but use "object" types instead of
+    # PyObject*, which invokes assumptions in cpython that we need to
+    # legitimately break to implement zero-copy string buffers in Image.read().
+    # This is valid use of the Python API and documented as a special case.
+    PyObject *PyBytes_FromStringAndSize(char *v, Py_ssize_t len) except NULL
+    char* PyBytes_AsString(PyObject *string) except NULL
+    int _PyBytes_Resize(PyObject **string, Py_ssize_t newsize) except -1
+
+ctypedef int (*librbd_progress_fn_t)(uint64_t offset, uint64_t total, void* ptr)
+
+cdef extern from "rbd/librbd.h" nogil:
+    enum:
+        _RBD_FEATURE_LAYERING "RBD_FEATURE_LAYERING"
+        _RBD_FEATURE_STRIPINGV2 "RBD_FEATURE_STRIPINGV2"
+        _RBD_FEATURE_EXCLUSIVE_LOCK "RBD_FEATURE_EXCLUSIVE_LOCK"
+        _RBD_FEATURE_OBJECT_MAP "RBD_FEATURE_OBJECT_MAP"
+        _RBD_FEATURE_FAST_DIFF "RBD_FEATURE_FAST_DIFF"
+        _RBD_FEATURE_DEEP_FLATTEN "RBD_FEATURE_DEEP_FLATTEN"
+        _RBD_FEATURE_JOURNALING "RBD_FEATURE_JOURNALING"
+
+        _RBD_FEATURES_INCOMPATIBLE "RBD_FEATURES_INCOMPATIBLE"
+        _RBD_FEATURES_RW_INCOMPATIBLE "RBD_FEATURES_RW_INCOMPATIBLE"
+        _RBD_FEATURES_MUTABLE "RBD_FEATURES_MUTABLE"
+        _RBD_FEATURES_SINGLE_CLIENT "RBD_FEATURES_SINGLE_CLIENT"
+        _RBD_FEATURES_ALL "RBD_FEATURES_ALL"
+
+        _RBD_FLAG_OBJECT_MAP_INVALID "RBD_FLAG_OBJECT_MAP_INVALID"
+        _RBD_FLAG_FAST_DIFF_INVALID "RBD_FLAG_FAST_DIFF_INVALID"
+
+        _RBD_IMAGE_OPTION_FORMAT "RBD_IMAGE_OPTION_FORMAT"
+        _RBD_IMAGE_OPTION_FEATURES "RBD_IMAGE_OPTION_FEATURES"
+        _RBD_IMAGE_OPTION_ORDER "RBD_IMAGE_OPTION_ORDER"
+        _RBD_IMAGE_OPTION_STRIPE_UNIT "RBD_IMAGE_OPTION_STRIPE_UNIT"
+        _RBD_IMAGE_OPTION_STRIPE_COUNT "RBD_IMAGE_OPTION_STRIPE_COUNT"
+
+        RBD_MAX_BLOCK_NAME_SIZE
+        RBD_MAX_IMAGE_NAME_SIZE
+
+    ctypedef void* rados_ioctx_t
+    ctypedef void* rbd_image_t
+    ctypedef void* rbd_image_options_t
+
+    ctypedef struct rbd_image_info_t:
+        uint64_t size
+        uint64_t obj_size
+        uint64_t num_objs
+        int order
+        char block_name_prefix[RBD_MAX_BLOCK_NAME_SIZE]
+        uint64_t parent_pool
+        char parent_name[RBD_MAX_IMAGE_NAME_SIZE]
+
+    ctypedef struct rbd_snap_info_t:
+        uint64_t id
+        uint64_t size
+        char *name
+
+    void rbd_version(int *major, int *minor, int *extra)
+
+    void rbd_image_options_create(rbd_image_options_t* opts)
+    void rbd_image_options_destroy(rbd_image_options_t opts)
+    int rbd_image_options_set_string(rbd_image_options_t opts, int optname,
+                                     const char* optval)
+    int rbd_image_options_set_uint64(rbd_image_options_t opts, int optname,
+                                     uint64_t optval)
+    int rbd_image_options_get_string(rbd_image_options_t opts, int optname,
+                                     char* optval, size_t maxlen)
+    int rbd_image_options_get_uint64(rbd_image_options_t opts, int optname,
+                                     uint64_t* optval)
+    int rbd_image_options_unset(rbd_image_options_t opts, int optname)
+    void rbd_image_options_clear(rbd_image_options_t opts)
+    int rbd_image_options_is_empty(rbd_image_options_t opts)
+
+    int rbd_list(rados_ioctx_t io, char *names, size_t *size)
+    int rbd_create(rados_ioctx_t io, const char *name, uint64_t size,
+                   int *order)
+    int rbd_create4(rados_ioctx_t io, const char *name, uint64_t size,
+                    rbd_image_options_t opts)
+    int rbd_clone3(rados_ioctx_t p_ioctx, const char *p_name,
+                   const char *p_snapname, rados_ioctx_t c_ioctx,
+                   const char *c_name, rbd_image_options_t c_opts)
+    int rbd_remove(rados_ioctx_t io, const char *name)
+    int rbd_rename(rados_ioctx_t src_io_ctx, const char *srcname,
+                   const char *destname)
+    int rbd_open(rados_ioctx_t io, const char *name,
+                 rbd_image_t *image, const char *snap_name)
+    int rbd_open_read_only(rados_ioctx_t io, const char *name,
+                           rbd_image_t *image, const char *snap_name)
+    int rbd_close(rbd_image_t image)
+    int rbd_resize(rbd_image_t image, uint64_t size)
+    int rbd_stat(rbd_image_t image, rbd_image_info_t *info, size_t infosize)
+    int rbd_get_old_format(rbd_image_t image, uint8_t *old)
+    int rbd_get_size(rbd_image_t image, uint64_t *size)
+    int rbd_get_features(rbd_image_t image, uint64_t *features)
+    int rbd_update_features(rbd_image_t image, uint64_t features,
+                            uint8_t enabled)
+    int rbd_get_stripe_unit(rbd_image_t image, uint64_t *stripe_unit)
+    int rbd_get_stripe_count(rbd_image_t image, uint64_t *stripe_count)
+    int rbd_get_overlap(rbd_image_t image, uint64_t *overlap)
+    int rbd_get_parent_info(rbd_image_t image,
+                            char *parent_poolname, size_t ppoolnamelen,
+                            char *parent_name, size_t pnamelen,
+                            char *parent_snapname, size_t psnapnamelen)
+    int rbd_get_flags(rbd_image_t image, uint64_t *flags)
+    int rbd_is_exclusive_lock_owner(rbd_image_t image, int *is_owner)
+    ssize_t rbd_read2(rbd_image_t image, uint64_t ofs, size_t len,
+                      char *buf, int op_flags)
+    ssize_t rbd_write2(rbd_image_t image, uint64_t ofs, size_t len,
+                       const char *buf, int op_flags)
+    int rbd_discard(rbd_image_t image, uint64_t ofs, uint64_t len)
+    int rbd_copy3(rbd_image_t src, rados_ioctx_t dest_io_ctx,
+                  const char *destname, rbd_image_options_t dest_opts)
+    int rbd_snap_list(rbd_image_t image, rbd_snap_info_t *snaps,
+                      int *max_snaps)
+    void rbd_snap_list_end(rbd_snap_info_t *snaps)
+    int rbd_snap_create(rbd_image_t image, const char *snapname)
+    int rbd_snap_remove(rbd_image_t image, const char *snapname)
+    int rbd_snap_rollback(rbd_image_t image, const char *snapname)
+    int rbd_snap_rename(rbd_image_t image, const char *snapname,
+                        const char* dstsnapsname)
+    int rbd_snap_protect(rbd_image_t image, const char *snap_name)
+    int rbd_snap_unprotect(rbd_image_t image, const char *snap_name)
+    int rbd_snap_is_protected(rbd_image_t image, const char *snap_name,
+                              int *is_protected)
+    int rbd_snap_set(rbd_image_t image, const char *snapname)
+    int rbd_flatten(rbd_image_t image)
+    int rbd_rebuild_object_map(rbd_image_t image, librbd_progress_fn_t cb,
+                               void *cbdata)
+    ssize_t rbd_list_children(rbd_image_t image, char *pools, size_t *pools_len,
+                              char *images, size_t *images_len)
+    ssize_t rbd_list_lockers(rbd_image_t image, int *exclusive,
+                             char *tag, size_t *tag_len,
+                             char *clients, size_t *clients_len,
+                             char *cookies, size_t *cookies_len,
+                             char *addrs, size_t *addrs_len)
+    int rbd_lock_exclusive(rbd_image_t image, const char *cookie)
+    int rbd_lock_shared(rbd_image_t image, const char *cookie,
+                        const char *tag)
+    int rbd_unlock(rbd_image_t image, const char *cookie)
+    int rbd_break_lock(rbd_image_t image, const char *client,
+                       const char *cookie)
+
+    # We use -9000 to propagate Python exceptions. We use except? to make sure
+    # things still work as intended if -9000 happens to be a valid errno value
+    # somewhere.
+    int rbd_diff_iterate2(rbd_image_t image, const char *fromsnapname,
+                         uint64_t ofs, uint64_t len,
+                         uint8_t include_parent, uint8_t whole_object,
+                         int (*cb)(uint64_t, size_t, int, void *)
+                             nogil except? -9000,
+                         void *arg) except? -9000
+
+    int rbd_flush(rbd_image_t image)
+    int rbd_invalidate_cache(rbd_image_t image)
+
+
+RBD_FEATURE_LAYERING = _RBD_FEATURE_LAYERING
+RBD_FEATURE_STRIPINGV2 = _RBD_FEATURE_STRIPINGV2
+RBD_FEATURE_EXCLUSIVE_LOCK = _RBD_FEATURE_EXCLUSIVE_LOCK
+RBD_FEATURE_OBJECT_MAP = _RBD_FEATURE_OBJECT_MAP
+RBD_FEATURE_FAST_DIFF = _RBD_FEATURE_FAST_DIFF
+RBD_FEATURE_DEEP_FLATTEN = _RBD_FEATURE_DEEP_FLATTEN
+RBD_FEATURE_JOURNALING = _RBD_FEATURE_JOURNALING
+
+RBD_FEATURES_INCOMPATIBLE = _RBD_FEATURES_INCOMPATIBLE
+RBD_FEATURES_RW_INCOMPATIBLE = _RBD_FEATURES_RW_INCOMPATIBLE
+RBD_FEATURES_MUTABLE = _RBD_FEATURES_MUTABLE
+RBD_FEATURES_SINGLE_CLIENT = _RBD_FEATURES_SINGLE_CLIENT
+RBD_FEATURES_ALL = _RBD_FEATURES_ALL
+
+RBD_FLAG_OBJECT_MAP_INVALID = _RBD_FLAG_OBJECT_MAP_INVALID
+
+RBD_IMAGE_OPTION_FORMAT = _RBD_IMAGE_OPTION_FORMAT
+RBD_IMAGE_OPTION_FEATURES = _RBD_IMAGE_OPTION_FEATURES
+RBD_IMAGE_OPTION_ORDER = _RBD_IMAGE_OPTION_ORDER
+RBD_IMAGE_OPTION_STRIPE_UNIT = _RBD_IMAGE_OPTION_STRIPE_UNIT
+RBD_IMAGE_OPTION_STRIPE_COUNT = _RBD_IMAGE_OPTION_STRIPE_COUNT
+
+
+class Error(Exception):
+    pass
+
+
+class PermissionError(Error):
+    pass
+
+
+class ImageNotFound(Error):
+    pass
+
+
+class ImageExists(Error):
+    pass
+
+
+class IOError(Error):
+    pass
+
+
+class NoSpace(Error):
+    pass
+
+
+class IncompleteWriteError(Error):
+    pass
+
+
+class InvalidArgument(Error):
+    pass
+
+
+class LogicError(Error):
+    pass
+
+
+class ReadOnlyImage(Error):
+    pass
+
+
+class ImageBusy(Error):
+    pass
+
+
+class ImageHasSnapshots(Error):
+    pass
+
+
+class FunctionNotSupported(Error):
+    pass
+
+
+class ArgumentOutOfRange(Error):
+    pass
+
+
+class ConnectionShutdown(Error):
+    pass
+
+
+class Timeout(Error):
+    pass
+
+
+cdef errno_to_exception = {
+    errno.EPERM     : PermissionError,
+    errno.ENOENT    : ImageNotFound,
+    errno.EIO       : IOError,
+    errno.ENOSPC    : NoSpace,
+    errno.EEXIST    : ImageExists,
+    errno.EINVAL    : InvalidArgument,
+    errno.EROFS     : ReadOnlyImage,
+    errno.EBUSY     : ImageBusy,
+    errno.ENOTEMPTY : ImageHasSnapshots,
+    errno.ENOSYS    : FunctionNotSupported,
+    errno.EDOM      : ArgumentOutOfRange,
+    errno.ESHUTDOWN : ConnectionShutdown,
+    errno.ETIMEDOUT : Timeout,
+}
+
+cdef make_ex(ret, msg):
+    """
+    Translate a librbd return code into an exception.
+
+    :param ret: the return code
+    :type ret: int
+    :param msg: the error message to use
+    :type msg: str
+    :returns: a subclass of :class:`Error`
+    """
+    ret = abs(ret)
+    if ret in errno_to_exception:
+        return errno_to_exception[ret](msg)
+    else:
+        return Error(msg + (": error code %d" % ret))
+
+cdef rados_ioctx_t convert_ioctx(ioctx) except? NULL:
+    return <rados_ioctx_t><uintptr_t>ioctx.io.value
+
+cdef int no_op_progress_callback(uint64_t offset, uint64_t total, void* ptr):
+    return 0
+
+def cstr(val, name, encoding="utf-8", opt=False):
+    """
+    Create a byte string from a Python string
+
+    :param basestring val: Python string
+    :param str name: Name of the string parameter, for exceptions
+    :param str encoding: Encoding to use
+    :param bool opt: If True, None is allowed
+    :rtype: bytes
+    :raises: :class:`InvalidArgument`
+    """
+    if opt and val is None:
+        return None
+    if isinstance(val, bytes):
+        return val
+    elif isinstance(val, unicode):
+        return val.encode(encoding)
+    else:
+        raise InvalidArgument('%s must be a string' % name)
+
+def decode_cstr(val, encoding="utf-8"):
+    """
+    Decode a byte string into a Python string.
+
+    :param bytes val: byte string
+    :rtype: unicode or None
+    """
+    if val is None:
+        return None
+
+    return val.decode(encoding)
+
+
+cdef char* opt_str(s) except? NULL:
+    if s is None:
+        return NULL
+    return s
+
+cdef void* realloc_chk(void* ptr, size_t size) except NULL:
+    cdef void *ret = realloc(ptr, size)
+    if ret == NULL:
+        raise MemoryError("realloc failed")
+    return ret
+
+class RBD(object):
+    """
+    This class wraps librbd CRUD functions.
+    """
+    def version(self):
+        """
+        Get the version number of the ``librbd`` C library.
+
+        :returns: a tuple of ``(major, minor, extra)`` components of the
+                  librbd version
+        """
+        cdef int major = 0
+        cdef int minor = 0
+        cdef int extra = 0
+        rbd_version(&major, &minor, &extra)
+        return (major, minor, extra)
+
+    def create(self, ioctx, name, size, order=None, old_format=True,
+               features=0, stripe_unit=0, stripe_count=0):
+        """
+        Create an rbd image.
+
+        :param ioctx: the context in which to create the image
+        :type ioctx: :class:`rados.Ioctx`
+        :param name: what the image is called
+        :type name: str
+        :param size: how big the image is in bytes
+        :type size: int
+        :param order: the image is split into (2**order) byte objects
+        :type order: int
+        :param old_format: whether to create an old-style image that
+                           is accessible by old clients, but can't
+                           use more advanced features like layering.
+        :type old_format: bool
+        :param features: bitmask of features to enable
+        :type features: int
+        :param stripe_unit: stripe unit in bytes (default 0 for object size)
+        :type stripe_unit: int
+        :param stripe_count: objects to stripe over before looping
+        :type stripe_count: int
+        :raises: :class:`ImageExists`
+        :raises: :class:`TypeError`
+        :raises: :class:`InvalidArgument`
+        :raises: :class:`FunctionNotSupported`
+        """
+        name = cstr(name, 'name')
+        cdef:
+            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
+            char *_name = name
+            uint64_t _size = size
+            int _order = 0
+            rbd_image_options_t opts
+        if order is not None:
+            _order = order
+        if old_format:
+            if features != 0 or stripe_unit != 0 or stripe_count != 0:
+                raise InvalidArgument('format 1 images do not support feature'
+                                      ' masks or non-default striping')
+            with nogil:
+                ret = rbd_create(_ioctx, _name, _size, &_order)
+        else:
+            rbd_image_options_create(&opts)
+            try:
+                rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FORMAT,
+                                             1 if old_format else 2)
+                rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FEATURES,
+                                             features)
+                rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_ORDER,
+                                             _order)
+                rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_STRIPE_UNIT,
+                                             stripe_unit)
+                rbd_image_options_set_uint64(opts,
+                                             RBD_IMAGE_OPTION_STRIPE_COUNT,
+                                             stripe_count)
+                with nogil:
+                    ret = rbd_create4(_ioctx, _name, _size, opts)
+            finally:
+                rbd_image_options_destroy(opts)
+        if ret < 0:
+            raise make_ex(ret, 'error creating image')
+
+    def clone(self, p_ioctx, p_name, p_snapname, c_ioctx, c_name,
+              features=0, order=None, stripe_unit=0, stripe_count=0):
+        """
+        Clone a parent rbd snapshot into a COW sparse child.
+
+        :param p_ioctx: the parent context that represents the parent snap
+        :type ioctx: :class:`rados.Ioctx`
+        :param p_name: the parent image name
+        :type name: str
+        :param p_snapname: the parent image snapshot name
+        :type name: str
+        :param c_ioctx: the child context that represents the new clone
+        :type ioctx: :class:`rados.Ioctx`
+        :param c_name: the clone (child) name
+        :type name: str
+        :param features: bitmask of features to enable; if set, must include layering
+        :type features: int
+        :param order: the image is split into (2**order) byte objects
+        :type order: int
+        :param stripe_unit: stripe unit in bytes (default 0 for object size)
+        :type stripe_unit: int
+        :param stripe_count: objects to stripe over before looping
+        :type stripe_count: int
+        :raises: :class:`TypeError`
+        :raises: :class:`InvalidArgument`
+        :raises: :class:`ImageExists`
+        :raises: :class:`FunctionNotSupported`
+        :raises: :class:`ArgumentOutOfRange`
+        """
+        p_snapname = cstr(p_snapname, 'p_snapname')
+        p_name = cstr(p_name, 'p_name')
+        c_name = cstr(c_name, 'c_name')
+        cdef:
+            rados_ioctx_t _p_ioctx = convert_ioctx(p_ioctx)
+            rados_ioctx_t _c_ioctx = convert_ioctx(c_ioctx)
+            char *_p_name = p_name
+            char *_p_snapname = p_snapname
+            char *_c_name = c_name
+            rbd_image_options_t opts
+        if order is None:
+            order = 0
+
+        rbd_image_options_create(&opts)
+        try:
+            rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FEATURES,
+                                         features)
+            rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_ORDER,
+                                         order)
+            rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_STRIPE_UNIT,
+                                         stripe_unit)
+            rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_STRIPE_COUNT,
+                                         stripe_count)
+            with nogil:
+                ret = rbd_clone3(_p_ioctx, _p_name, _p_snapname,
+                                 _c_ioctx, _c_name, opts)
+        finally:
+            rbd_image_options_destroy(opts)
+        if ret < 0:
+            raise make_ex(ret, 'error creating clone')
+
+    def list(self, ioctx):
+        """
+        List image names.
+
+        :param ioctx: determines which RADOS pool is read
+        :type ioctx: :class:`rados.Ioctx`
+        :returns: list -- a list of image names
+        """
+        cdef:
+            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
+            size_t size = 512
+            char *c_names = NULL
+        try:
+            while True:
+                c_names = <char *>realloc_chk(c_names, size)
+                with nogil:
+                    ret = rbd_list(_ioctx, c_names, &size)
+                if ret >= 0:
+                    break
+                elif ret != -errno.ERANGE:
+                    raise make_ex(ret, 'error listing images')
+            return [decode_cstr(name) for name in c_names[:ret].split('\0')
+                    if name]
+        finally:
+            free(c_names)
+
+    def remove(self, ioctx, name):
+        """
+        Delete an RBD image. This may take a long time, since it does
+        not return until every object that comprises the image has
+        been deleted. Note that all snapshots must be deleted before
+        the image can be removed. If there are snapshots left,
+        :class:`ImageHasSnapshots` is raised. If the image is still
+        open, or the watch from a crashed client has not expired,
+        :class:`ImageBusy` is raised.
+
+        :param ioctx: determines which RADOS pool the image is in
+        :type ioctx: :class:`rados.Ioctx`
+        :param name: the name of the image to remove
+        :type name: str
+        :raises: :class:`ImageNotFound`, :class:`ImageBusy`,
+                 :class:`ImageHasSnapshots`
+        """
+        name = cstr(name, 'name')
+        cdef:
+            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
+            char *_name = name
+        with nogil:
+            ret = rbd_remove(_ioctx, _name)
+        if ret != 0:
+            raise make_ex(ret, 'error removing image')
+
+    def rename(self, ioctx, src, dest):
+        """
+        Rename an RBD image.
+
+        :param ioctx: determines which RADOS pool the image is in
+        :type ioctx: :class:`rados.Ioctx`
+        :param src: the current name of the image
+        :type src: str
+        :param dest: the new name of the image
+        :type dest: str
+        :raises: :class:`ImageNotFound`, :class:`ImageExists`
+        """
+        src = cstr(src, 'src')
+        dest = cstr(dest, 'dest')
+        cdef:
+            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
+            char *_src = src
+            char *_dest = dest
+        with nogil:
+            ret = rbd_rename(_ioctx, _src, _dest)
+        if ret != 0:
+            raise make_ex(ret, 'error renaming image')
+
+
+cdef int diff_iterate_cb(uint64_t offset, size_t length, int write, void *cb) \
+    except? -9000 with gil:
+    # Make sure that if we wound up with an exception from a previous callback,
+    # we stop calling back (just in case librbd ever fails to bail out on the
+    # first negative return, as older versions did)
+    if exc.PyErr_Occurred():
+        return -9000
+    ret = (<object>cb)(offset, length, bool(write))
+    if ret is None:
+        return 0
+    return ret
+
+
+cdef class Image(object):
+    """
+    This class represents an RBD image. It is used to perform I/O on
+    the image and interact with snapshots.
+
+    **Note**: Any method of this class may raise :class:`ImageNotFound`
+    if the image has been deleted.
+    """
+    cdef rbd_image_t image
+    cdef bint closed
+    cdef object name
+    cdef object ioctx
+    cdef rados_ioctx_t _ioctx
+
+    def __init__(self, ioctx, name, snapshot=None, read_only=False):
+        """
+        Open the image at the given snapshot.
+        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` should
+        be called.  For ease of use, this is done automatically when
+        an :class:`Image` is used as a context manager (see :pep:`343`).
+
+        :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
+        """
+        name = cstr(name, 'name')
+        snapshot = cstr(snapshot, 'snapshot', opt=True)
+        self.closed = True
+        self.name = name
+        # Keep around a reference to the ioctx, so it won't get deleted
+        self.ioctx = ioctx
+        cdef:
+            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
+            char *_name = name
+            char *_snapshot = opt_str(snapshot)
+        if read_only:
+            with nogil:
+                ret = rbd_open_read_only(_ioctx, _name, &self.image, _snapshot)
+        else:
+            with nogil:
+                ret = rbd_open(_ioctx, _name, &self.image, _snapshot)
+        if ret != 0:
+            raise make_ex(ret, 'error opening image %s at snapshot %s' % (name, snapshot))
+        self.closed = False
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, type_, value, traceback):
+        """
+        Closes the image. See :func:`close`
+        """
+        self.close()
+        return False
+
+    def close(self):
+        """
+        Release the resources used by this image object.
+
+        After this is called, this object should not be used.
+        """
+        if not self.closed:
+            with nogil:
+                ret = rbd_close(self.image)
+            if ret < 0:
+                raise make_ex(ret, 'error while closing image %s' % (
+                              self.name,))
+            self.closed = True
+
+    def __del__(self):
+        self.close()
+
+    def __repr__(self):
+        return "rbd.Image(ioctx, %r)" % self.name
+
+    def resize(self, size):
+        """
+        Change the size of the image.
+
+        :param size: the new size of the image
+        :type size: int
+        """
+        cdef uint64_t _size = size
+        with nogil:
+            ret = rbd_resize(self.image, _size)
+        if ret < 0:
+            raise make_ex(ret, 'error resizing image %s' % (self.name,))
+
+    def stat(self):
+        """
+        Get information about the image. Currently parent pool and
+        parent name are always -1 and ''.
+
+        :returns: dict - contains the following keys:
+
+            * ``size`` (int) - the size of the image in bytes
+
+            * ``obj_size`` (int) - the size of each object that comprises the
+              image
+
+            * ``num_objs`` (int) - the number of objects in the image
+
+            * ``order`` (int) - log_2(object_size)
+
+            * ``block_name_prefix`` (str) - the prefix of the RADOS objects used
+              to store the image
+
+            * ``parent_pool`` (int) - deprecated
+
+            * ``parent_name``  (str) - deprecated
+
+            See also :meth:`format` and :meth:`features`.
+
+        """
+        cdef rbd_image_info_t info
+        with nogil:
+            ret = rbd_stat(self.image, &info, sizeof(info))
+        if ret != 0:
+            raise make_ex(ret, 'error getting info for image %s' % (self.name,))
+        return {
+            'size'              : info.size,
+            'obj_size'          : info.obj_size,
+            'num_objs'          : info.num_objs,
+            'order'             : info.order,
+            'block_name_prefix' : decode_cstr(info.block_name_prefix),
+            'parent_pool'       : info.parent_pool,
+            'parent_name'       : info.parent_name
+            }
+
+    def parent_info(self):
+        """
+        Get information about a cloned image's parent (if any)
+
+        :returns: tuple - ``(pool name, image name, snapshot name)`` components
+                  of the parent image
+        :raises: :class:`ImageNotFound` if the image doesn't have a parent
+        """
+        cdef:
+            int ret = -errno.ERANGE
+            size_t size = 8
+            char *pool = NULL
+            char *name = NULL
+            char *snapname = NULL
+        try:
+            while ret == -errno.ERANGE and size <= 4096:
+                pool = <char *>realloc_chk(pool, size)
+                name = <char *>realloc_chk(name, size)
+                snapname = <char *>realloc_chk(snapname, size)
+                with nogil:
+                    ret = rbd_get_parent_info(self.image, pool, size, name,
+                                              size, snapname, size)
+                if ret == -errno.ERANGE:
+                    size *= 2
+
+            if ret != 0:
+                raise make_ex(ret, 'error getting parent info for image %s' % (self.name,))
+            return (decode_cstr(pool), decode_cstr(name), decode_cstr(snapname))
+        finally:
+            free(pool)
+            free(name)
+            free(snapname)
+
+    def old_format(self):
+        """
+        Find out whether the image uses the old RBD format.
+
+        :returns: bool - whether the image uses the old RBD format
+        """
+        cdef uint8_t old
+        with nogil:
+            ret = rbd_get_old_format(self.image, &old)
+        if ret != 0:
+            raise make_ex(ret, 'error getting old_format for image' % (self.name))
+        return old != 0
+
+    def size(self):
+        """
+        Get the size of the image. If open to a snapshot, returns the
+        size of that snapshot.
+
+        :returns: the size of the image in bytes
+        """
+        cdef uint64_t image_size
+        with nogil:
+            ret = rbd_get_size(self.image, &image_size)
+        if ret != 0:
+            raise make_ex(ret, 'error getting size for image' % (self.name))
+        return image_size
+
+    def features(self):
+        """
+        Gets the features bitmask of the image.
+
+        :returns: int - the features bitmask of the image
+        """
+        cdef uint64_t features
+        with nogil:
+            ret = rbd_get_features(self.image, &features)
+        if ret != 0:
+            raise make_ex(ret, 'error getting features for image' % (self.name))
+        return features
+
+    def update_features(self, features, enabled):
+        """
+        Updates the features bitmask of the image by enabling/disabling
+        a single feature.  The feature must support the ability to be
+        dynamically enabled/disabled.
+
+        :param features: feature bitmask to enable/disable
+        :type features: int
+        :param enabled: whether to enable/disable the feature
+        :type enabled: bool
+        :raises: :class:`InvalidArgument`
+        """
+        cdef:
+            uint64_t _features = features
+            uint8_t _enabled = bool(enabled)
+        with nogil:
+            ret = rbd_update_features(self.image, _features, _enabled)
+        if ret != 0:
+            raise make_ex(ret, 'error updating features for image %s' %
+                               (self.name))
+
+    def overlap(self):
+        """
+        Gets the number of overlapping bytes between the image and its parent
+        image. If open to a snapshot, returns the overlap between the snapshot
+        and the parent image.
+
+        :returns: int - the overlap in bytes
+        :raises: :class:`ImageNotFound` if the image doesn't have a parent
+        """
+        cdef uint64_t overlap
+        with nogil:
+            ret = rbd_get_overlap(self.image, &overlap)
+        if ret != 0:
+            raise make_ex(ret, 'error getting overlap for image' % (self.name))
+        return overlap
+
+    def flags(self):
+        """
+        Gets the flags bitmask of the image.
+
+        :returns: int - the flags bitmask of the image
+        """
+        cdef uint64_t flags
+        with nogil:
+            ret = rbd_get_flags(self.image, &flags)
+        if ret != 0:
+            raise make_ex(ret, 'error getting flags for image' % (self.name))
+        return flags
+
+    def is_exclusive_lock_owner(self):
+        """
+        Gets the status of the image exclusive lock.
+
+        :returns: bool - true if the image is exclusively locked
+        """
+        cdef int owner
+        with nogil:
+            ret = rbd_is_exclusive_lock_owner(self.image, &owner)
+        if ret != 0:
+            raise make_ex(ret, 'error getting lock status for image' % (self.name))
+        return owner == 1
+
+    def copy(self, dest_ioctx, dest_name, features=0, order=None, stripe_unit=0,
+             stripe_count=0):
+        """
+        Copy the image to another location.
+
+        :param dest_ioctx: determines which pool to copy into
+        :type dest_ioctx: :class:`rados.Ioctx`
+        :param dest_name: the name of the copy
+        :type dest_name: str
+        :param features: bitmask of features to enable; if set, must include layering
+        :type features: int
+        :param order: the image is split into (2**order) byte objects
+        :type order: int
+        :param stripe_unit: stripe unit in bytes (default 0 for object size)
+        :type stripe_unit: int
+        :param stripe_count: objects to stripe over before looping
+        :type stripe_count: int
+        :raises: :class:`TypeError`
+        :raises: :class:`InvalidArgument`
+        :raises: :class:`ImageExists`
+        :raises: :class:`FunctionNotSupported`
+        :raises: :class:`ArgumentOutOfRange`
+        """
+        if order is None:
+            order = 0
+        dest_name = cstr(dest_name, 'dest_name')
+        cdef:
+            rados_ioctx_t _dest_ioctx = convert_ioctx(dest_ioctx)
+            char *_dest_name = dest_name
+            rbd_image_options_t opts
+
+        rbd_image_options_create(&opts)
+        try:
+            rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FEATURES,
+                                         features)
+            rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_ORDER,
+                                         order)
+            rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_STRIPE_UNIT,
+                                         stripe_unit)
+            rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_STRIPE_COUNT,
+                                         stripe_count)
+            with nogil:
+                ret = rbd_copy3(self.image, _dest_ioctx, _dest_name, opts)
+        finally:
+            rbd_image_options_destroy(opts)
+        if ret < 0:
+            raise make_ex(ret, 'error copying image %s to %s' % (self.name, dest_name))
+
+    def list_snaps(self):
+        """
+        Iterate over the snapshots of an image.
+
+        :returns: :class:`SnapIterator`
+        """
+        return SnapIterator(self)
+
+    def create_snap(self, name):
+        """
+        Create a snapshot of the image.
+
+        :param name: the name of the snapshot
+        :type name: str
+        :raises: :class:`ImageExists`
+        """
+        name = cstr(name, 'name')
+        cdef char *_name = name
+        with nogil:
+            ret = rbd_snap_create(self.image, _name)
+        if ret != 0:
+            raise make_ex(ret, 'error creating snapshot %s from %s' % (name, self.name))
+
+    def rename_snap(self, srcname, dstname):
+        """
+        rename a snapshot of the image.
+
+        :param srcname: the src name of the snapshot
+        :type srcname: str
+        :param dstname: the dst name of the snapshot
+        :type dstname: str
+        :raises: :class:`ImageExists`
+        """
+        srcname = cstr(srcname, 'srcname')
+        dstname = cstr(dstname, 'dstname')
+        cdef:
+            char *_srcname = srcname
+            char *_dstname = dstname
+        with nogil:
+            ret = rbd_snap_rename(self.image, _srcname, _dstname)
+        if ret != 0:
+            raise make_ex(ret, 'error renaming snapshot of %s from %s to %s' % (self.name, srcname, dstname))
+
+    def remove_snap(self, name):
+        """
+        Delete a snapshot of the image.
+
+        :param name: the name of the snapshot
+        :type name: str
+        :raises: :class:`IOError`, :class:`ImageBusy`
+        """
+        name = cstr(name, 'name')
+        cdef char *_name = name
+        with nogil:
+            ret = rbd_snap_remove(self.image, _name)
+        if ret != 0:
+            raise make_ex(ret, 'error removing snapshot %s from %s' % (name, self.name))
+
+    def rollback_to_snap(self, name):
+        """
+        Revert the image to its contents at a snapshot. This is a
+        potentially expensive operation, since it rolls back each
+        object individually.
+
+        :param name: the snapshot to rollback to
+        :type name: str
+        :raises: :class:`IOError`
+        """
+        name = cstr(name, 'name')
+        cdef char *_name = name
+        with nogil:
+            ret = rbd_snap_rollback(self.image, _name)
+        if ret != 0:
+            raise make_ex(ret, 'error rolling back image %s to snapshot %s' % (self.name, name))
+
+    def protect_snap(self, name):
+        """
+        Mark a snapshot as protected. This means it can't be deleted
+        until it is unprotected.
+
+        :param name: the snapshot to protect
+        :type name: str
+        :raises: :class:`IOError`, :class:`ImageNotFound`
+        """
+        name = cstr(name, 'name')
+        cdef char *_name = name
+        with nogil:
+            ret = rbd_snap_protect(self.image, _name)
+        if ret != 0:
+            raise make_ex(ret, 'error protecting snapshot %s@%s' % (self.name, name))
+
+    def unprotect_snap(self, name):
+        """
+        Mark a snapshot unprotected. This allows it to be deleted if
+        it was protected.
+
+        :param name: the snapshot to unprotect
+        :type name: str
+        :raises: :class:`IOError`, :class:`ImageNotFound`
+        """
+        name = cstr(name, 'name')
+        cdef char *_name = name
+        with nogil:
+            ret = rbd_snap_unprotect(self.image, _name)
+        if ret != 0:
+            raise make_ex(ret, 'error unprotecting snapshot %s@%s' % (self.name, name))
+
+    def is_protected_snap(self, name):
+        """
+        Find out whether a snapshot is protected from deletion.
+
+        :param name: the snapshot to check
+        :type name: str
+        :returns: bool - whether the snapshot is protected
+        :raises: :class:`IOError`, :class:`ImageNotFound`
+        """
+        name = cstr(name, 'name')
+        cdef:
+            char *_name = name
+            int is_protected
+        with nogil:
+            ret = rbd_snap_is_protected(self.image, _name, &is_protected)
+        if ret != 0:
+            raise make_ex(ret, 'error checking if snapshot %s@%s is protected' % (self.name, name))
+        return is_protected == 1
+
+    def set_snap(self, name):
+        """
+        Set the snapshot to read from. Writes will raise ReadOnlyImage
+        while a snapshot is set. Pass None to unset the snapshot
+        (reads come from the current image) , and allow writing again.
+
+        :param name: the snapshot to read from, or None to unset the snapshot
+        :type name: str or None
+        """
+        name = cstr(name, 'name', opt=True)
+        cdef char *_name = opt_str(name)
+        with nogil:
+            ret = rbd_snap_set(self.image, _name)
+        if ret != 0:
+            raise make_ex(ret, 'error setting image %s to snapshot %s' % (self.name, name))
+
+    def read(self, offset, length, fadvise_flags=0):
+        """
+        Read data from the image. Raises :class:`InvalidArgument` if
+        part of the range specified is outside the image.
+
+        :param offset: the offset to start reading at
+        :type offset: int
+        :param length: how many bytes to read
+        :type length: int
+        :param fadvise_flags: fadvise flags for this read
+        :type fadvise_flags: int
+        :returns: str - the data read
+        :raises: :class:`InvalidArgument`, :class:`IOError`
+        """
+
+        # This usage of the Python API allows us to construct a string
+        # that librbd directly reads into, avoiding an extra copy. Although
+        # strings are normally immutable, this usage is explicitly supported
+        # for freshly created string objects.
+        cdef:
+            char *ret_buf
+            uint64_t _offset = offset
+            size_t _length = length
+            int _fadvise_flags = fadvise_flags
+            PyObject* ret_s = NULL
+        ret_s = PyBytes_FromStringAndSize(NULL, length)
+        try:
+            ret_buf = PyBytes_AsString(ret_s)
+            with nogil:
+                ret = rbd_read2(self.image, _offset, _length, ret_buf,
+                                _fadvise_flags)
+            if ret < 0:
+                raise make_ex(ret, 'error reading %s %ld~%ld' % (self.name, offset, length))
+
+            if ret != length:
+                _PyBytes_Resize(&ret_s, ret)
+
+            return <object>ret_s
+        finally:
+            # We DECREF unconditionally: the cast to object above will have
+            # INCREFed if necessary. This also takes care of exceptions,
+            # including if _PyString_Resize fails (that will free the string
+            # itself and set ret_s to NULL, hence XDECREF).
+            ref.Py_XDECREF(ret_s)
+
+    def diff_iterate(self, offset, length, from_snapshot, iterate_cb,
+                     include_parent = True, whole_object = False):
+        """
+        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.
+
+        iterate_cb may raise an exception, which will abort the diff and will be
+        propagated to the caller.
+
+        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
+        :param include_parent: True if full history diff should include parent
+        :type include_parent: bool
+        :param whole_object: True if diff extents should cover whole object
+        :type whole_object: bool
+        :raises: :class:`InvalidArgument`, :class:`IOError`,
+                 :class:`ImageNotFound`
+        """
+        from_snapshot = cstr(from_snapshot, 'from_snapshot', opt=True)
+        cdef:
+            char *_from_snapshot = opt_str(from_snapshot)
+            uint64_t _offset = offset, _length = length
+            uint8_t _include_parent = include_parent
+            uint8_t _whole_object = whole_object
+        with nogil:
+            ret = rbd_diff_iterate2(self.image, _from_snapshot, _offset,
+                                    _length, _include_parent, _whole_object,
+                                    &diff_iterate_cb, <void *>iterate_cb)
+        if ret < 0:
+            msg = 'error generating diff from snapshot %s' % from_snapshot
+            raise make_ex(ret, msg)
+
+    def write(self, data, offset, fadvise_flags=0):
+        """
+        Write data to the image. Raises :class:`InvalidArgument` if
+        part of the write would fall outside the image.
+
+        :param data: the data to be written
+        :type data: bytes
+        :param offset: where to start writing data
+        :type offset: int
+        :param fadvise_flags: fadvise flags for this write
+        :type fadvise_flags: int
+        :returns: int - the number of bytes written
+        :raises: :class:`IncompleteWriteError`, :class:`LogicError`,
+                 :class:`InvalidArgument`, :class:`IOError`
+        """
+        if not isinstance(data, bytes):
+            raise TypeError('data must be a byte string')
+        cdef:
+            uint64_t _offset = offset, length = len(data)
+            char *_data = data
+            int _fadvise_flags = fadvise_flags
+        with nogil:
+            ret = rbd_write2(self.image, _offset, length, _data, _fadvise_flags)
+
+        if ret == length:
+            return ret
+        elif ret < 0:
+            raise make_ex(ret, "error writing to %s" % (self.name,))
+        elif ret < length:
+            raise IncompleteWriteError("Wrote only %ld out of %ld bytes" % (ret, length))
+        else:
+            raise LogicError("logic error: rbd_write(%s) \
+returned %d, but %d was the maximum number of bytes it could have \
+written." % (self.name, ret, length))
+
+    def discard(self, offset, length):
+        """
+        Trim the range from the image. It will be logically filled
+        with zeroes.
+        """
+        cdef uint64_t _offset = offset, _length = length
+        with nogil:
+            ret = rbd_discard(self.image, _offset, _length)
+        if ret < 0:
+            msg = 'error discarding region %d~%d' % (offset, length)
+            raise make_ex(ret, msg)
+
+    def flush(self):
+        """
+        Block until all writes are fully flushed if caching is enabled.
+        """
+        with nogil:
+            ret = rbd_flush(self.image)
+        if ret < 0:
+            raise make_ex(ret, 'error flushing image')
+
+    def invalidate_cache(self):
+        """
+        Drop any cached data for the image.
+        """
+        with nogil:
+            ret = rbd_invalidate_cache(self.image)
+        if ret < 0:
+            raise make_ex(ret, 'error invalidating cache')
+
+    def stripe_unit(self):
+        """
+        Returns the stripe unit used for the image.
+        """
+        cdef uint64_t stripe_unit
+        with nogil:
+            ret = rbd_get_stripe_unit(self.image, &stripe_unit)
+        if ret != 0:
+            raise make_ex(ret, 'error getting stripe unit for image' % (self.name))
+        return stripe_unit
+
+    def stripe_count(self):
+        """
+        Returns the stripe count used for the image.
+        """
+        cdef uint64_t stripe_count
+        with nogil:
+            ret = rbd_get_stripe_count(self.image, &stripe_count)
+        if ret != 0:
+            raise make_ex(ret, 'error getting stripe count for image' % (self.name))
+        return stripe_count
+
+    def flatten(self):
+        """
+        Flatten clone image (copy all blocks from parent to child)
+        """
+        with nogil:
+            ret = rbd_flatten(self.image)
+        if ret < 0:
+            raise make_ex(ret, "error flattening %s" % self.name)
+
+    def rebuild_object_map(self):
+        """
+        Rebuilds the object map for the image HEAD or currently set snapshot
+        """
+        cdef librbd_progress_fn_t prog_cb = &no_op_progress_callback
+        with nogil:
+            ret = rbd_rebuild_object_map(self.image, prog_cb, NULL)
+        if ret < 0:
+            raise make_ex(ret, "error rebuilding object map %s" % self.name)
+
+    def list_children(self):
+        """
+        List children of the currently set snapshot (set via set_snap()).
+
+        :returns: list - a list of (pool name, image name) tuples
+        """
+        cdef:
+            size_t pools_size = 512, images_size = 512
+            char *c_pools = NULL
+            char *c_images = NULL
+        try:
+            while True:
+                c_pools = <char *>realloc_chk(c_pools, pools_size)
+                c_images = <char *>realloc_chk(c_images, images_size)
+                with nogil:
+                    ret = rbd_list_children(self.image, c_pools, &pools_size,
+                                            c_images, &images_size)
+                if ret >= 0:
+                    break
+                elif ret != -errno.ERANGE:
+                    raise make_ex(ret, 'error listing images')
+            if ret == 0:
+                return []
+            pools = map(decode_cstr, c_pools[:pools_size - 1].split('\0'))
+            images = map(decode_cstr, c_images[:images_size - 1].split('\0'))
+            return list(zip(pools, images))
+        finally:
+            free(c_pools)
+            free(c_images)
+
+    def list_lockers(self):
+        """
+        List clients that have locked the image and information
+        about the lock.
+
+        :returns: dict - contains the following keys:
+
+                  * ``tag`` - the tag associated with the lock (every
+                    additional locker must use the same tag)
+                  * ``exclusive`` - boolean indicating whether the
+                     lock is exclusive or shared
+                  * ``lockers`` - a list of (client, cookie, address)
+                    tuples
+        """
+        cdef:
+            size_t clients_size = 512, cookies_size = 512
+            size_t addrs_size = 512, tag_size = 512
+            int exclusive = 0
+            char *c_clients = NULL
+            char *c_cookies = NULL
+            char *c_addrs = NULL
+            char *c_tag = NULL
+
+        try:
+            while True:
+                c_clients = <char *>realloc_chk(c_clients, clients_size)
+                c_cookies = <char *>realloc_chk(c_cookies, cookies_size)
+                c_addrs = <char *>realloc_chk(c_addrs, addrs_size)
+                c_tag = <char *>realloc_chk(c_tag, tag_size)
+                with nogil:
+                    ret = rbd_list_lockers(self.image, &exclusive,
+                                           c_tag, &tag_size,
+                                           c_clients, &clients_size,
+                                           c_cookies, &cookies_size,
+                                           c_addrs, &addrs_size)
+                if ret >= 0:
+                    break
+                elif ret != -errno.ERANGE:
+                    raise make_ex(ret, 'error listing images')
+            if ret == 0:
+                return []
+            clients = map(decode_cstr, c_clients[:clients_size - 1].split('\0'))
+            cookies = map(decode_cstr, c_cookies[:cookies_size - 1].split('\0'))
+            addrs = map(decode_cstr, c_addrs[:addrs_size - 1].split('\0'))
+            return {
+                'tag'       : decode_cstr(c_tag),
+                'exclusive' : exclusive == 1,
+                'lockers'   : list(zip(clients, cookies, addrs)),
+                }
+        finally:
+            free(c_clients)
+            free(c_cookies)
+            free(c_addrs)
+            free(c_tag)
+
+    def lock_exclusive(self, cookie):
+        """
+        Take an exclusive lock on the image.
+
+        :raises: :class:`ImageBusy` if a different client or cookie locked it
+                 :class:`ImageExists` if the same client and cookie locked it
+        """
+        cookie = cstr(cookie, 'cookie')
+        cdef char *_cookie = cookie
+        with nogil:
+            ret = rbd_lock_exclusive(self.image, _cookie)
+        if ret < 0:
+            raise make_ex(ret, 'error acquiring exclusive lock on image')
+
+    def lock_shared(self, cookie, tag):
+        """
+        Take a shared lock on the image. The tag must match
+        that of the existing lockers, if any.
+
+        :raises: :class:`ImageBusy` if a different client or cookie locked it
+                 :class:`ImageExists` if the same client and cookie locked it
+        """
+        cookie = cstr(cookie, 'cookie')
+        tag = cstr(tag, 'tag')
+        cdef:
+            char *_cookie = cookie
+            char *_tag = tag
+        with nogil:
+            ret = rbd_lock_shared(self.image, _cookie, _tag)
+        if ret < 0:
+            raise make_ex(ret, 'error acquiring shared lock on image')
+
+    def unlock(self, cookie):
+        """
+        Release a lock on the image that was locked by this rados client.
+        """
+        cookie = cstr(cookie, 'cookie')
+        cdef char *_cookie = cookie
+        with nogil:
+            ret = rbd_unlock(self.image, _cookie)
+        if ret < 0:
+            raise make_ex(ret, 'error unlocking image')
+
+    def break_lock(self, client, cookie):
+        """
+        Release a lock held by another rados client.
+        """
+        client = cstr(client, 'client')
+        cookie = cstr(cookie, 'cookie')
+        cdef:
+            char *_client = client
+            char *_cookie = cookie
+        with nogil:
+            ret = rbd_break_lock(self.image, _client, _cookie)
+        if ret < 0:
+            raise make_ex(ret, 'error unlocking image')
+
+
+cdef class SnapIterator(object):
+    """
+    Iterator over snapshot info for an image.
+
+    Yields a dictionary containing information about a snapshot.
+
+    Keys are:
+
+    * ``id`` (int) - numeric identifier of the snapshot
+
+    * ``size`` (int) - size of the image at the time of snapshot (in bytes)
+
+    * ``name`` (str) - name of the snapshot
+    """
+
+    cdef rbd_snap_info_t *snaps
+    cdef int num_snaps
+
+    def __init__(self, Image image):
+        self.snaps = NULL
+        self.num_snaps = 10
+        while True:
+            self.snaps = <rbd_snap_info_t*>realloc_chk(self.snaps,
+                                                   self.num_snaps *
+                                                   sizeof(rbd_snap_info_t))
+            with nogil:
+                ret = rbd_snap_list(image.image, self.snaps, &self.num_snaps)
+            if ret >= 0:
+                self.num_snaps = ret
+                break
+            elif ret != -errno.ERANGE:
+                raise make_ex(ret, 'error listing snapshots for image %s' % (image.name,))
+
+    def __iter__(self):
+        for i in range(self.num_snaps):
+            yield {
+                'id'   : self.snaps[i].id,
+                'size' : self.snaps[i].size,
+                'name' : decode_cstr(self.snaps[i].name),
+                }
+
+    def __del__(self):
+        if self.snaps:
+            rbd_snap_list_end(self.snaps)
+            free(self.snaps)
diff --git a/src/pybind/rbd/setup.py b/src/pybind/rbd/setup.py
new file mode 100755 (executable)
index 0000000..1eda454
--- /dev/null
@@ -0,0 +1,51 @@
+# Largely taken from
+# https://blog.kevin-brown.com/programming/2014/09/24/combining-autotools-and-setuptools.html
+import os, sys, os.path
+
+from setuptools.command.egg_info import egg_info
+from distutils.core import setup
+from distutils.extension import Extension
+from Cython.Build import cythonize
+
+def get_version():
+    try:
+        for line in open(os.path.join(os.path.dirname(__file__), "..", "ceph_ver.h")):
+            if "CEPH_GIT_NICE_VER" in line:
+                return line.split()[2][1:-1]
+        else:
+            return "0"
+    except IOError:
+        return "0"
+
+class EggInfoCommand(egg_info):
+    def finalize_options(self):
+        egg_info.finalize_options(self)
+        if "build" in self.distribution.command_obj:
+            build_command = self.distribution.command_obj["build"]
+            self.egg_base = build_command.build_base
+            self.egg_info = os.path.join(self.egg_base, os.path.basename(self.egg_info))
+
+# Disable cythonification if we're not really building anything
+if (len(sys.argv) >= 2 and
+    any(i in sys.argv[1:] for i in ('--help', 'clean', 'egg_info', '--version')
+    )):
+    def cythonize(x, **kwargs):
+        return x
+
+setup(
+    name = 'rbd',
+    version = get_version(),
+    description = "Python libraries for the Ceph librbd library",
+    long_description = (
+        "This package contains Python libraries for interacting with Ceph's "
+        "RBD block device library."),
+    ext_modules = cythonize([
+        Extension("rbd",
+            ["rbd.pyx"],
+            libraries=["rbd"]
+            )
+    ], build_dir=os.environ.get("CYTHON_BUILD_DIR", None)),
+    cmdclass={
+        "egg_info": EggInfoCommand,
+    },
+)
diff --git a/src/pybind/setup.py b/src/pybind/setup.py
deleted file mode 100755 (executable)
index 1eda454..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-# Largely taken from
-# https://blog.kevin-brown.com/programming/2014/09/24/combining-autotools-and-setuptools.html
-import os, sys, os.path
-
-from setuptools.command.egg_info import egg_info
-from distutils.core import setup
-from distutils.extension import Extension
-from Cython.Build import cythonize
-
-def get_version():
-    try:
-        for line in open(os.path.join(os.path.dirname(__file__), "..", "ceph_ver.h")):
-            if "CEPH_GIT_NICE_VER" in line:
-                return line.split()[2][1:-1]
-        else:
-            return "0"
-    except IOError:
-        return "0"
-
-class EggInfoCommand(egg_info):
-    def finalize_options(self):
-        egg_info.finalize_options(self)
-        if "build" in self.distribution.command_obj:
-            build_command = self.distribution.command_obj["build"]
-            self.egg_base = build_command.build_base
-            self.egg_info = os.path.join(self.egg_base, os.path.basename(self.egg_info))
-
-# Disable cythonification if we're not really building anything
-if (len(sys.argv) >= 2 and
-    any(i in sys.argv[1:] for i in ('--help', 'clean', 'egg_info', '--version')
-    )):
-    def cythonize(x, **kwargs):
-        return x
-
-setup(
-    name = 'rbd',
-    version = get_version(),
-    description = "Python libraries for the Ceph librbd library",
-    long_description = (
-        "This package contains Python libraries for interacting with Ceph's "
-        "RBD block device library."),
-    ext_modules = cythonize([
-        Extension("rbd",
-            ["rbd.pyx"],
-            libraries=["rbd"]
-            )
-    ], build_dir=os.environ.get("CYTHON_BUILD_DIR", None)),
-    cmdclass={
-        "egg_info": EggInfoCommand,
-    },
-)