initial code repo
[stor4nfv.git] / src / ceph / src / pybind / rados / rados.pyx
diff --git a/src/ceph/src/pybind/rados/rados.pyx b/src/ceph/src/pybind/rados/rados.pyx
new file mode 100644 (file)
index 0000000..1131801
--- /dev/null
@@ -0,0 +1,3852 @@
+# cython: embedsignature=True
+"""
+This module is a thin wrapper around librados.
+
+Error codes from librados are turned into exceptions that subclass
+:class:`Error`. Almost all methods may raise :class:`Error(the base class of all rados exceptions), :class:`PermissionError`
+(the base class of all rados exceptions), :class:`PermissionError`
+and :class:`IOError`, in addition to those documented for the
+method.
+"""
+# Copyright 2011 Josh Durgin
+# Copyright 2011, Hannu Valtonen <hannu.valtonen@ormod.com>
+# Copyright 2015 Hector Martin <marcan@marcan.st>
+# Copyright 2016 Mehdi Abaakouk <sileht@redhat.com>
+
+from cpython cimport PyObject, ref
+from cpython.pycapsule cimport *
+from libc cimport errno
+from libc.stdint cimport *
+from libc.stdlib cimport malloc, realloc, free
+
+import sys
+import threading
+import time
+
+from collections import Callable
+from datetime import datetime
+from functools import partial, wraps
+from itertools import chain
+
+# Are we running Python 2.x
+if sys.version_info[0] < 3:
+    str_type = basestring
+else:
+    str_type = str
+
+
+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 Ioctx.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
+    void PyEval_InitThreads()
+
+
+cdef extern from "time.h":
+    ctypedef long int time_t
+    ctypedef long int suseconds_t
+
+
+cdef extern from "sys/time.h":
+    cdef struct timeval:
+        time_t tv_sec
+        suseconds_t tv_usec
+
+
+cdef extern from "rados/rados_types.h" nogil:
+    cdef char* _LIBRADOS_ALL_NSPACES "LIBRADOS_ALL_NSPACES"
+
+
+cdef extern from "rados/librados.h" nogil:
+    enum:
+        _LIBRADOS_OP_FLAG_EXCL "LIBRADOS_OP_FLAG_EXCL"
+        _LIBRADOS_OP_FLAG_FAILOK "LIBRADOS_OP_FLAG_FAILOK"
+        _LIBRADOS_OP_FLAG_FADVISE_RANDOM "LIBRADOS_OP_FLAG_FADVISE_RANDOM"
+        _LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL "LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL"
+        _LIBRADOS_OP_FLAG_FADVISE_WILLNEED "LIBRADOS_OP_FLAG_FADVISE_WILLNEED"
+        _LIBRADOS_OP_FLAG_FADVISE_DONTNEED "LIBRADOS_OP_FLAG_FADVISE_DONTNEED"
+        _LIBRADOS_OP_FLAG_FADVISE_NOCACHE "LIBRADOS_OP_FLAG_FADVISE_NOCACHE"
+
+
+    enum:
+        _LIBRADOS_OPERATION_NOFLAG "LIBRADOS_OPERATION_NOFLAG"
+        _LIBRADOS_OPERATION_BALANCE_READS "LIBRADOS_OPERATION_BALANCE_READS"
+        _LIBRADOS_OPERATION_LOCALIZE_READS "LIBRADOS_OPERATION_LOCALIZE_READS"
+        _LIBRADOS_OPERATION_ORDER_READS_WRITES "LIBRADOS_OPERATION_ORDER_READS_WRITES"
+        _LIBRADOS_OPERATION_IGNORE_CACHE "LIBRADOS_OPERATION_IGNORE_CACHE"
+        _LIBRADOS_OPERATION_SKIPRWLOCKS "LIBRADOS_OPERATION_SKIPRWLOCKS"
+        _LIBRADOS_OPERATION_IGNORE_OVERLAY "LIBRADOS_OPERATION_IGNORE_OVERLAY"
+        _LIBRADOS_CREATE_EXCLUSIVE "LIBRADOS_CREATE_EXCLUSIVE"
+        _LIBRADOS_CREATE_IDEMPOTENT "LIBRADOS_CREATE_IDEMPOTENT"
+
+    cdef uint64_t _LIBRADOS_SNAP_HEAD "LIBRADOS_SNAP_HEAD"
+
+    ctypedef void* rados_t
+    ctypedef void* rados_config_t
+    ctypedef void* rados_ioctx_t
+    ctypedef void* rados_xattrs_iter_t
+    ctypedef void* rados_omap_iter_t
+    ctypedef void* rados_list_ctx_t
+    ctypedef uint64_t rados_snap_t
+    ctypedef void *rados_write_op_t
+    ctypedef void *rados_read_op_t
+    ctypedef void *rados_completion_t
+    ctypedef void (*rados_callback_t)(rados_completion_t cb, void *arg)
+    ctypedef void (*rados_log_callback_t)(void *arg, const char *line, const char *who,
+                                          uint64_t sec, uint64_t nsec, uint64_t seq, const char *level, const char *msg)
+    ctypedef void (*rados_log_callback2_t)(void *arg, const char *line, const char *channel, const char *who, const char *name,
+                                          uint64_t sec, uint64_t nsec, uint64_t seq, const char *level, const char *msg)
+
+
+    cdef struct rados_cluster_stat_t:
+        uint64_t kb
+        uint64_t kb_used
+        uint64_t kb_avail
+        uint64_t num_objects
+
+    cdef struct rados_pool_stat_t:
+        uint64_t num_bytes
+        uint64_t num_kb
+        uint64_t num_objects
+        uint64_t num_object_clones
+        uint64_t num_object_copies
+        uint64_t num_objects_missing_on_primary
+        uint64_t num_objects_unfound
+        uint64_t num_objects_degraded
+        uint64_t num_rd
+        uint64_t num_rd_kb
+        uint64_t num_wr
+        uint64_t num_wr_kb
+
+    void rados_buffer_free(char *buf)
+
+    void rados_version(int *major, int *minor, int *extra)
+    int rados_create2(rados_t *pcluster, const char *const clustername,
+                      const char * const name, uint64_t flags)
+    int rados_create_with_context(rados_t *cluster, rados_config_t cct)
+    int rados_connect(rados_t cluster)
+    void rados_shutdown(rados_t cluster)
+    int rados_conf_read_file(rados_t cluster, const char *path)
+    int rados_conf_parse_argv_remainder(rados_t cluster, int argc, const char **argv, const char **remargv)
+    int rados_conf_parse_env(rados_t cluster, const char *var)
+    int rados_conf_set(rados_t cluster, char *option, const char *value)
+    int rados_conf_get(rados_t cluster, char *option, char *buf, size_t len)
+
+    int rados_ioctx_pool_stat(rados_ioctx_t io, rados_pool_stat_t *stats)
+    int64_t rados_pool_lookup(rados_t cluster, const char *pool_name)
+    int rados_pool_reverse_lookup(rados_t cluster, int64_t id, char *buf, size_t maxlen)
+    int rados_pool_create(rados_t cluster, const char *pool_name)
+    int rados_pool_create_with_auid(rados_t cluster, const char *pool_name, uint64_t auid)
+    int rados_pool_create_with_crush_rule(rados_t cluster, const char *pool_name, uint8_t crush_rule_num)
+    int rados_pool_create_with_all(rados_t cluster, const char *pool_name, uint64_t auid, uint8_t crush_rule_num)
+    int rados_pool_get_base_tier(rados_t cluster, int64_t pool, int64_t *base_tier)
+    int rados_pool_list(rados_t cluster, char *buf, size_t len)
+    int rados_pool_delete(rados_t cluster, const char *pool_name)
+    int rados_inconsistent_pg_list(rados_t cluster, int64_t pool, char *buf, size_t len)
+
+    int rados_cluster_stat(rados_t cluster, rados_cluster_stat_t *result)
+    int rados_cluster_fsid(rados_t cluster, char *buf, size_t len)
+    int rados_blacklist_add(rados_t cluster, char *client_address, uint32_t expire_seconds)
+    int rados_application_enable(rados_ioctx_t io, const char *app_name,
+                                 int force)
+    int rados_application_list(rados_ioctx_t io, char *values,
+                             size_t *values_len)
+    int rados_application_metadata_get(rados_ioctx_t io, const char *app_name,
+                                       const char *key, char *value,
+                                       size_t *value_len)
+    int rados_application_metadata_set(rados_ioctx_t io, const char *app_name,
+                                       const char *key, const char *value)
+    int rados_application_metadata_remove(rados_ioctx_t io,
+                                          const char *app_name, const char *key)
+    int rados_application_metadata_list(rados_ioctx_t io,
+                                        const char *app_name, char *keys,
+                                        size_t *key_len, char *values,
+                                        size_t *value_len)
+    int rados_ping_monitor(rados_t cluster, const char *mon_id, char **outstr, size_t *outstrlen)
+    int rados_mon_command(rados_t cluster, const char **cmd, size_t cmdlen,
+                          const char *inbuf, size_t inbuflen,
+                          char **outbuf, size_t *outbuflen,
+                          char **outs, size_t *outslen)
+    int rados_mgr_command(rados_t cluster, const char **cmd, size_t cmdlen,
+                          const char *inbuf, size_t inbuflen,
+                          char **outbuf, size_t *outbuflen,
+                          char **outs, size_t *outslen)
+    int rados_mon_command_target(rados_t cluster, const char *name, const char **cmd, size_t cmdlen,
+                                 const char *inbuf, size_t inbuflen,
+                                 char **outbuf, size_t *outbuflen,
+                                 char **outs, size_t *outslen)
+    int rados_osd_command(rados_t cluster, int osdid, const char **cmd, size_t cmdlen,
+                          const char *inbuf, size_t inbuflen,
+                          char **outbuf, size_t *outbuflen,
+                          char **outs, size_t *outslen)
+    int rados_pg_command(rados_t cluster, const char *pgstr, const char **cmd, size_t cmdlen,
+                         const char *inbuf, size_t inbuflen,
+                         char **outbuf, size_t *outbuflen,
+                         char **outs, size_t *outslen)
+    int rados_monitor_log(rados_t cluster, const char *level, rados_log_callback_t cb, void *arg)
+    int rados_monitor_log2(rados_t cluster, const char *level, rados_log_callback2_t cb, void *arg)
+
+    int rados_wait_for_latest_osdmap(rados_t cluster)
+
+    int rados_ioctx_create(rados_t cluster, const char *pool_name, rados_ioctx_t *ioctx)
+    void rados_ioctx_destroy(rados_ioctx_t io)
+    int rados_ioctx_pool_set_auid(rados_ioctx_t io, uint64_t auid)
+    void rados_ioctx_locator_set_key(rados_ioctx_t io, const char *key)
+    void rados_ioctx_set_namespace(rados_ioctx_t io, const char * nspace)
+
+    uint64_t rados_get_last_version(rados_ioctx_t io)
+    int rados_stat(rados_ioctx_t io, const char *o, uint64_t *psize, time_t *pmtime)
+    int rados_write(rados_ioctx_t io, const char *oid, const char *buf, size_t len, uint64_t off)
+    int rados_write_full(rados_ioctx_t io, const char *oid, const char *buf, size_t len)
+    int rados_append(rados_ioctx_t io, const char *oid, const char *buf, size_t len)
+    int rados_read(rados_ioctx_t io, const char *oid, char *buf, size_t len, uint64_t off)
+    int rados_remove(rados_ioctx_t io, const char *oid)
+    int rados_trunc(rados_ioctx_t io, const char *oid, uint64_t size)
+    int rados_getxattr(rados_ioctx_t io, const char *o, const char *name, char *buf, size_t len)
+    int rados_setxattr(rados_ioctx_t io, const char *o, const char *name, const char *buf, size_t len)
+    int rados_rmxattr(rados_ioctx_t io, const char *o, const char *name)
+    int rados_getxattrs(rados_ioctx_t io, const char *oid, rados_xattrs_iter_t *iter)
+    int rados_getxattrs_next(rados_xattrs_iter_t iter, const char **name, const char **val, size_t *len)
+    void rados_getxattrs_end(rados_xattrs_iter_t iter)
+
+    int rados_nobjects_list_open(rados_ioctx_t io, rados_list_ctx_t *ctx)
+    int rados_nobjects_list_next(rados_list_ctx_t ctx, const char **entry, const char **key, const char **nspace)
+    void rados_nobjects_list_close(rados_list_ctx_t ctx)
+
+    int rados_ioctx_snap_rollback(rados_ioctx_t io, const char * oid, const char * snapname)
+    int rados_ioctx_snap_create(rados_ioctx_t io, const char * snapname)
+    int rados_ioctx_snap_remove(rados_ioctx_t io, const char * snapname)
+    int rados_ioctx_snap_lookup(rados_ioctx_t io, const char * name, rados_snap_t * id)
+    int rados_ioctx_snap_get_name(rados_ioctx_t io, rados_snap_t id, char * name, int maxlen)
+    void rados_ioctx_snap_set_read(rados_ioctx_t io, rados_snap_t snap)
+    int rados_ioctx_snap_list(rados_ioctx_t io, rados_snap_t * snaps, int maxlen)
+    int rados_ioctx_snap_get_stamp(rados_ioctx_t io, rados_snap_t id, time_t * t)
+
+    int rados_lock_exclusive(rados_ioctx_t io, const char * oid, const char * name,
+                             const char * cookie, const char * desc,
+                             timeval * duration, uint8_t flags)
+    int rados_lock_shared(rados_ioctx_t io, const char * o, const char * name,
+                          const char * cookie, const char * tag, const char * desc,
+                          timeval * duration, uint8_t flags)
+    int rados_unlock(rados_ioctx_t io, const char * o, const char * name, const char * cookie)
+
+    rados_write_op_t rados_create_write_op()
+    void rados_release_write_op(rados_write_op_t write_op)
+
+    rados_read_op_t rados_create_read_op()
+    void rados_release_read_op(rados_read_op_t read_op)
+
+    int rados_aio_create_completion(void * cb_arg, rados_callback_t cb_complete, rados_callback_t cb_safe, rados_completion_t * pc)
+    void rados_aio_release(rados_completion_t c)
+    int rados_aio_stat(rados_ioctx_t io, const char *oid, rados_completion_t completion, uint64_t *psize, time_t *pmtime)
+    int rados_aio_write(rados_ioctx_t io, const char * oid, rados_completion_t completion, const char * buf, size_t len, uint64_t off)
+    int rados_aio_append(rados_ioctx_t io, const char * oid, rados_completion_t completion, const char * buf, size_t len)
+    int rados_aio_write_full(rados_ioctx_t io, const char * oid, rados_completion_t completion, const char * buf, size_t len)
+    int rados_aio_remove(rados_ioctx_t io, const char * oid, rados_completion_t completion)
+    int rados_aio_read(rados_ioctx_t io, const char * oid, rados_completion_t completion, char * buf, size_t len, uint64_t off)
+    int rados_aio_flush(rados_ioctx_t io)
+
+    int rados_aio_get_return_value(rados_completion_t c)
+    int rados_aio_wait_for_complete_and_cb(rados_completion_t c)
+    int rados_aio_wait_for_safe_and_cb(rados_completion_t c)
+    int rados_aio_wait_for_complete(rados_completion_t c)
+    int rados_aio_wait_for_safe(rados_completion_t c)
+    int rados_aio_is_complete(rados_completion_t c)
+    int rados_aio_is_safe(rados_completion_t c)
+
+    int rados_exec(rados_ioctx_t io, const char * oid, const char * cls, const char * method,
+                   const char * in_buf, size_t in_len, char * buf, size_t out_len)
+    int rados_aio_exec(rados_ioctx_t io, const char * oid, rados_completion_t completion, const char * cls, const char * method,
+                       const char * in_buf, size_t in_len, char * buf, size_t out_len)
+
+    int rados_write_op_operate(rados_write_op_t write_op, rados_ioctx_t io, const char * oid, time_t * mtime, int flags)
+    int rados_aio_write_op_operate(rados_write_op_t write_op, rados_ioctx_t io, rados_completion_t completion, const char *oid, time_t *mtime, int flags)
+    void rados_write_op_omap_set(rados_write_op_t write_op, const char * const* keys, const char * const* vals, const size_t * lens, size_t num)
+    void rados_write_op_omap_rm_keys(rados_write_op_t write_op, const char * const* keys, size_t keys_len)
+    void rados_write_op_omap_clear(rados_write_op_t write_op)
+    void rados_write_op_set_flags(rados_write_op_t write_op, int flags)
+
+    void rados_write_op_create(rados_write_op_t write_op, int exclusive, const char *category)
+    void rados_write_op_append(rados_write_op_t write_op, const char *buffer, size_t len)
+    void rados_write_op_write_full(rados_write_op_t write_op, const char *buffer, size_t len)
+    void rados_write_op_write(rados_write_op_t write_op, const char *buffer, size_t len, uint64_t offset)
+    void rados_write_op_remove(rados_write_op_t write_op)
+    void rados_write_op_truncate(rados_write_op_t write_op, uint64_t offset)
+    void rados_write_op_zero(rados_write_op_t write_op, uint64_t offset, uint64_t len)
+
+    void rados_read_op_omap_get_vals2(rados_read_op_t read_op, const char * start_after, const char * filter_prefix, uint64_t max_return, rados_omap_iter_t * iter, unsigned char *pmore, int * prval)
+    void rados_read_op_omap_get_keys2(rados_read_op_t read_op, const char * start_after, uint64_t max_return, rados_omap_iter_t * iter, unsigned char *pmore, int * prval)
+    void rados_read_op_omap_get_vals_by_keys(rados_read_op_t read_op, const char * const* keys, size_t keys_len, rados_omap_iter_t * iter, int * prval)
+    int rados_read_op_operate(rados_read_op_t read_op, rados_ioctx_t io, const char * oid, int flags)
+    int rados_aio_read_op_operate(rados_read_op_t read_op, rados_ioctx_t io, rados_completion_t completion, const char *oid, int flags)
+    void rados_read_op_set_flags(rados_read_op_t read_op, int flags)
+    int rados_omap_get_next(rados_omap_iter_t iter, const char * const* key, const char * const* val, size_t * len)
+    void rados_omap_get_end(rados_omap_iter_t iter)
+
+
+LIBRADOS_OP_FLAG_EXCL = _LIBRADOS_OP_FLAG_EXCL
+LIBRADOS_OP_FLAG_FAILOK = _LIBRADOS_OP_FLAG_FAILOK
+LIBRADOS_OP_FLAG_FADVISE_RANDOM = _LIBRADOS_OP_FLAG_FADVISE_RANDOM
+LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL = _LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL
+LIBRADOS_OP_FLAG_FADVISE_WILLNEED = _LIBRADOS_OP_FLAG_FADVISE_WILLNEED
+LIBRADOS_OP_FLAG_FADVISE_DONTNEED = _LIBRADOS_OP_FLAG_FADVISE_DONTNEED
+LIBRADOS_OP_FLAG_FADVISE_NOCACHE = _LIBRADOS_OP_FLAG_FADVISE_NOCACHE
+
+LIBRADOS_SNAP_HEAD = _LIBRADOS_SNAP_HEAD
+
+LIBRADOS_OPERATION_NOFLAG = _LIBRADOS_OPERATION_NOFLAG
+LIBRADOS_OPERATION_BALANCE_READS = _LIBRADOS_OPERATION_BALANCE_READS
+LIBRADOS_OPERATION_LOCALIZE_READS = _LIBRADOS_OPERATION_LOCALIZE_READS
+LIBRADOS_OPERATION_ORDER_READS_WRITES = _LIBRADOS_OPERATION_ORDER_READS_WRITES
+LIBRADOS_OPERATION_IGNORE_CACHE = _LIBRADOS_OPERATION_IGNORE_CACHE
+LIBRADOS_OPERATION_SKIPRWLOCKS = _LIBRADOS_OPERATION_SKIPRWLOCKS
+LIBRADOS_OPERATION_IGNORE_OVERLAY = _LIBRADOS_OPERATION_IGNORE_OVERLAY
+
+LIBRADOS_ALL_NSPACES = _LIBRADOS_ALL_NSPACES.decode('utf-8')
+
+LIBRADOS_CREATE_EXCLUSIVE = _LIBRADOS_CREATE_EXCLUSIVE
+LIBRADOS_CREATE_IDEMPOTENT = _LIBRADOS_CREATE_IDEMPOTENT
+
+ANONYMOUS_AUID = 0xffffffffffffffff
+ADMIN_AUID = 0
+
+
+class Error(Exception):
+    """ `Error` class, derived from `Exception` """
+    pass
+
+
+class InvalidArgumentError(Error):
+    pass
+
+
+class OSError(Error):
+    """ `OSError` class, derived from `Error` """
+    def __init__(self, message, errno=None):
+        super(OSError, self).__init__(message)
+        self.errno = errno
+
+    def __str__(self):
+        msg = super(OSError, self).__str__()
+        if self.errno is None:
+            return msg
+        return '[errno {0}] {1}'.format(self.errno, msg)
+
+    def __reduce__(self):
+        return (self.__class__, (self.message, self.errno))
+
+class InterruptedOrTimeoutError(OSError):
+    """ `InterruptedOrTimeoutError` class, derived from `OSError` """
+    pass
+
+
+class PermissionError(OSError):
+    """ `PermissionError` class, derived from `OSError` """
+    pass
+
+
+class PermissionDeniedError(OSError):
+    """ deal with EACCES related. """
+    pass
+
+
+class ObjectNotFound(OSError):
+    """ `ObjectNotFound` class, derived from `OSError` """
+    pass
+
+
+class NoData(OSError):
+    """ `NoData` class, derived from `OSError` """
+    pass
+
+
+class ObjectExists(OSError):
+    """ `ObjectExists` class, derived from `OSError` """
+    pass
+
+
+class ObjectBusy(OSError):
+    """ `ObjectBusy` class, derived from `IOError` """
+    pass
+
+
+class IOError(OSError):
+    """ `ObjectBusy` class, derived from `OSError` """
+    pass
+
+
+class NoSpace(OSError):
+    """ `NoSpace` class, derived from `OSError` """
+    pass
+
+
+class RadosStateError(Error):
+    """ `RadosStateError` class, derived from `Error` """
+    pass
+
+
+class IoctxStateError(Error):
+    """ `IoctxStateError` class, derived from `Error` """
+    pass
+
+
+class ObjectStateError(Error):
+    """ `ObjectStateError` class, derived from `Error` """
+    pass
+
+
+class LogicError(Error):
+    """ `` class, derived from `Error` """
+    pass
+
+
+class TimedOut(OSError):
+    """ `TimedOut` class, derived from `OSError` """
+    pass
+
+
+IF UNAME_SYSNAME == "FreeBSD":
+    cdef errno_to_exception = {
+        errno.EPERM     : PermissionError,
+        errno.ENOENT    : ObjectNotFound,
+        errno.EIO       : IOError,
+        errno.ENOSPC    : NoSpace,
+        errno.EEXIST    : ObjectExists,
+        errno.EBUSY     : ObjectBusy,
+        errno.ENOATTR   : NoData,
+        errno.EINTR     : InterruptedOrTimeoutError,
+        errno.ETIMEDOUT : TimedOut,
+        errno.EACCES    : PermissionDeniedError,
+        errno.EINVAL    : InvalidArgumentError,
+    }
+ELSE:
+    cdef errno_to_exception = {
+        errno.EPERM     : PermissionError,
+        errno.ENOENT    : ObjectNotFound,
+        errno.EIO       : IOError,
+        errno.ENOSPC    : NoSpace,
+        errno.EEXIST    : ObjectExists,
+        errno.EBUSY     : ObjectBusy,
+        errno.ENODATA   : NoData,
+        errno.EINTR     : InterruptedOrTimeoutError,
+        errno.ETIMEDOUT : TimedOut,
+        errno.EACCES    : PermissionDeniedError,
+        errno.EINVAL    : InvalidArgumentError,
+    }
+
+
+cdef make_ex(ret, msg):
+    """
+    Translate a librados 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, errno=ret)
+    else:
+        return OSError(msg, errno=ret)
+
+
+# helper to specify an optional argument, where in addition to `cls`, `None`
+# is also acceptable
+def opt(cls):
+    return (cls, None)
+
+
+# validate argument types of an instance method
+# kwargs is an un-ordered dict, so use args instead
+def requires(*types):
+    def is_type_of(v, t):
+        if t is None:
+            return v is None
+        else:
+            return isinstance(v, t)
+
+    def check_type(val, arg_name, arg_type):
+        if isinstance(arg_type, tuple):
+            if any(is_type_of(val, t) for t in arg_type):
+                return
+            type_names = ' or '.join('None' if t is None else t.__name__
+                                     for t in arg_type)
+            raise TypeError('%s must be %s' % (arg_name, type_names))
+        else:
+            if is_type_of(val, arg_type):
+                return
+            assert(arg_type is not None)
+            raise TypeError('%s must be %s' % (arg_name, arg_type.__name__))
+
+    def wrapper(f):
+        # FIXME(sileht): this stop with
+        # AttributeError: 'method_descriptor' object has no attribute '__module__'
+        # @wraps(f)
+        def validate_func(*args, **kwargs):
+            # ignore the `self` arg
+            pos_args = zip(args[1:], types)
+            named_args = ((kwargs[name], (name, spec)) for name, spec in types
+                          if name in kwargs)
+            for arg_val, (arg_name, arg_type) in chain(pos_args, named_args):
+                check_type(arg_val, arg_name, arg_type)
+            return f(*args, **kwargs)
+        return validate_func
+    return wrapper
+
+
+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 TypeError('%s must be a string' % name)
+
+
+def cstr_list(list_str, name, encoding="utf-8"):
+    return [cstr(s, name) for s in list_str]
+
+
+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
+
+
+cdef size_t * to_csize_t_array(list_int):
+    cdef size_t *ret = <size_t *>malloc(len(list_int) * sizeof(size_t))
+    if ret == NULL:
+        raise MemoryError("malloc failed")
+    for i in xrange(len(list_int)):
+        ret[i] = <size_t>list_int[i]
+    return ret
+
+
+cdef char ** to_bytes_array(list_bytes):
+    cdef char **ret = <char **>malloc(len(list_bytes) * sizeof(char *))
+    if ret == NULL:
+        raise MemoryError("malloc failed")
+    for i in xrange(len(list_bytes)):
+        ret[i] = <char *>list_bytes[i]
+    return ret
+
+
+
+cdef int __monitor_callback(void *arg, const char *line, const char *who,
+                             uint64_t sec, uint64_t nsec, uint64_t seq,
+                             const char *level, const char *msg) with gil:
+    cdef object cb_info = <object>arg
+    cb_info[0](cb_info[1], line, who, sec, nsec, seq, level, msg)
+    return 0
+
+cdef int __monitor_callback2(void *arg, const char *line, const char *channel,
+                             const char *who,
+                             const char *name,
+                             uint64_t sec, uint64_t nsec, uint64_t seq,
+                             const char *level, const char *msg) with gil:
+    cdef object cb_info = <object>arg
+    cb_info[0](cb_info[1], line, channel, name, who, sec, nsec, seq, level, msg)
+    return 0
+
+
+class Version(object):
+    """ Version information """
+    def __init__(self, major, minor, extra):
+        self.major = major
+        self.minor = minor
+        self.extra = extra
+
+    def __str__(self):
+        return "%d.%d.%d" % (self.major, self.minor, self.extra)
+
+
+cdef class Rados(object):
+    """This class wraps librados functions"""
+    # NOTE(sileht): attributes declared in .pyd
+
+    def __init__(self, *args, **kwargs):
+        PyEval_InitThreads()
+        self.__setup(*args, **kwargs)
+
+    @requires(('rados_id', opt(str_type)), ('name', opt(str_type)), ('clustername', opt(str_type)),
+              ('conffile', opt(str_type)))
+    def __setup(self, rados_id=None, name=None, clustername=None,
+                conf_defaults=None, conffile=None, conf=None, flags=0,
+                context=None):
+        self.monitor_callback = None
+        self.monitor_callback2 = None
+        self.parsed_args = []
+        self.conf_defaults = conf_defaults
+        self.conffile = conffile
+        self.rados_id = rados_id
+
+        if rados_id and name:
+            raise Error("Rados(): can't supply both rados_id and name")
+        elif rados_id:
+            name = 'client.' + rados_id
+        elif name is None:
+            name = 'client.admin'
+        if clustername is None:
+            clustername = ''
+
+        name = cstr(name, 'name')
+        clustername = cstr(clustername, 'clustername')
+        cdef:
+            char *_name = name
+            char *_clustername = clustername
+            int _flags = flags
+            int ret
+
+        if context:
+            # Unpack void* (aka rados_config_t) from capsule
+            rados_config = <rados_config_t> PyCapsule_GetPointer(context, NULL)
+            with nogil:
+                ret = rados_create_with_context(&self.cluster, rados_config)
+        else:
+            with nogil:
+                ret = rados_create2(&self.cluster, _clustername, _name, _flags)
+        if ret != 0:
+            raise Error("rados_initialize failed with error code: %d" % ret)
+
+        self.state = "configuring"
+        # order is important: conf_defaults, then conffile, then conf
+        if conf_defaults:
+            for key, value in conf_defaults.items():
+                self.conf_set(key, value)
+        if conffile is not None:
+            # read the default conf file when '' is given
+            if conffile == '':
+                conffile = None
+            self.conf_read_file(conffile)
+        if conf:
+            for key, value in conf.items():
+                self.conf_set(key, value)
+
+    def require_state(self, *args):
+        """
+        Checks if the Rados object is in a special state
+
+        :raises: RadosStateError
+        """
+        if self.state in args:
+            return
+        raise RadosStateError("You cannot perform that operation on a \
+Rados object in state %s." % self.state)
+
+    def shutdown(self):
+        """
+        Disconnects from the cluster.  Call this explicitly when a
+        Rados.connect()ed object is no longer used.
+        """
+        if self.state != "shutdown":
+            with nogil:
+                rados_shutdown(self.cluster)
+            self.state = "shutdown"
+
+    def __enter__(self):
+        self.connect()
+        return self
+
+    def __exit__(self, type_, value, traceback):
+        self.shutdown()
+        return False
+
+    def version(self):
+        """
+        Get the version number of the ``librados`` C library.
+
+        :returns: a tuple of ``(major, minor, extra)`` components of the
+                  librados version
+        """
+        cdef int major = 0
+        cdef int minor = 0
+        cdef int extra = 0
+        with nogil:
+            rados_version(&major, &minor, &extra)
+        return Version(major, minor, extra)
+
+    @requires(('path', opt(str_type)))
+    def conf_read_file(self, path=None):
+        """
+        Configure the cluster handle using a Ceph config file.
+
+        :param path: path to the config file
+        :type path: str
+        """
+        self.require_state("configuring", "connected")
+        path = cstr(path, 'path', opt=True)
+        cdef:
+            char *_path = opt_str(path)
+        with nogil:
+            ret = rados_conf_read_file(self.cluster, _path)
+        if ret != 0:
+            raise make_ex(ret, "error calling conf_read_file")
+
+    def conf_parse_argv(self, args):
+        """
+        Parse known arguments from args, and remove; returned
+        args contain only those unknown to ceph
+        """
+        self.require_state("configuring", "connected")
+        if not args:
+            return
+
+        cargs = cstr_list(args, 'args')
+        cdef:
+            int _argc = len(args)
+            char **_argv = to_bytes_array(cargs)
+            char **_remargv = NULL
+
+        try:
+            _remargv = <char **>malloc(_argc * sizeof(char *))
+            with nogil:
+                ret = rados_conf_parse_argv_remainder(self.cluster, _argc,
+                                                      <const char**>_argv,
+                                                      <const char**>_remargv)
+            if ret:
+                raise make_ex(ret, "error calling conf_parse_argv_remainder")
+
+            # _remargv was allocated with fixed argc; collapse return
+            # list to eliminate any missing args
+            retargs = [decode_cstr(a) for a in _remargv[:_argc]
+                       if a != NULL]
+            self.parsed_args = args
+            return retargs
+        finally:
+            free(_argv)
+            free(_remargv)
+
+    def conf_parse_env(self, var='CEPH_ARGS'):
+        """
+        Parse known arguments from an environment variable, normally
+        CEPH_ARGS.
+        """
+        self.require_state("configuring", "connected")
+        if not var:
+            return
+
+        var = cstr(var, 'var')
+        cdef:
+            char *_var = var
+        with nogil:
+            ret = rados_conf_parse_env(self.cluster, _var)
+        if ret != 0:
+            raise make_ex(ret, "error calling conf_parse_env")
+
+    @requires(('option', str_type))
+    def conf_get(self, option):
+        """
+        Get the value of a configuration option
+
+        :param option: which option to read
+        :type option: str
+
+        :returns: str - value of the option or None
+        :raises: :class:`TypeError`
+        """
+        self.require_state("configuring", "connected")
+        option = cstr(option, 'option')
+        cdef:
+            char *_option = option
+            size_t length = 20
+            char *ret_buf = NULL
+
+        try:
+            while True:
+                ret_buf = <char *>realloc_chk(ret_buf, length)
+                with nogil:
+                    ret = rados_conf_get(self.cluster, _option, ret_buf, length)
+                if ret == 0:
+                    return decode_cstr(ret_buf)
+                elif ret == -errno.ENAMETOOLONG:
+                    length = length * 2
+                elif ret == -errno.ENOENT:
+                    return None
+                else:
+                    raise make_ex(ret, "error calling conf_get")
+        finally:
+            free(ret_buf)
+
+    @requires(('option', str_type), ('val', str_type))
+    def conf_set(self, option, val):
+        """
+        Set the value of a configuration option
+
+        :param option: which option to set
+        :type option: str
+        :param option: value of the option
+        :type option: str
+
+        :raises: :class:`TypeError`, :class:`ObjectNotFound`
+        """
+        self.require_state("configuring", "connected")
+        option = cstr(option, 'option')
+        val = cstr(val, 'val')
+        cdef:
+            char *_option = option
+            char *_val = val
+
+        with nogil:
+            ret = rados_conf_set(self.cluster, _option, _val)
+        if ret != 0:
+            raise make_ex(ret, "error calling conf_set")
+
+    def ping_monitor(self, mon_id):
+        """
+        Ping a monitor to assess liveness
+
+        May be used as a simply way to assess liveness, or to obtain
+        information about the monitor in a simple way even in the
+        absence of quorum.
+
+        :param mon_id: the ID portion of the monitor's name (i.e., mon.<ID>)
+        :type mon_id: str
+        :returns: the string reply from the monitor
+        """
+
+        self.require_state("configuring", "connected")
+
+        mon_id = cstr(mon_id, 'mon_id')
+        cdef:
+            char *_mon_id = mon_id
+            size_t outstrlen = 0
+            char *outstr
+
+        with nogil:
+            ret = rados_ping_monitor(self.cluster, _mon_id, &outstr, &outstrlen)
+
+        if ret != 0:
+            raise make_ex(ret, "error calling ping_monitor")
+
+        if outstrlen:
+            my_outstr = outstr[:outstrlen]
+            rados_buffer_free(outstr)
+            return decode_cstr(my_outstr)
+
+    def connect(self, timeout=0):
+        """
+        Connect to the cluster.  Use shutdown() to release resources.
+        """
+        self.require_state("configuring")
+        # NOTE(sileht): timeout was supported by old python API,
+        # but this is not something available in C API, so ignore
+        # for now and remove it later
+        with nogil:
+            ret = rados_connect(self.cluster)
+        if ret != 0:
+            raise make_ex(ret, "error connecting to the cluster")
+        self.state = "connected"
+
+    def get_cluster_stats(self):
+        """
+        Read usage info about the cluster
+
+        This tells you total space, space used, space available, and number
+        of objects. These are not updated immediately when data is written,
+        they are eventually consistent.
+
+        :returns: dict - contains the following keys:
+
+            - ``kb`` (int) - total space
+
+            - ``kb_used`` (int) - space used
+
+            - ``kb_avail`` (int) - free space available
+
+            - ``num_objects`` (int) - number of objects
+
+        """
+        cdef:
+            rados_cluster_stat_t stats
+
+        with nogil:
+            ret = rados_cluster_stat(self.cluster, &stats)
+
+        if ret < 0:
+            raise make_ex(
+                ret, "Rados.get_cluster_stats(%s): get_stats failed" % self.rados_id)
+        return {'kb': stats.kb,
+                'kb_used': stats.kb_used,
+                'kb_avail': stats.kb_avail,
+                'num_objects': stats.num_objects}
+
+    @requires(('pool_name', str_type))
+    def pool_exists(self, pool_name):
+        """
+        Checks if a given pool exists.
+
+        :param pool_name: name of the pool to check
+        :type pool_name: str
+
+        :raises: :class:`TypeError`, :class:`Error`
+        :returns: bool - whether the pool exists, false otherwise.
+        """
+        self.require_state("connected")
+
+        pool_name = cstr(pool_name, 'pool_name')
+        cdef:
+            char *_pool_name = pool_name
+
+        with nogil:
+            ret = rados_pool_lookup(self.cluster, _pool_name)
+        if ret >= 0:
+            return True
+        elif ret == -errno.ENOENT:
+            return False
+        else:
+            raise make_ex(ret, "error looking up pool '%s'" % pool_name)
+
+    @requires(('pool_name', str_type))
+    def pool_lookup(self, pool_name):
+        """
+        Returns a pool's ID based on its name.
+
+        :param pool_name: name of the pool to look up
+        :type pool_name: str
+
+        :raises: :class:`TypeError`, :class:`Error`
+        :returns: int - pool ID, or None if it doesn't exist
+        """
+        self.require_state("connected")
+        pool_name = cstr(pool_name, 'pool_name')
+        cdef:
+            char *_pool_name = pool_name
+
+        with nogil:
+            ret = rados_pool_lookup(self.cluster, _pool_name)
+        if ret >= 0:
+            return int(ret)
+        elif ret == -errno.ENOENT:
+            return None
+        else:
+            raise make_ex(ret, "error looking up pool '%s'" % pool_name)
+
+    @requires(('pool_id', int))
+    def pool_reverse_lookup(self, pool_id):
+        """
+        Returns a pool's name based on its ID.
+
+        :param pool_id: ID of the pool to look up
+        :type pool_id: int
+
+        :raises: :class:`TypeError`, :class:`Error`
+        :returns: string - pool name, or None if it doesn't exist
+        """
+        self.require_state("connected")
+        cdef:
+            int64_t _pool_id = pool_id
+            size_t size = 512
+            char *name = NULL
+
+        try:
+            while True:
+                name = <char *>realloc_chk(name, size)
+                with nogil:
+                    ret = rados_pool_reverse_lookup(self.cluster, _pool_id, name, size)
+                if ret >= 0:
+                    break
+                elif ret != -errno.ERANGE and size <= 4096:
+                    size *= 2
+                elif ret == -errno.ENOENT:
+                    return None
+                elif ret < 0:
+                    raise make_ex(ret, "error reverse looking up pool '%s'" % pool_id)
+
+            return decode_cstr(name)
+
+        finally:
+            free(name)
+
+    @requires(('pool_name', str_type), ('auid', opt(int)), ('crush_rule', opt(int)))
+    def create_pool(self, pool_name, auid=None, crush_rule=None):
+        """
+        Create a pool:
+        - with default settings: if auid=None and crush_rule=None
+        - owned by a specific auid: auid given and crush_rule=None
+        - with a specific CRUSH rule: if auid=None and crush_rule given
+        - with a specific CRUSH rule and auid: if auid and crush_rule given
+
+        :param pool_name: name of the pool to create
+        :type pool_name: str
+        :param auid: the id of the owner of the new pool
+        :type auid: int
+        :param crush_rule: rule to use for placement in the new pool
+        :type crush_rule: int
+
+        :raises: :class:`TypeError`, :class:`Error`
+        """
+        self.require_state("connected")
+
+        pool_name = cstr(pool_name, 'pool_name')
+        cdef:
+            char *_pool_name = pool_name
+            uint8_t _crush_rule
+            uint64_t _auid
+
+        if auid is None and crush_rule is None:
+            with nogil:
+                ret = rados_pool_create(self.cluster, _pool_name)
+        elif auid is None:
+            _crush_rule = crush_rule
+            with nogil:
+                ret = rados_pool_create_with_crush_rule(self.cluster, _pool_name, _crush_rule)
+        elif crush_rule is None:
+            _auid = auid
+            with nogil:
+                ret = rados_pool_create_with_auid(self.cluster, _pool_name, _auid)
+        else:
+            _auid = auid
+            _crush_rule = crush_rule
+            with nogil:
+                ret = rados_pool_create_with_all(self.cluster, _pool_name, _auid, _crush_rule)
+        if ret < 0:
+            raise make_ex(ret, "error creating pool '%s'" % pool_name)
+
+    @requires(('pool_id', int))
+    def get_pool_base_tier(self, pool_id):
+        """
+        Get base pool
+
+        :returns: base pool, or pool_id if tiering is not configured for the pool
+        """
+        self.require_state("connected")
+        cdef:
+            int64_t base_tier = 0
+            int64_t _pool_id = pool_id
+
+        with nogil:
+            ret = rados_pool_get_base_tier(self.cluster, _pool_id, &base_tier)
+        if ret < 0:
+            raise make_ex(ret, "get_pool_base_tier(%d)" % pool_id)
+        return int(base_tier)
+
+    @requires(('pool_name', str_type))
+    def delete_pool(self, pool_name):
+        """
+        Delete a pool and all data inside it.
+
+        The pool is removed from the cluster immediately,
+        but the actual data is deleted in the background.
+
+        :param pool_name: name of the pool to delete
+        :type pool_name: str
+
+        :raises: :class:`TypeError`, :class:`Error`
+        """
+        self.require_state("connected")
+
+        pool_name = cstr(pool_name, 'pool_name')
+        cdef:
+            char *_pool_name = pool_name
+
+        with nogil:
+            ret = rados_pool_delete(self.cluster, _pool_name)
+        if ret < 0:
+            raise make_ex(ret, "error deleting pool '%s'" % pool_name)
+
+    @requires(('pool_id', int))
+    def get_inconsistent_pgs(self, pool_id):
+        """
+        List inconsistent placement groups in the given pool
+
+        :param pool_id: ID of the pool in which PGs are listed
+        :type pool_id: int
+        :returns: list - inconsistent placement groups
+        """
+        self.require_state("connected")
+        cdef:
+            int64_t pool = pool_id
+            size_t size = 512
+            char *pgs = NULL
+
+        try:
+            while True:
+                pgs = <char *>realloc_chk(pgs, size);
+                with nogil:
+                    ret = rados_inconsistent_pg_list(self.cluster, pool,
+                                                     pgs, size)
+                if ret > <int>size:
+                    size *= 2
+                elif ret >= 0:
+                    break
+                else:
+                    raise make_ex(ret, "error calling inconsistent_pg_list")
+            return [pg for pg in decode_cstr(pgs[:ret]).split('\0') if pg]
+        finally:
+            free(pgs)
+
+    def list_pools(self):
+        """
+        Gets a list of pool names.
+
+        :returns: list - of pool names.
+        """
+        self.require_state("connected")
+        cdef:
+            size_t size = 512
+            char *c_names = NULL
+
+        try:
+            while True:
+                c_names = <char *>realloc_chk(c_names, size)
+                with nogil:
+                    ret = rados_pool_list(self.cluster, c_names, size)
+                if ret > <int>size:
+                    size *= 2
+                elif ret >= 0:
+                    break
+            return [name for name in decode_cstr(c_names[:ret]).split('\0')
+                    if name]
+        finally:
+            free(c_names)
+
+    def get_fsid(self):
+        """
+        Get the fsid of the cluster as a hexadecimal string.
+
+        :raises: :class:`Error`
+        :returns: str - cluster fsid
+        """
+        self.require_state("connected")
+        cdef:
+            char *ret_buf
+            size_t buf_len = 37
+            PyObject* ret_s = NULL
+
+        ret_s = PyBytes_FromStringAndSize(NULL, buf_len)
+        try:
+            ret_buf = PyBytes_AsString(ret_s)
+            with nogil:
+                ret = rados_cluster_fsid(self.cluster, ret_buf, buf_len)
+            if ret < 0:
+                raise make_ex(ret, "error getting cluster fsid")
+            if ret != <int>buf_len:
+                _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)
+
+    @requires(('ioctx_name', str_type))
+    def open_ioctx(self, ioctx_name):
+        """
+        Create an io context
+
+        The io context allows you to perform operations within a particular
+        pool.
+
+        :param ioctx_name: name of the pool
+        :type ioctx_name: str
+
+        :raises: :class:`TypeError`, :class:`Error`
+        :returns: Ioctx - Rados Ioctx object
+        """
+        self.require_state("connected")
+        ioctx_name = cstr(ioctx_name, 'ioctx_name')
+        cdef:
+            rados_ioctx_t ioctx
+            char *_ioctx_name = ioctx_name
+        with nogil:
+            ret = rados_ioctx_create(self.cluster, _ioctx_name, &ioctx)
+        if ret < 0:
+            raise make_ex(ret, "error opening pool '%s'" % ioctx_name)
+        io = Ioctx(ioctx_name)
+        io.io = ioctx
+        return io
+
+    def mon_command(self, cmd, inbuf, timeout=0, target=None):
+        """
+        mon_command[_target](cmd, inbuf, outbuf, outbuflen, outs, outslen)
+        returns (int ret, string outbuf, string outs)
+        """
+        # NOTE(sileht): timeout is ignored because C API doesn't provide
+        # timeout argument, but we keep it for backward compat with old python binding
+
+        self.require_state("connected")
+        cmd = cstr_list(cmd, 'c')
+
+        if isinstance(target, int):
+        # NOTE(sileht): looks weird but test_monmap_dump pass int
+            target = str(target)
+
+        target = cstr(target, 'target', opt=True)
+        inbuf = cstr(inbuf, 'inbuf')
+
+        cdef:
+            char *_target = opt_str(target)
+            char **_cmd = to_bytes_array(cmd)
+            size_t _cmdlen = len(cmd)
+
+            char *_inbuf = inbuf
+            size_t _inbuf_len = len(inbuf)
+
+            char *_outbuf
+            size_t _outbuf_len
+            char *_outs
+            size_t _outs_len
+
+        try:
+            if target:
+                with nogil:
+                    ret = rados_mon_command_target(self.cluster, _target,
+                                                <const char **>_cmd, _cmdlen,
+                                                <const char*>_inbuf, _inbuf_len,
+                                                &_outbuf, &_outbuf_len,
+                                                &_outs, &_outs_len)
+            else:
+                with nogil:
+                    ret = rados_mon_command(self.cluster,
+                                            <const char **>_cmd, _cmdlen,
+                                            <const char*>_inbuf, _inbuf_len,
+                                            &_outbuf, &_outbuf_len,
+                                            &_outs, &_outs_len)
+
+            my_outs = decode_cstr(_outs[:_outs_len])
+            my_outbuf = _outbuf[:_outbuf_len]
+            if _outs_len:
+                rados_buffer_free(_outs)
+            if _outbuf_len:
+                rados_buffer_free(_outbuf)
+            return (ret, my_outbuf, my_outs)
+        finally:
+            free(_cmd)
+
+    def osd_command(self, osdid, cmd, inbuf, timeout=0):
+        """
+        osd_command(osdid, cmd, inbuf, outbuf, outbuflen, outs, outslen)
+        returns (int ret, string outbuf, string outs)
+        """
+        # NOTE(sileht): timeout is ignored because C API doesn't provide
+        # timeout argument, but we keep it for backward compat with old python binding
+        self.require_state("connected")
+
+        cmd = cstr_list(cmd, 'cmd')
+        inbuf = cstr(inbuf, 'inbuf')
+
+        cdef:
+            int _osdid = osdid
+            char **_cmd = to_bytes_array(cmd)
+            size_t _cmdlen = len(cmd)
+
+            char *_inbuf = inbuf
+            size_t _inbuf_len = len(inbuf)
+
+            char *_outbuf
+            size_t _outbuf_len
+            char *_outs
+            size_t _outs_len
+
+        try:
+            with nogil:
+                ret = rados_osd_command(self.cluster, _osdid,
+                                        <const char **>_cmd, _cmdlen,
+                                        <const char*>_inbuf, _inbuf_len,
+                                        &_outbuf, &_outbuf_len,
+                                        &_outs, &_outs_len)
+
+            my_outs = decode_cstr(_outs[:_outs_len])
+            my_outbuf = _outbuf[:_outbuf_len]
+            if _outs_len:
+                rados_buffer_free(_outs)
+            if _outbuf_len:
+                rados_buffer_free(_outbuf)
+            return (ret, my_outbuf, my_outs)
+        finally:
+            free(_cmd)
+
+    def mgr_command(self, cmd, inbuf, timeout=0):
+        """
+        returns (int ret, string outbuf, string outs)
+        """
+        # NOTE(sileht): timeout is ignored because C API doesn't provide
+        # timeout argument, but we keep it for backward compat with old python binding
+        self.require_state("connected")
+
+        cmd = cstr_list(cmd, 'cmd')
+        inbuf = cstr(inbuf, 'inbuf')
+
+        cdef:
+            char **_cmd = to_bytes_array(cmd)
+            size_t _cmdlen = len(cmd)
+
+            char *_inbuf = inbuf
+            size_t _inbuf_len = len(inbuf)
+
+            char *_outbuf
+            size_t _outbuf_len
+            char *_outs
+            size_t _outs_len
+
+        try:
+            with nogil:
+                ret = rados_mgr_command(self.cluster,
+                                        <const char **>_cmd, _cmdlen,
+                                        <const char*>_inbuf, _inbuf_len,
+                                        &_outbuf, &_outbuf_len,
+                                        &_outs, &_outs_len)
+
+            my_outs = decode_cstr(_outs[:_outs_len])
+            my_outbuf = _outbuf[:_outbuf_len]
+            if _outs_len:
+                rados_buffer_free(_outs)
+            if _outbuf_len:
+                rados_buffer_free(_outbuf)
+            return (ret, my_outbuf, my_outs)
+        finally:
+            free(_cmd)
+
+    def pg_command(self, pgid, cmd, inbuf, timeout=0):
+        """
+        pg_command(pgid, cmd, inbuf, outbuf, outbuflen, outs, outslen)
+        returns (int ret, string outbuf, string outs)
+        """
+        # NOTE(sileht): timeout is ignored because C API doesn't provide
+        # timeout argument, but we keep it for backward compat with old python binding
+        self.require_state("connected")
+
+        pgid = cstr(pgid, 'pgid')
+        cmd = cstr_list(cmd, 'cmd')
+        inbuf = cstr(inbuf, 'inbuf')
+
+        cdef:
+            char *_pgid = pgid
+            char **_cmd = to_bytes_array(cmd)
+            size_t _cmdlen = len(cmd)
+
+            char *_inbuf = inbuf
+            size_t _inbuf_len = len(inbuf)
+
+            char *_outbuf
+            size_t _outbuf_len
+            char *_outs
+            size_t _outs_len
+
+        try:
+            with nogil:
+                ret = rados_pg_command(self.cluster, _pgid,
+                                       <const char **>_cmd, _cmdlen,
+                                       <const char *>_inbuf, _inbuf_len,
+                                       &_outbuf, &_outbuf_len,
+                                       &_outs, &_outs_len)
+
+            my_outs = decode_cstr(_outs[:_outs_len])
+            my_outbuf = _outbuf[:_outbuf_len]
+            if _outs_len:
+                rados_buffer_free(_outs)
+            if _outbuf_len:
+                rados_buffer_free(_outbuf)
+            return (ret, my_outbuf, my_outs)
+        finally:
+            free(_cmd)
+
+    def wait_for_latest_osdmap(self):
+        self.require_state("connected")
+        with nogil:
+            ret = rados_wait_for_latest_osdmap(self.cluster)
+        return ret
+
+    def blacklist_add(self, client_address, expire_seconds=0):
+        """
+        Blacklist a client from the OSDs
+
+        :param client_address: client address
+        :type client_address: str
+        :param expire_seconds: number of seconds to blacklist
+        :type expire_seconds: int
+
+        :raises: :class:`Error`
+        """
+        self.require_state("connected")
+        client_address =  cstr(client_address, 'client_address')
+        cdef:
+            uint32_t _expire_seconds = expire_seconds
+            char *_client_address = client_address
+
+        with nogil:
+            ret = rados_blacklist_add(self.cluster, _client_address, _expire_seconds)
+        if ret < 0:
+            raise make_ex(ret, "error blacklisting client '%s'" % client_address)
+
+    def monitor_log(self, level, callback, arg):
+        if level not in MONITOR_LEVELS:
+            raise LogicError("invalid monitor level " + level)
+        if callback is not None and not callable(callback):
+            raise LogicError("callback must be a callable function or None")
+
+        level = cstr(level, 'level')
+        cdef char *_level = level
+
+        if callback is None:
+            with nogil:
+                r = rados_monitor_log(self.cluster, <const char*>_level, NULL, NULL)
+            self.monitor_callback = None
+            self.monitor_callback2 = None
+            return
+
+        cb = (callback, arg)
+        cdef PyObject* _arg = <PyObject*>cb
+        with nogil:
+            r = rados_monitor_log(self.cluster, <const char*>_level,
+                                  <rados_log_callback_t>&__monitor_callback, _arg)
+
+        if r:
+            raise make_ex(r, 'error calling rados_monitor_log')
+        # NOTE(sileht): Prevents the callback method from being garbage collected
+        self.monitor_callback = cb
+        self.monitor_callback2 = None
+
+    def monitor_log2(self, level, callback, arg):
+        if level not in MONITOR_LEVELS:
+            raise LogicError("invalid monitor level " + level)
+        if callback is not None and not callable(callback):
+            raise LogicError("callback must be a callable function or None")
+
+        level = cstr(level, 'level')
+        cdef char *_level = level
+
+        if callback is None:
+            with nogil:
+                r = rados_monitor_log2(self.cluster, <const char*>_level, NULL, NULL)
+            self.monitor_callback = None
+            self.monitor_callback2 = None
+            return
+
+        cb = (callback, arg)
+        cdef PyObject* _arg = <PyObject*>cb
+        with nogil:
+            r = rados_monitor_log2(self.cluster, <const char*>_level,
+                                  <rados_log_callback2_t>&__monitor_callback2, _arg)
+
+        if r:
+            raise make_ex(r, 'error calling rados_monitor_log')
+        # NOTE(sileht): Prevents the callback method from being garbage collected
+        self.monitor_callback = None
+        self.monitor_callback2 = cb
+
+
+cdef class OmapIterator(object):
+    """Omap iterator"""
+
+    cdef public Ioctx ioctx
+    cdef rados_omap_iter_t ctx
+
+    def __cinit__(self, Ioctx ioctx):
+        self.ioctx = ioctx
+
+    def __iter__(self):
+        return self
+
+    def __next__(self):
+        """
+        Get the next key-value pair in the object
+        :returns: next rados.OmapItem
+        """
+        cdef:
+            char *key_ = NULL
+            char *val_ = NULL
+            size_t len_
+
+        with nogil:
+            ret = rados_omap_get_next(self.ctx, &key_, &val_, &len_)
+
+        if ret != 0:
+            raise make_ex(ret, "error iterating over the omap")
+        if key_ == NULL:
+            raise StopIteration()
+        key = decode_cstr(key_)
+        val = None
+        if val_ != NULL:
+            val = val_[:len_]
+        return (key, val)
+
+    def __dealloc__(self):
+        with nogil:
+            rados_omap_get_end(self.ctx)
+
+
+cdef class ObjectIterator(object):
+    """rados.Ioctx Object iterator"""
+
+    cdef rados_list_ctx_t ctx
+
+    cdef public object ioctx
+
+    def __cinit__(self, Ioctx ioctx):
+        self.ioctx = ioctx
+
+        with nogil:
+            ret = rados_nobjects_list_open(ioctx.io, &self.ctx)
+        if ret < 0:
+            raise make_ex(ret, "error iterating over the objects in ioctx '%s'"
+                          % self.ioctx.name)
+
+    def __iter__(self):
+        return self
+
+    def __next__(self):
+        """
+        Get the next object name and locator in the pool
+
+        :raises: StopIteration
+        :returns: next rados.Ioctx Object
+        """
+        cdef:
+            const char *key_ = NULL
+            const char *locator_ = NULL
+            const char *nspace_ = NULL
+
+        with nogil:
+            ret = rados_nobjects_list_next(self.ctx, &key_, &locator_, &nspace_)
+
+        if ret < 0:
+            raise StopIteration()
+
+        key = decode_cstr(key_)
+        locator = decode_cstr(locator_) if locator_ != NULL else None
+        nspace = decode_cstr(nspace_) if nspace_ != NULL else None
+        return Object(self.ioctx, key, locator, nspace)
+
+    def __dealloc__(self):
+        with nogil:
+            rados_nobjects_list_close(self.ctx)
+
+
+cdef class XattrIterator(object):
+    """Extended attribute iterator"""
+
+    cdef rados_xattrs_iter_t it
+    cdef char* _oid
+
+    cdef public Ioctx ioctx
+    cdef public object oid
+
+    def __cinit__(self, Ioctx ioctx, oid):
+        self.ioctx = ioctx
+        self.oid = cstr(oid, 'oid')
+        self._oid = self.oid
+
+        with nogil:
+            ret = rados_getxattrs(ioctx.io,  self._oid, &self.it)
+        if ret != 0:
+            raise make_ex(ret, "Failed to get rados xattrs for object %r" % oid)
+
+    def __iter__(self):
+        return self
+
+    def __next__(self):
+        """
+        Get the next xattr on the object
+
+        :raises: StopIteration
+        :returns: pair - of name and value of the next Xattr
+        """
+        cdef:
+            const char *name_ = NULL
+            const char *val_ = NULL
+            size_t len_ = 0
+
+        with nogil:
+            ret = rados_getxattrs_next(self.it, &name_, &val_, &len_)
+        if ret != 0:
+            raise make_ex(ret, "error iterating over the extended attributes \
+in '%s'" % self.oid)
+        if name_ == NULL:
+            raise StopIteration()
+        name = decode_cstr(name_)
+        val = val_[:len_]
+        return (name, val)
+
+    def __dealloc__(self):
+        with nogil:
+            rados_getxattrs_end(self.it)
+
+
+cdef class SnapIterator(object):
+    """Snapshot iterator"""
+
+    cdef public Ioctx ioctx
+
+    cdef rados_snap_t *snaps
+    cdef int max_snap
+    cdef int cur_snap
+
+    def __cinit__(self, Ioctx ioctx):
+        self.ioctx = ioctx
+        # We don't know how big a buffer we need until we've called the
+        # function. So use the exponential doubling strategy.
+        cdef int num_snaps = 10
+        while True:
+            self.snaps = <rados_snap_t*>realloc_chk(self.snaps,
+                                                    num_snaps *
+                                                    sizeof(rados_snap_t))
+
+            with nogil:
+                ret = rados_ioctx_snap_list(ioctx.io, self.snaps, num_snaps)
+            if ret >= 0:
+                self.max_snap = ret
+                break
+            elif ret != -errno.ERANGE:
+                raise make_ex(ret, "error calling rados_snap_list for \
+ioctx '%s'" % self.ioctx.name)
+            num_snaps = num_snaps * 2
+        self.cur_snap = 0
+
+    def __iter__(self):
+        return self
+
+    def __next__(self):
+        """
+        Get the next Snapshot
+
+        :raises: :class:`Error`, StopIteration
+        :returns: Snap - next snapshot
+        """
+        if self.cur_snap >= self.max_snap:
+            raise StopIteration
+
+        cdef:
+            rados_snap_t snap_id = self.snaps[self.cur_snap]
+            int name_len = 10
+            char *name = NULL
+
+        try:
+            while True:
+                name = <char *>realloc_chk(name, name_len)
+                with nogil:
+                    ret = rados_ioctx_snap_get_name(self.ioctx.io, snap_id, name, name_len)
+                if ret == 0:
+                    break
+                elif ret != -errno.ERANGE:
+                    raise make_ex(ret, "rados_snap_get_name error")
+                else:
+                    name_len = name_len * 2
+
+            snap = Snap(self.ioctx, decode_cstr(name[:name_len]).rstrip('\0'), snap_id)
+            self.cur_snap = self.cur_snap + 1
+            return snap
+        finally:
+            free(name)
+
+
+cdef class Snap(object):
+    """Snapshot object"""
+    cdef public Ioctx ioctx
+    cdef public object name
+
+    # NOTE(sileht): old API was storing the ctypes object
+    # instead of the value ....
+    cdef public rados_snap_t snap_id
+
+    def __cinit__(self, Ioctx ioctx, object name, rados_snap_t snap_id):
+        self.ioctx = ioctx
+        self.name = name
+        self.snap_id = snap_id
+
+    def __str__(self):
+        return "rados.Snap(ioctx=%s,name=%s,snap_id=%d)" \
+            % (str(self.ioctx), self.name, self.snap_id)
+
+    def get_timestamp(self):
+        """
+        Find when a snapshot in the current pool occurred
+
+        :raises: :class:`Error`
+        :returns: datetime - the data and time the snapshot was created
+        """
+        cdef time_t snap_time
+
+        with nogil:
+            ret = rados_ioctx_snap_get_stamp(self.ioctx.io, self.snap_id, &snap_time)
+        if ret != 0:
+            raise make_ex(ret, "rados_ioctx_snap_get_stamp error")
+        return datetime.fromtimestamp(snap_time)
+
+
+cdef class Completion(object):
+    """completion object"""
+
+    cdef public:
+         Ioctx ioctx
+         object oncomplete
+         object onsafe
+
+    cdef:
+         rados_callback_t complete_cb
+         rados_callback_t safe_cb
+         rados_completion_t rados_comp
+         PyObject* buf
+
+    def __cinit__(self, Ioctx ioctx, object oncomplete, object onsafe):
+        self.oncomplete = oncomplete
+        self.onsafe = onsafe
+        self.ioctx = ioctx
+
+    def is_safe(self):
+        """
+        Is an asynchronous operation safe?
+
+        This does not imply that the safe callback has finished.
+
+        :returns: True if the operation is safe
+        """
+        with nogil:
+            ret = rados_aio_is_safe(self.rados_comp)
+        return ret == 1
+
+    def is_complete(self):
+        """
+        Has an asynchronous operation completed?
+
+        This does not imply that the safe callback has finished.
+
+        :returns: True if the operation is completed
+        """
+        with nogil:
+            ret = rados_aio_is_complete(self.rados_comp)
+        return ret == 1
+
+    def wait_for_safe(self):
+        """
+        Wait for an asynchronous operation to be marked safe
+
+        This does not imply that the safe callback has finished.
+        """
+        with nogil:
+            rados_aio_wait_for_safe(self.rados_comp)
+
+    def wait_for_complete(self):
+        """
+        Wait for an asynchronous operation to complete
+
+        This does not imply that the complete callback has finished.
+        """
+        with nogil:
+            rados_aio_wait_for_complete(self.rados_comp)
+
+    def wait_for_safe_and_cb(self):
+        """
+        Wait for an asynchronous operation to be marked safe and for
+        the safe callback to have returned
+        """
+        with nogil:
+            rados_aio_wait_for_safe_and_cb(self.rados_comp)
+
+    def wait_for_complete_and_cb(self):
+        """
+        Wait for an asynchronous operation to complete and for the
+        complete callback to have returned
+
+        :returns:  whether the operation is completed
+        """
+        with nogil:
+            ret = rados_aio_wait_for_complete_and_cb(self.rados_comp)
+        return ret
+
+    def get_return_value(self):
+        """
+        Get the return value of an asychronous operation
+
+        The return value is set when the operation is complete or safe,
+        whichever comes first.
+
+        :returns: int - return value of the operation
+        """
+        with nogil:
+            ret = rados_aio_get_return_value(self.rados_comp)
+        return ret
+
+    def __dealloc__(self):
+        """
+        Release a completion
+
+        Call this when you no longer need the completion. It may not be
+        freed immediately if the operation is not acked and committed.
+        """
+        ref.Py_XDECREF(self.buf)
+        self.buf = NULL
+        if self.rados_comp != NULL:
+            with nogil:
+                rados_aio_release(self.rados_comp)
+                self.rados_comp = NULL
+
+    def _complete(self):
+        self.oncomplete(self)
+        with self.ioctx.lock:
+            if self.oncomplete:
+                self.ioctx.complete_completions.remove(self)
+
+    def _safe(self):
+        self.onsafe(self)
+        with self.ioctx.lock:
+            if self.onsafe:
+                self.ioctx.safe_completions.remove(self)
+
+    def _cleanup(self):
+        with self.ioctx.lock:
+            if self.oncomplete:
+                self.ioctx.complete_completions.remove(self)
+            if self.onsafe:
+                self.ioctx.safe_completions.remove(self)
+
+
+class OpCtx(object):
+    def __enter__(self):
+        return self.create()
+
+    def __exit__(self, type, msg, traceback):
+        self.release()
+
+
+cdef class WriteOp(object):
+    cdef rados_write_op_t write_op
+
+    def create(self):
+        with nogil:
+            self.write_op = rados_create_write_op()
+        return self
+
+    def release(self):
+        with nogil:
+            rados_release_write_op(self.write_op)
+
+    @requires(('exclusive', opt(int)))
+    def new(self, exclusive=None):
+        """
+        Create the object.
+        """
+
+        cdef:
+            int _exclusive = exclusive
+
+        with nogil:
+            rados_write_op_create(self.write_op, _exclusive, NULL)
+
+
+    def remove(self):
+        """
+        Remove object.
+        """
+        with nogil:
+            rados_write_op_remove(self.write_op)
+
+    @requires(('flags', int))
+    def set_flags(self, flags=LIBRADOS_OPERATION_NOFLAG):
+        """
+        Set flags for the last operation added to this write_op.
+        :para flags: flags to apply to the last operation
+        :type flags: int
+        """
+
+        cdef:
+            int _flags = flags
+
+        with nogil:
+            rados_write_op_set_flags(self.write_op, _flags)
+
+    @requires(('to_write', bytes))
+    def append(self, to_write):
+        """
+        Append data to an object synchronously
+        :param to_write: data to write
+        :type to_write: bytes
+        """
+
+        cdef:
+            char *_to_write = to_write
+            size_t length = len(to_write)
+
+        with nogil:
+            rados_write_op_append(self.write_op, _to_write, length)
+
+    @requires(('to_write', bytes))
+    def write_full(self, to_write):
+        """
+        Write whole object, atomically replacing it.
+        :param to_write: data to write
+        :type to_write: bytes
+        """
+
+        cdef:
+            char *_to_write = to_write
+            size_t length = len(to_write)
+
+        with nogil:
+            rados_write_op_write_full(self.write_op, _to_write, length)
+
+    @requires(('to_write', bytes), ('offset', int))
+    def write(self, to_write, offset=0):
+        """
+        Write to offset.
+        :param to_write: data to write
+        :type to_write: bytes
+        :param offset: byte offset in the object to begin writing at
+        :type offset: int
+        """
+
+        cdef:
+            char *_to_write = to_write
+            size_t length = len(to_write)
+            uint64_t _offset = offset
+
+        with nogil:
+            rados_write_op_write(self.write_op, _to_write, length, _offset)
+
+    @requires(('offset', int), ('length', int))
+    def zero(self, offset, length):
+        """
+        Zero part of an object.
+        :param offset: byte offset in the object to begin writing at
+        :type offset: int
+        :param offset: number of zero to write
+        :type offset: int
+        """
+
+        cdef:
+            size_t _length = length
+            uint64_t _offset = offset
+
+        with nogil:
+            rados_write_op_zero(self.write_op, _length, _offset)
+
+    @requires(('offset', int))
+    def truncate(self, offset):
+        """
+        Truncate an object.
+        :param offset: byte offset in the object to begin truncating at
+        :type offset: int
+        """
+
+        cdef:
+            uint64_t _offset = offset
+
+        with nogil:
+            rados_write_op_truncate(self.write_op,  _offset)
+
+
+class WriteOpCtx(WriteOp, OpCtx):
+    """write operation context manager"""
+
+
+cdef class ReadOp(object):
+    cdef rados_read_op_t read_op
+
+    def create(self):
+        with nogil:
+            self.read_op = rados_create_read_op()
+        return self
+
+    def release(self):
+        with nogil:
+            rados_release_read_op(self.read_op)
+
+    @requires(('flags', int))
+    def set_flags(self, flags=LIBRADOS_OPERATION_NOFLAG):
+        """
+        Set flags for the last operation added to this read_op.
+        :para flags: flags to apply to the last operation
+        :type flags: int
+        """
+
+        cdef:
+            int _flags = flags
+
+        with nogil:
+            rados_read_op_set_flags(self.read_op, _flags)
+
+
+class ReadOpCtx(ReadOp, OpCtx):
+    """read operation context manager"""
+
+
+cdef int __aio_safe_cb(rados_completion_t completion, void *args) with gil:
+    """
+    Callback to onsafe() for asynchronous operations
+    """
+    cdef object cb = <object>args
+    cb._safe()
+    return 0
+
+
+cdef int __aio_complete_cb(rados_completion_t completion, void *args) with gil:
+    """
+    Callback to oncomplete() for asynchronous operations
+    """
+    cdef object cb = <object>args
+    cb._complete()
+    return 0
+
+
+cdef class Ioctx(object):
+    """rados.Ioctx object"""
+    # NOTE(sileht): attributes declared in .pyd
+
+    def __init__(self, name):
+        self.name = name
+        self.state = "open"
+
+        self.locator_key = ""
+        self.nspace = ""
+        self.lock = threading.Lock()
+        self.safe_completions = []
+        self.complete_completions = []
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, type_, value, traceback):
+        self.close()
+        return False
+
+    def __dealloc__(self):
+        self.close()
+
+    def __track_completion(self, completion_obj):
+        if completion_obj.oncomplete:
+            with self.lock:
+                self.complete_completions.append(completion_obj)
+        if completion_obj.onsafe:
+            with self.lock:
+                self.safe_completions.append(completion_obj)
+
+    def __get_completion(self, oncomplete, onsafe):
+        """
+        Constructs a completion to use with asynchronous operations
+
+        :param oncomplete: what to do when the write is safe and complete in memory
+            on all replicas
+        :type oncomplete: completion
+        :param onsafe:  what to do when the write is safe and complete on storage
+            on all replicas
+        :type onsafe: completion
+
+        :raises: :class:`Error`
+        :returns: completion object
+        """
+
+        completion_obj = Completion(self, oncomplete, onsafe)
+
+        cdef:
+            rados_callback_t complete_cb = NULL
+            rados_callback_t safe_cb = NULL
+            rados_completion_t completion
+            PyObject* p_completion_obj= <PyObject*>completion_obj
+
+        if oncomplete:
+            complete_cb = <rados_callback_t>&__aio_complete_cb
+        if onsafe:
+            safe_cb = <rados_callback_t>&__aio_safe_cb
+
+        with nogil:
+            ret = rados_aio_create_completion(p_completion_obj, complete_cb, safe_cb,
+                                              &completion)
+        if ret < 0:
+            raise make_ex(ret, "error getting a completion")
+
+        completion_obj.rados_comp = completion
+        return completion_obj
+
+    @requires(('object_name', str_type), ('oncomplete', opt(Callable)))
+    def aio_stat(self, object_name, oncomplete):
+        """
+        Asynchronously get object stats (size/mtime)
+
+        oncomplete will be called with the returned size and mtime
+        as well as the completion:
+
+        oncomplete(completion, size, mtime)
+
+        :param object_name: the name of the object to get stats from
+        :type object_name: str
+        :param oncomplete: what to do when the stat is complete
+        :type oncomplete: completion
+
+        :raises: :class:`Error`
+        :returns: completion object
+        """
+
+        object_name = cstr(object_name, 'object_name')
+
+        cdef:
+            Completion completion
+            char *_object_name = object_name
+            uint64_t psize
+            time_t pmtime
+
+        def oncomplete_(completion_v):
+            cdef Completion _completion_v = completion_v
+            return_value = _completion_v.get_return_value()
+            if return_value >= 0:
+                return oncomplete(_completion_v, psize, time.localtime(pmtime))
+            else:
+                return oncomplete(_completion_v, None, None)
+
+        completion = self.__get_completion(oncomplete_, None)
+        self.__track_completion(completion)
+        with nogil:
+            ret = rados_aio_stat(self.io, _object_name, completion.rados_comp,
+                                 &psize, &pmtime)
+
+        if ret < 0:
+            completion._cleanup()
+            raise make_ex(ret, "error stating %s" % object_name)
+        return completion
+
+    @requires(('object_name', str_type), ('to_write', bytes), ('offset', int),
+              ('oncomplete', opt(Callable)), ('onsafe', opt(Callable)))
+    def aio_write(self, object_name, to_write, offset=0,
+                  oncomplete=None, onsafe=None):
+        """
+        Write data to an object asynchronously
+
+        Queues the write and returns.
+
+        :param object_name: name of the object
+        :type object_name: str
+        :param to_write: data to write
+        :type to_write: bytes
+        :param offset: byte offset in the object to begin writing at
+        :type offset: int
+        :param oncomplete: what to do when the write is safe and complete in memory
+            on all replicas
+        :type oncomplete: completion
+        :param onsafe:  what to do when the write is safe and complete on storage
+            on all replicas
+        :type onsafe: completion
+
+        :raises: :class:`Error`
+        :returns: completion object
+        """
+
+        object_name = cstr(object_name, 'object_name')
+
+        cdef:
+            Completion completion
+            char* _object_name = object_name
+            char* _to_write = to_write
+            size_t size = len(to_write)
+            uint64_t _offset = offset
+
+        completion = self.__get_completion(oncomplete, onsafe)
+        self.__track_completion(completion)
+        with nogil:
+            ret = rados_aio_write(self.io, _object_name, completion.rados_comp,
+                                _to_write, size, _offset)
+        if ret < 0:
+            completion._cleanup()
+            raise make_ex(ret, "error writing object %s" % object_name)
+        return completion
+
+    @requires(('object_name', str_type), ('to_write', bytes), ('oncomplete', opt(Callable)),
+              ('onsafe', opt(Callable)))
+    def aio_write_full(self, object_name, to_write,
+                       oncomplete=None, onsafe=None):
+        """
+        Asychronously write an entire object
+
+        The object is filled with the provided data. If the object exists,
+        it is atomically truncated and then written.
+        Queues the write and returns.
+
+        :param object_name: name of the object
+        :type object_name: str
+        :param to_write: data to write
+        :type to_write: str
+        :param oncomplete: what to do when the write is safe and complete in memory
+            on all replicas
+        :type oncomplete: completion
+        :param onsafe:  what to do when the write is safe and complete on storage
+            on all replicas
+        :type onsafe: completion
+
+        :raises: :class:`Error`
+        :returns: completion object
+        """
+
+        object_name = cstr(object_name, 'object_name')
+
+        cdef:
+            Completion completion
+            char* _object_name = object_name
+            char* _to_write = to_write
+            size_t size = len(to_write)
+
+        completion = self.__get_completion(oncomplete, onsafe)
+        self.__track_completion(completion)
+        with nogil:
+            ret = rados_aio_write_full(self.io, _object_name,
+                                    completion.rados_comp,
+                                    _to_write, size)
+        if ret < 0:
+            completion._cleanup()
+            raise make_ex(ret, "error writing object %s" % object_name)
+        return completion
+
+    @requires(('object_name', str_type), ('to_append', bytes), ('oncomplete', opt(Callable)),
+              ('onsafe', opt(Callable)))
+    def aio_append(self, object_name, to_append, oncomplete=None, onsafe=None):
+        """
+        Asychronously append data to an object
+
+        Queues the write and returns.
+
+        :param object_name: name of the object
+        :type object_name: str
+        :param to_append: data to append
+        :type to_append: str
+        :param offset: byte offset in the object to begin writing at
+        :type offset: int
+        :param oncomplete: what to do when the write is safe and complete in memory
+            on all replicas
+        :type oncomplete: completion
+        :param onsafe:  what to do when the write is safe and complete on storage
+            on all replicas
+        :type onsafe: completion
+
+        :raises: :class:`Error`
+        :returns: completion object
+        """
+        object_name = cstr(object_name, 'object_name')
+
+        cdef:
+            Completion completion
+            char* _object_name = object_name
+            char* _to_append = to_append
+            size_t size = len(to_append)
+
+        completion = self.__get_completion(oncomplete, onsafe)
+        self.__track_completion(completion)
+        with nogil:
+            ret = rados_aio_append(self.io, _object_name,
+                                completion.rados_comp,
+                                _to_append, size)
+        if ret < 0:
+            completion._cleanup()
+            raise make_ex(ret, "error appending object %s" % object_name)
+        return completion
+
+    def aio_flush(self):
+        """
+        Block until all pending writes in an io context are safe
+
+        :raises: :class:`Error`
+        """
+        with nogil:
+            ret = rados_aio_flush(self.io)
+        if ret < 0:
+            raise make_ex(ret, "error flushing")
+
+    @requires(('object_name', str_type), ('length', int), ('offset', int),
+              ('oncomplete', opt(Callable)))
+    def aio_read(self, object_name, length, offset, oncomplete):
+        """
+        Asychronously read data from an object
+
+        oncomplete will be called with the returned read value as
+        well as the completion:
+
+        oncomplete(completion, data_read)
+
+        :param object_name: name of the object to read from
+        :type object_name: str
+        :param length: the number of bytes to read
+        :type length: int
+        :param offset: byte offset in the object to begin reading from
+        :type offset: int
+        :param oncomplete: what to do when the read is complete
+        :type oncomplete: completion
+
+        :raises: :class:`Error`
+        :returns: completion object
+        """
+
+        object_name = cstr(object_name, 'object_name')
+
+        cdef:
+            Completion completion
+            char* _object_name = object_name
+            uint64_t _offset = offset
+
+            char *ref_buf
+            size_t _length = length
+
+        def oncomplete_(completion_v):
+            cdef Completion _completion_v = completion_v
+            return_value = _completion_v.get_return_value()
+            if return_value > 0 and return_value != length:
+                _PyBytes_Resize(&_completion_v.buf, return_value)
+            return oncomplete(_completion_v, <object>_completion_v.buf if return_value >= 0 else None)
+
+        completion = self.__get_completion(oncomplete_, None)
+        completion.buf = PyBytes_FromStringAndSize(NULL, length)
+        ret_buf = PyBytes_AsString(completion.buf)
+        self.__track_completion(completion)
+        with nogil:
+            ret = rados_aio_read(self.io, _object_name, completion.rados_comp,
+                                ret_buf, _length, _offset)
+        if ret < 0:
+            completion._cleanup()
+            raise make_ex(ret, "error reading %s" % object_name)
+        return completion
+
+    @requires(('object_name', str_type), ('cls', str_type), ('method', str_type),
+              ('data', bytes), ('length', int),
+              ('oncomplete', opt(Callable)), ('onsafe', opt(Callable)))
+    def aio_execute(self, object_name, cls, method, data,
+                    length=8192, oncomplete=None, onsafe=None):
+        """
+        Asynchronously execute an OSD class method on an object.
+
+        oncomplete and onsafe will be called with the data returned from
+        the plugin as well as the completion:
+
+        oncomplete(completion, data)
+        onsafe(completion, data)
+
+        :param object_name: name of the object
+        :type object_name: str
+        :param cls: name of the object class
+        :type cls: str
+        :param method: name of the method
+        :type method: str
+        :param data: input data
+        :type data: bytes
+        :param length: size of output buffer in bytes (default=8192)
+        :type length: int
+        :param oncomplete: what to do when the execution is complete
+        :type oncomplete: completion
+        :param onsafe:  what to do when the execution is safe and complete
+        :type onsafe: completion
+
+        :raises: :class:`Error`
+        :returns: completion object
+        """
+
+        object_name = cstr(object_name, 'object_name')
+        cls = cstr(cls, 'cls')
+        method = cstr(method, 'method')
+        cdef:
+            Completion completion
+            char *_object_name = object_name
+            char *_cls = cls
+            char *_method = method
+            char *_data = data
+            size_t _data_len = len(data)
+
+            char *ref_buf
+            size_t _length = length
+
+        def oncomplete_(completion_v):
+            cdef Completion _completion_v = completion_v
+            return_value = _completion_v.get_return_value()
+            if return_value > 0 and return_value != length:
+                _PyBytes_Resize(&_completion_v.buf, return_value)
+            return oncomplete(_completion_v, <object>_completion_v.buf if return_value >= 0 else None)
+
+        def onsafe_(completion_v):
+            cdef Completion _completion_v = completion_v
+            return_value = _completion_v.get_return_value()
+            return onsafe(_completion_v, <object>_completion_v.buf if return_value >= 0 else None)
+
+        completion = self.__get_completion(oncomplete_ if oncomplete else None, onsafe_ if onsafe else None)
+        completion.buf = PyBytes_FromStringAndSize(NULL, length)
+        ret_buf = PyBytes_AsString(completion.buf)
+        self.__track_completion(completion)
+        with nogil:
+            ret = rados_aio_exec(self.io, _object_name, completion.rados_comp,
+                                 _cls, _method, _data, _data_len, ret_buf, _length)
+        if ret < 0:
+            completion._cleanup()
+            raise make_ex(ret, "error executing %s::%s on %s" % (cls, method, object_name))
+        return completion
+
+    @requires(('object_name', str_type), ('oncomplete', opt(Callable)), ('onsafe', opt(Callable)))
+    def aio_remove(self, object_name, oncomplete=None, onsafe=None):
+        """
+        Asychronously remove an object
+
+        :param object_name: name of the object to remove
+        :type object_name: str
+        :param oncomplete: what to do when the remove is safe and complete in memory
+            on all replicas
+        :type oncomplete: completion
+        :param onsafe:  what to do when the remove is safe and complete on storage
+            on all replicas
+        :type onsafe: completion
+
+        :raises: :class:`Error`
+        :returns: completion object
+        """
+        object_name = cstr(object_name, 'object_name')
+
+        cdef:
+            Completion completion
+            char* _object_name = object_name
+
+        completion = self.__get_completion(oncomplete, onsafe)
+        self.__track_completion(completion)
+        with nogil:
+            ret = rados_aio_remove(self.io, _object_name,
+                                completion.rados_comp)
+        if ret < 0:
+            completion._cleanup()
+            raise make_ex(ret, "error removing %s" % object_name)
+        return completion
+
+    def require_ioctx_open(self):
+        """
+        Checks if the rados.Ioctx object state is 'open'
+
+        :raises: IoctxStateError
+        """
+        if self.state != "open":
+            raise IoctxStateError("The pool is %s" % self.state)
+
+    def change_auid(self, auid):
+        """
+        Attempt to change an io context's associated auid "owner."
+
+        Requires that you have write permission on both the current and new
+        auid.
+
+        :raises: :class:`Error`
+        """
+        self.require_ioctx_open()
+
+        cdef:
+            uint64_t _auid = auid
+
+        with nogil:
+            ret = rados_ioctx_pool_set_auid(self.io, _auid)
+        if ret < 0:
+            raise make_ex(ret, "error changing auid of '%s' to %d"
+                          % (self.name, auid))
+
+    @requires(('loc_key', str_type))
+    def set_locator_key(self, loc_key):
+        """
+        Set the key for mapping objects to pgs within an io context.
+
+        The key is used instead of the object name to determine which
+        placement groups an object is put in. This affects all subsequent
+        operations of the io context - until a different locator key is
+        set, all objects in this io context will be placed in the same pg.
+
+        :param loc_key: the key to use as the object locator, or NULL to discard
+            any previously set key
+        :type loc_key: str
+
+        :raises: :class:`TypeError`
+        """
+        self.require_ioctx_open()
+        cloc_key = cstr(loc_key, 'loc_key')
+        cdef char *_loc_key = cloc_key
+        with nogil:
+            rados_ioctx_locator_set_key(self.io, _loc_key)
+        self.locator_key = loc_key
+
+    def get_locator_key(self):
+        """
+        Get the locator_key of context
+
+        :returns: locator_key
+        """
+        return self.locator_key
+
+    @requires(('snap_id', long))
+    def set_read(self, snap_id):
+        """
+        Set the snapshot for reading objects.
+
+        To stop to read from snapshot, use set_read(LIBRADOS_SNAP_HEAD)
+
+        :param snap_id: the snapshot Id
+        :type snap_id: int
+
+        :raises: :class:`TypeError`
+        """
+        self.require_ioctx_open()
+        cdef rados_snap_t _snap_id = snap_id
+        with nogil:
+            rados_ioctx_snap_set_read(self.io, _snap_id)
+
+    @requires(('nspace', str_type))
+    def set_namespace(self, nspace):
+        """
+        Set the namespace for objects within an io context.
+
+        The namespace in addition to the object name fully identifies
+        an object. This affects all subsequent operations of the io context
+        - until a different namespace is set, all objects in this io context
+        will be placed in the same namespace.
+
+        :param nspace: the namespace to use, or None/"" for the default namespace
+        :type nspace: str
+
+        :raises: :class:`TypeError`
+        """
+        self.require_ioctx_open()
+        if nspace is None:
+            nspace = ""
+        cnspace = cstr(nspace, 'nspace')
+        cdef char *_nspace = cnspace
+        with nogil:
+            rados_ioctx_set_namespace(self.io, _nspace)
+        self.nspace = nspace
+
+    def get_namespace(self):
+        """
+        Get the namespace of context
+
+        :returns: namespace
+        """
+        return self.nspace
+
+    def close(self):
+        """
+        Close a rados.Ioctx object.
+
+        This just tells librados that you no longer need to use the io context.
+        It may not be freed immediately if there are pending asynchronous
+        requests on it, but you should not use an io context again after
+        calling this function on it.
+        """
+        if self.state == "open":
+            self.require_ioctx_open()
+            with nogil:
+                rados_ioctx_destroy(self.io)
+            self.state = "closed"
+
+
+    @requires(('key', str_type), ('data', bytes))
+    def write(self, key, data, offset=0):
+        """
+        Write data to an object synchronously
+
+        :param key: name of the object
+        :type key: str
+        :param data: data to write
+        :type data: bytes
+        :param offset: byte offset in the object to begin writing at
+        :type offset: int
+
+        :raises: :class:`TypeError`
+        :raises: :class:`LogicError`
+        :returns: int - 0 on success
+        """
+        self.require_ioctx_open()
+
+        key = cstr(key, 'key')
+        cdef:
+            char *_key = key
+            char *_data = data
+            size_t length = len(data)
+            uint64_t _offset = offset
+
+        with nogil:
+            ret = rados_write(self.io, _key, _data, length, _offset)
+        if ret == 0:
+            return ret
+        elif ret < 0:
+            raise make_ex(ret, "Ioctx.write(%s): failed to write %s"
+                          % (self.name, key))
+        else:
+            raise LogicError("Ioctx.write(%s): rados_write \
+returned %d, but should return zero on success." % (self.name, ret))
+
+    @requires(('key', str_type), ('data', bytes))
+    def write_full(self, key, data):
+        """
+        Write an entire object synchronously.
+
+        The object is filled with the provided data. If the object exists,
+        it is atomically truncated and then written.
+
+        :param key: name of the object
+        :type key: str
+        :param data: data to write
+        :type data: bytes
+
+        :raises: :class:`TypeError`
+        :raises: :class:`Error`
+        :returns: int - 0 on success
+        """
+        self.require_ioctx_open()
+        key = cstr(key, 'key')
+        cdef:
+            char *_key = key
+            char *_data = data
+            size_t length = len(data)
+
+        with nogil:
+            ret = rados_write_full(self.io, _key, _data, length)
+        if ret == 0:
+            return ret
+        elif ret < 0:
+            raise make_ex(ret, "Ioctx.write_full(%s): failed to write %s"
+                          % (self.name, key))
+        else:
+            raise LogicError("Ioctx.write_full(%s): rados_write_full \
+returned %d, but should return zero on success." % (self.name, ret))
+
+    @requires(('key', str_type), ('data', bytes))
+    def append(self, key, data):
+        """
+        Append data to an object synchronously
+
+        :param key: name of the object
+        :type key: str
+        :param data: data to write
+        :type data: bytes
+
+        :raises: :class:`TypeError`
+        :raises: :class:`LogicError`
+        :returns: int - 0 on success
+        """
+        self.require_ioctx_open()
+        key = cstr(key, 'key')
+        cdef:
+            char *_key = key
+            char *_data = data
+            size_t length = len(data)
+
+        with nogil:
+            ret = rados_append(self.io, _key, _data, length)
+        if ret == 0:
+            return ret
+        elif ret < 0:
+            raise make_ex(ret, "Ioctx.append(%s): failed to append %s"
+                          % (self.name, key))
+        else:
+            raise LogicError("Ioctx.append(%s): rados_append \
+returned %d, but should return zero on success." % (self.name, ret))
+
+    @requires(('key', str_type))
+    def read(self, key, length=8192, offset=0):
+        """
+        Read data from an object synchronously
+
+        :param key: name of the object
+        :type key: str
+        :param length: the number of bytes to read (default=8192)
+        :type length: int
+        :param offset: byte offset in the object to begin reading at
+        :type offset: int
+
+        :raises: :class:`TypeError`
+        :raises: :class:`Error`
+        :returns: str - data read from object
+        """
+        self.require_ioctx_open()
+        key = cstr(key, 'key')
+        cdef:
+            char *_key = key
+            char *ret_buf
+            uint64_t _offset = offset
+            size_t _length = length
+            PyObject* ret_s = NULL
+
+        ret_s = PyBytes_FromStringAndSize(NULL, length)
+        try:
+            ret_buf = PyBytes_AsString(ret_s)
+            with nogil:
+                ret = rados_read(self.io, _key, ret_buf, _length, _offset)
+            if ret < 0:
+                raise make_ex(ret, "Ioctx.read(%s): failed to read %s" % (self.name, key))
+
+            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)
+
+    @requires(('key', str_type), ('cls', str_type), ('method', str_type), ('data', bytes))
+    def execute(self, key, cls, method, data, length=8192):
+        """
+        Execute an OSD class method on an object.
+
+        :param key: name of the object
+        :type key: str
+        :param cls: name of the object class
+        :type cls: str
+        :param method: name of the method
+        :type method: str
+        :param data: input data
+        :type data: bytes
+        :param length: size of output buffer in bytes (default=8192)
+        :type length: int
+
+        :raises: :class:`TypeError`
+        :raises: :class:`Error`
+        :returns: (ret, method output)
+        """
+        self.require_ioctx_open()
+
+        key = cstr(key, 'key')
+        cls = cstr(cls, 'cls')
+        method = cstr(method, 'method')
+        cdef:
+            char *_key = key
+            char *_cls = cls
+            char *_method = method
+            char *_data = data
+            size_t _data_len = len(data)
+
+            char *ref_buf
+            size_t _length = length
+            PyObject* ret_s = NULL
+
+        ret_s = PyBytes_FromStringAndSize(NULL, length)
+        try:
+            ret_buf = PyBytes_AsString(ret_s)
+            with nogil:
+                ret = rados_exec(self.io, _key, _cls, _method, _data,
+                                 _data_len, ret_buf, _length)
+            if ret < 0:
+                raise make_ex(ret, "Ioctx.read(%s): failed to read %s" % (self.name, key))
+
+            if ret != length:
+                _PyBytes_Resize(&ret_s, ret)
+
+            return ret, <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 get_stats(self):
+        """
+        Get pool usage statistics
+
+        :returns: dict - contains the following keys:
+
+            - ``num_bytes`` (int) - size of pool in bytes
+
+            - ``num_kb`` (int) - size of pool in kbytes
+
+            - ``num_objects`` (int) - number of objects in the pool
+
+            - ``num_object_clones`` (int) - number of object clones
+
+            - ``num_object_copies`` (int) - number of object copies
+
+            - ``num_objects_missing_on_primary`` (int) - number of objets
+                missing on primary
+
+            - ``num_objects_unfound`` (int) - number of unfound objects
+
+            - ``num_objects_degraded`` (int) - number of degraded objects
+
+            - ``num_rd`` (int) - bytes read
+
+            - ``num_rd_kb`` (int) - kbytes read
+
+            - ``num_wr`` (int) - bytes written
+
+            - ``num_wr_kb`` (int) - kbytes written
+        """
+        self.require_ioctx_open()
+        cdef rados_pool_stat_t stats
+        with nogil:
+            ret = rados_ioctx_pool_stat(self.io, &stats)
+        if ret < 0:
+            raise make_ex(ret, "Ioctx.get_stats(%s): get_stats failed" % self.name)
+        return {'num_bytes': stats.num_bytes,
+                'num_kb': stats.num_kb,
+                'num_objects': stats.num_objects,
+                'num_object_clones': stats.num_object_clones,
+                'num_object_copies': stats.num_object_copies,
+                "num_objects_missing_on_primary": stats.num_objects_missing_on_primary,
+                "num_objects_unfound": stats.num_objects_unfound,
+                "num_objects_degraded": stats.num_objects_degraded,
+                "num_rd": stats.num_rd,
+                "num_rd_kb": stats.num_rd_kb,
+                "num_wr": stats.num_wr,
+                "num_wr_kb": stats.num_wr_kb}
+
+    @requires(('key', str_type))
+    def remove_object(self, key):
+        """
+        Delete an object
+
+        This does not delete any snapshots of the object.
+
+        :param key: the name of the object to delete
+        :type key: str
+
+        :raises: :class:`TypeError`
+        :raises: :class:`Error`
+        :returns: bool - True on success
+        """
+        self.require_ioctx_open()
+        key = cstr(key, 'key')
+        cdef:
+            char *_key = key
+
+        with nogil:
+            ret = rados_remove(self.io, _key)
+        if ret < 0:
+            raise make_ex(ret, "Failed to remove '%s'" % key)
+        return True
+
+    @requires(('key', str_type))
+    def trunc(self, key, size):
+        """
+        Resize an object
+
+        If this enlarges the object, the new area is logically filled with
+        zeroes. If this shrinks the object, the excess data is removed.
+
+        :param key: the name of the object to resize
+        :type key: str
+        :param size: the new size of the object in bytes
+        :type size: int
+
+        :raises: :class:`TypeError`
+        :raises: :class:`Error`
+        :returns: int - 0 on success, otherwise raises error
+        """
+
+        self.require_ioctx_open()
+        key = cstr(key, 'key')
+        cdef:
+            char *_key = key
+            uint64_t _size = size
+
+        with nogil:
+            ret = rados_trunc(self.io, _key, _size)
+        if ret < 0:
+            raise make_ex(ret, "Ioctx.trunc(%s): failed to truncate %s" % (self.name, key))
+        return ret
+
+    @requires(('key', str_type))
+    def stat(self, key):
+        """
+        Get object stats (size/mtime)
+
+        :param key: the name of the object to get stats from
+        :type key: str
+
+        :raises: :class:`TypeError`
+        :raises: :class:`Error`
+        :returns: (size,timestamp)
+        """
+        self.require_ioctx_open()
+
+        key = cstr(key, 'key')
+        cdef:
+            char *_key = key
+            uint64_t psize
+            time_t pmtime
+
+        with nogil:
+            ret = rados_stat(self.io, _key, &psize, &pmtime)
+        if ret < 0:
+            raise make_ex(ret, "Failed to stat %r" % key)
+        return psize, time.localtime(pmtime)
+
+    @requires(('key', str_type), ('xattr_name', str_type))
+    def get_xattr(self, key, xattr_name):
+        """
+        Get the value of an extended attribute on an object.
+
+        :param key: the name of the object to get xattr from
+        :type key: str
+        :param xattr_name: which extended attribute to read
+        :type xattr_name: str
+
+        :raises: :class:`TypeError`
+        :raises: :class:`Error`
+        :returns: str - value of the xattr
+        """
+        self.require_ioctx_open()
+
+        key = cstr(key, 'key')
+        xattr_name = cstr(xattr_name, 'xattr_name')
+        cdef:
+            char *_key = key
+            char *_xattr_name = xattr_name
+            size_t ret_length = 4096
+            char *ret_buf = NULL
+
+        try:
+            while ret_length < 4096 * 1024 * 1024:
+                ret_buf = <char *>realloc_chk(ret_buf, ret_length)
+                with nogil:
+                    ret = rados_getxattr(self.io, _key, _xattr_name, ret_buf, ret_length)
+                if ret == -errno.ERANGE:
+                    ret_length *= 2
+                elif ret < 0:
+                    raise make_ex(ret, "Failed to get xattr %r" % xattr_name)
+                else:
+                    break
+            return ret_buf[:ret]
+        finally:
+            free(ret_buf)
+
+    @requires(('oid', str_type))
+    def get_xattrs(self, oid):
+        """
+        Start iterating over xattrs on an object.
+
+        :param oid: the name of the object to get xattrs from
+        :type oid: str
+
+        :raises: :class:`TypeError`
+        :raises: :class:`Error`
+        :returns: XattrIterator
+        """
+        self.require_ioctx_open()
+        return XattrIterator(self, oid)
+
+    @requires(('key', str_type), ('xattr_name', str_type), ('xattr_value', bytes))
+    def set_xattr(self, key, xattr_name, xattr_value):
+        """
+        Set an extended attribute on an object.
+
+        :param key: the name of the object to set xattr to
+        :type key: str
+        :param xattr_name: which extended attribute to set
+        :type xattr_name: str
+        :param xattr_value: the value of the  extended attribute
+        :type xattr_value: bytes
+
+        :raises: :class:`TypeError`
+        :raises: :class:`Error`
+        :returns: bool - True on success, otherwise raise an error
+        """
+        self.require_ioctx_open()
+
+        key = cstr(key, 'key')
+        xattr_name = cstr(xattr_name, 'xattr_name')
+        cdef:
+            char *_key = key
+            char *_xattr_name = xattr_name
+            char *_xattr_value = xattr_value
+            size_t _xattr_value_len = len(xattr_value)
+
+        with nogil:
+            ret = rados_setxattr(self.io, _key, _xattr_name,
+                                 _xattr_value, _xattr_value_len)
+        if ret < 0:
+            raise make_ex(ret, "Failed to set xattr %r" % xattr_name)
+        return True
+
+    @requires(('key', str_type), ('xattr_name', str_type))
+    def rm_xattr(self, key, xattr_name):
+        """
+        Removes an extended attribute on from an object.
+
+        :param key: the name of the object to remove xattr from
+        :type key: str
+        :param xattr_name: which extended attribute to remove
+        :type xattr_name: str
+
+        :raises: :class:`TypeError`
+        :raises: :class:`Error`
+        :returns: bool - True on success, otherwise raise an error
+        """
+        self.require_ioctx_open()
+
+        key = cstr(key, 'key')
+        xattr_name = cstr(xattr_name, 'xattr_name')
+        cdef:
+            char *_key = key
+            char *_xattr_name = xattr_name
+
+        with nogil:
+            ret = rados_rmxattr(self.io, _key, _xattr_name)
+        if ret < 0:
+            raise make_ex(ret, "Failed to delete key %r xattr %r" %
+                          (key, xattr_name))
+        return True
+
+    def list_objects(self):
+        """
+        Get ObjectIterator on rados.Ioctx object.
+
+        :returns: ObjectIterator
+        """
+        self.require_ioctx_open()
+        return ObjectIterator(self)
+
+    def list_snaps(self):
+        """
+        Get SnapIterator on rados.Ioctx object.
+
+        :returns: SnapIterator
+        """
+        self.require_ioctx_open()
+        return SnapIterator(self)
+
+    @requires(('snap_name', str_type))
+    def create_snap(self, snap_name):
+        """
+        Create a pool-wide snapshot
+
+        :param snap_name: the name of the snapshot
+        :type snap_name: str
+
+        :raises: :class:`TypeError`
+        :raises: :class:`Error`
+        """
+        self.require_ioctx_open()
+        snap_name = cstr(snap_name, 'snap_name')
+        cdef char *_snap_name = snap_name
+
+        with nogil:
+            ret = rados_ioctx_snap_create(self.io, _snap_name)
+        if ret != 0:
+            raise make_ex(ret, "Failed to create snap %s" % snap_name)
+
+    @requires(('snap_name', str_type))
+    def remove_snap(self, snap_name):
+        """
+        Removes a pool-wide snapshot
+
+        :param snap_name: the name of the snapshot
+        :type snap_name: str
+
+        :raises: :class:`TypeError`
+        :raises: :class:`Error`
+        """
+        self.require_ioctx_open()
+        snap_name = cstr(snap_name, 'snap_name')
+        cdef char *_snap_name = snap_name
+
+        with nogil:
+            ret = rados_ioctx_snap_remove(self.io, _snap_name)
+        if ret != 0:
+            raise make_ex(ret, "Failed to remove snap %s" % snap_name)
+
+    @requires(('snap_name', str_type))
+    def lookup_snap(self, snap_name):
+        """
+        Get the id of a pool snapshot
+
+        :param snap_name: the name of the snapshot to lookop
+        :type snap_name: str
+
+        :raises: :class:`TypeError`
+        :raises: :class:`Error`
+        :returns: Snap - on success
+        """
+        self.require_ioctx_open()
+        csnap_name = cstr(snap_name, 'snap_name')
+        cdef:
+            char *_snap_name = csnap_name
+            rados_snap_t snap_id
+
+        with nogil:
+            ret = rados_ioctx_snap_lookup(self.io, _snap_name, &snap_id)
+        if ret != 0:
+            raise make_ex(ret, "Failed to lookup snap %s" % snap_name)
+        return Snap(self, snap_name, int(snap_id))
+
+    @requires(('oid', str_type), ('snap_name', str_type))
+    def snap_rollback(self, oid, snap_name):
+        """
+        Rollback an object to a snapshot
+
+        :param oid: the name of the object
+        :type oid: str
+        :param snap_name: the name of the snapshot
+        :type snap_name: str
+
+        :raises: :class:`TypeError`
+        :raises: :class:`Error`
+        """
+        self.require_ioctx_open()
+        oid = cstr(oid, 'oid')
+        snap_name = cstr(snap_name, 'snap_name')
+        cdef:
+            char *_snap_name = snap_name
+            char *_oid = oid
+
+        with nogil:
+            ret = rados_ioctx_snap_rollback(self.io, _oid, _snap_name)
+        if ret != 0:
+            raise make_ex(ret, "Failed to rollback %s" % oid)
+
+    def get_last_version(self):
+        """
+        Return the version of the last object read or written to.
+
+        This exposes the internal version number of the last object read or
+        written via this io context
+
+        :returns: version of the last object used
+        """
+        self.require_ioctx_open()
+        with nogil:
+            ret = rados_get_last_version(self.io)
+        return int(ret)
+
+    def create_write_op(self):
+        """
+        create write operation object.
+        need call release_write_op after use
+        """
+        return WriteOp().create()
+
+    def create_read_op(self):
+        """
+        create read operation object.
+        need call release_read_op after use
+        """
+        return ReadOp().create()
+
+    def release_write_op(self, write_op):
+        """
+        release memory alloc by create_write_op
+        """
+        write_op.release()
+
+    def release_read_op(self, read_op):
+        """
+        release memory alloc by create_read_op
+        :para read_op: read_op object
+        :type: int
+        """
+        read_op.release()
+
+    @requires(('write_op', WriteOp), ('keys', tuple), ('values', tuple))
+    def set_omap(self, write_op, keys, values):
+        """
+        set keys values to write_op
+        :para write_op: write_operation object
+        :type write_op: WriteOp
+        :para keys: a tuple of keys
+        :type keys: tuple
+        :para values: a tuple of values
+        :type values: tuple
+        """
+
+        if len(keys) != len(values):
+            raise Error("Rados(): keys and values must have the same number of items")
+
+        keys = cstr_list(keys, 'keys')
+        cdef:
+            WriteOp _write_op = write_op
+            size_t key_num = len(keys)
+            char **_keys = to_bytes_array(keys)
+            char **_values = to_bytes_array(values)
+            size_t *_lens = to_csize_t_array([len(v) for v in values])
+
+        try:
+            with nogil:
+                rados_write_op_omap_set(_write_op.write_op,
+                                        <const char**>_keys,
+                                        <const char**>_values,
+                                        <const size_t*>_lens, key_num)
+        finally:
+            free(_keys)
+            free(_values)
+            free(_lens)
+
+    @requires(('write_op', WriteOp), ('oid', str_type), ('mtime', opt(int)), ('flags', opt(int)))
+    def operate_write_op(self, write_op, oid, mtime=0, flags=LIBRADOS_OPERATION_NOFLAG):
+        """
+        excute the real write operation
+        :para write_op: write operation object
+        :type write_op: WriteOp
+        :para oid: object name
+        :type oid: str
+        :para mtime: the time to set the mtime to, 0 for the current time
+        :type mtime: int
+        :para flags: flags to apply to the entire operation
+        :type flags: int
+        """
+
+        oid = cstr(oid, 'oid')
+        cdef:
+            WriteOp _write_op = write_op
+            char *_oid = oid
+            time_t _mtime = mtime
+            int _flags = flags
+
+        with nogil:
+            ret = rados_write_op_operate(_write_op.write_op, self.io, _oid, &_mtime, _flags)
+        if ret != 0:
+            raise make_ex(ret, "Failed to operate write op for oid %s" % oid)
+
+    @requires(('write_op', WriteOp), ('oid', str_type), ('oncomplete', opt(Callable)), ('onsafe', opt(Callable)), ('mtime', opt(int)), ('flags', opt(int)))
+    def operate_aio_write_op(self, write_op, oid, oncomplete=None, onsafe=None, mtime=0, flags=LIBRADOS_OPERATION_NOFLAG):
+        """
+        excute the real write operation asynchronously
+        :para write_op: write operation object
+        :type write_op: WriteOp
+        :para oid: object name
+        :type oid: str
+        :param oncomplete: what to do when the remove is safe and complete in memory
+            on all replicas
+        :type oncomplete: completion
+        :param onsafe:  what to do when the remove is safe and complete on storage
+            on all replicas
+        :type onsafe: completion
+        :para mtime: the time to set the mtime to, 0 for the current time
+        :type mtime: int
+        :para flags: flags to apply to the entire operation
+        :type flags: int
+
+        :raises: :class:`Error`
+        :returns: completion object
+        """
+
+        oid = cstr(oid, 'oid')
+        cdef:
+            WriteOp _write_op = write_op
+            char *_oid = oid
+            Completion completion
+            time_t _mtime = mtime
+            int _flags = flags
+
+        completion = self.__get_completion(oncomplete, onsafe)
+        self.__track_completion(completion)
+
+        with nogil:
+            ret = rados_aio_write_op_operate(_write_op.write_op, self.io, completion.rados_comp, _oid,
+                                             &_mtime, _flags)
+        if ret != 0:
+            completion._cleanup()
+            raise make_ex(ret, "Failed to operate aio write op for oid %s" % oid)
+        return completion
+
+    @requires(('read_op', ReadOp), ('oid', str_type), ('flag', opt(int)))
+    def operate_read_op(self, read_op, oid, flag=LIBRADOS_OPERATION_NOFLAG):
+        """
+        excute the real read operation
+        :para read_op: read operation object
+        :type read_op: ReadOp
+        :para oid: object name
+        :type oid: str
+        :para flag: flags to apply to the entire operation
+        :type flag: int
+        """
+        oid = cstr(oid, 'oid')
+        cdef:
+            ReadOp _read_op = read_op
+            char *_oid = oid
+            int _flag = flag
+
+        with nogil:
+            ret = rados_read_op_operate(_read_op.read_op, self.io, _oid, _flag)
+        if ret != 0:
+            raise make_ex(ret, "Failed to operate read op for oid %s" % oid)
+
+    @requires(('read_op', ReadOp), ('oid', str_type), ('oncomplete', opt(Callable)), ('onsafe', opt(Callable)), ('flag', opt(int)))
+    def operate_aio_read_op(self, read_op, oid, oncomplete=None, onsafe=None, flag=LIBRADOS_OPERATION_NOFLAG):
+        """
+        excute the real read operation
+        :para read_op: read operation object
+        :type read_op: ReadOp
+        :para oid: object name
+        :type oid: str
+        :param oncomplete: what to do when the remove is safe and complete in memory
+            on all replicas
+        :type oncomplete: completion
+        :param onsafe:  what to do when the remove is safe and complete on storage
+            on all replicas
+        :type onsafe: completion
+        :para flag: flags to apply to the entire operation
+        :type flag: int
+        """
+        oid = cstr(oid, 'oid')
+        cdef:
+            ReadOp _read_op = read_op
+            char *_oid = oid
+            Completion completion
+            int _flag = flag
+
+        completion = self.__get_completion(oncomplete, onsafe)
+        self.__track_completion(completion)
+
+        with nogil:
+            ret = rados_aio_read_op_operate(_read_op.read_op, self.io, completion.rados_comp, _oid, _flag)
+        if ret != 0:
+            completion._cleanup()
+            raise make_ex(ret, "Failed to operate aio read op for oid %s" % oid)
+        return completion
+
+    @requires(('read_op', ReadOp), ('start_after', str_type), ('filter_prefix', str_type), ('max_return', int))
+    def get_omap_vals(self, read_op, start_after, filter_prefix, max_return):
+        """
+        get the omap values
+        :para read_op: read operation object
+        :type read_op: ReadOp
+        :para start_after: list keys starting after start_after
+        :type start_after: str
+        :para filter_prefix: list only keys beginning with filter_prefix
+        :type filter_prefix: str
+        :para max_return: list no more than max_return key/value pairs
+        :type max_return: int
+        :returns: an iterator over the requested omap values, return value from this action
+        """
+
+        start_after = cstr(start_after, 'start_after') if start_after else None
+        filter_prefix = cstr(filter_prefix, 'filter_prefix') if filter_prefix else None
+        cdef:
+            char *_start_after = opt_str(start_after)
+            char *_filter_prefix = opt_str(filter_prefix)
+            ReadOp _read_op = read_op
+            rados_omap_iter_t iter_addr = NULL
+            int _max_return = max_return
+            int prval = 0
+
+        with nogil:
+            rados_read_op_omap_get_vals2(_read_op.read_op, _start_after, _filter_prefix,
+                                         _max_return, &iter_addr, NULL, &prval)
+        it = OmapIterator(self)
+        it.ctx = iter_addr
+        return it, int(prval)
+
+    @requires(('read_op', ReadOp), ('start_after', str_type), ('max_return', int))
+    def get_omap_keys(self, read_op, start_after, max_return):
+        """
+        get the omap keys
+        :para read_op: read operation object
+        :type read_op: ReadOp
+        :para start_after: list keys starting after start_after
+        :type start_after: str
+        :para max_return: list no more than max_return key/value pairs
+        :type max_return: int
+        :returns: an iterator over the requested omap values, return value from this action
+        """
+        start_after = cstr(start_after, 'start_after') if start_after else None
+        cdef:
+            char *_start_after = opt_str(start_after)
+            ReadOp _read_op = read_op
+            rados_omap_iter_t iter_addr = NULL
+            int _max_return = max_return
+            int prval = 0
+
+        with nogil:
+            rados_read_op_omap_get_keys2(_read_op.read_op, _start_after,
+                                         _max_return, &iter_addr, NULL, &prval)
+        it = OmapIterator(self)
+        it.ctx = iter_addr
+        return it, int(prval)
+
+    @requires(('read_op', ReadOp), ('keys', tuple))
+    def get_omap_vals_by_keys(self, read_op, keys):
+        """
+        get the omap values by keys
+        :para read_op: read operation object
+        :type read_op: ReadOp
+        :para keys: input key tuple
+        :type keys: tuple
+        :returns: an iterator over the requested omap values, return value from this action
+        """
+        keys = cstr_list(keys, 'keys')
+        cdef:
+            ReadOp _read_op = read_op
+            rados_omap_iter_t iter_addr
+            char **_keys = to_bytes_array(keys)
+            size_t key_num = len(keys)
+            int prval = 0
+
+        try:
+            with nogil:
+                rados_read_op_omap_get_vals_by_keys(_read_op.read_op,
+                                                    <const char**>_keys,
+                                                    key_num, &iter_addr,  &prval)
+            it = OmapIterator(self)
+            it.ctx = iter_addr
+            return it, int(prval)
+        finally:
+            free(_keys)
+
+    @requires(('write_op', WriteOp), ('keys', tuple))
+    def remove_omap_keys(self, write_op, keys):
+        """
+        remove omap keys specifiled
+        :para write_op: write operation object
+        :type write_op: WriteOp
+        :para keys: input key tuple
+        :type keys: tuple
+        """
+
+        keys = cstr_list(keys, 'keys')
+        cdef:
+            WriteOp _write_op = write_op
+            size_t key_num = len(keys)
+            char **_keys = to_bytes_array(keys)
+
+        try:
+            with nogil:
+                rados_write_op_omap_rm_keys(_write_op.write_op, <const char**>_keys, key_num)
+        finally:
+            free(_keys)
+
+    @requires(('write_op', WriteOp))
+    def clear_omap(self, write_op):
+        """
+        Remove all key/value pairs from an object
+        :para write_op: write operation object
+        :type write_op: WriteOp
+        """
+
+        cdef:
+            WriteOp _write_op = write_op
+
+        with nogil:
+            rados_write_op_omap_clear(_write_op.write_op)
+
+    @requires(('key', str_type), ('name', str_type), ('cookie', str_type), ('desc', str_type),
+              ('duration', opt(int)), ('flags', int))
+    def lock_exclusive(self, key, name, cookie, desc="", duration=None, flags=0):
+
+        """
+        Take an exclusive lock on an object
+
+        :param key: name of the object
+        :type key: str
+        :param name: name of the lock
+        :type name: str
+        :param cookie: cookie of the lock
+        :type cookie: str
+        :param desc: description of the lock
+        :type desc: str
+        :param duration: duration of the lock in seconds
+        :type duration: int
+        :param flags: flags
+        :type flags: int
+
+        :raises: :class:`TypeError`
+        :raises: :class:`Error`
+        """
+        self.require_ioctx_open()
+
+        key = cstr(key, 'key')
+        name = cstr(name, 'name')
+        cookie = cstr(cookie, 'cookie')
+        desc = cstr(desc, 'desc')
+
+        cdef:
+            char* _key = key
+            char* _name = name
+            char* _cookie = cookie
+            char* _desc = desc
+            uint8_t _flags = flags
+            timeval _duration
+
+        if duration is None:
+            with nogil:
+                ret = rados_lock_exclusive(self.io, _key, _name, _cookie, _desc,
+                                           NULL, _flags)
+        else:
+            _duration.tv_sec = duration
+            with nogil:
+                ret = rados_lock_exclusive(self.io, _key, _name, _cookie, _desc,
+                                           &_duration, _flags)
+
+        if ret < 0:
+            raise make_ex(ret, "Ioctx.rados_lock_exclusive(%s): failed to set lock %s on %s" % (self.name, name, key))
+
+    @requires(('key', str_type), ('name', str_type), ('cookie', str_type), ('tag', str_type),
+              ('desc', str_type), ('duration', opt(int)), ('flags', int))
+    def lock_shared(self, key, name, cookie, tag, desc="", duration=None, flags=0):
+
+        """
+        Take a shared lock on an object
+
+        :param key: name of the object
+        :type key: str
+        :param name: name of the lock
+        :type name: str
+        :param cookie: cookie of the lock
+        :type cookie: str
+        :param tag: tag of the lock
+        :type tag: str
+        :param desc: description of the lock
+        :type desc: str
+        :param duration: duration of the lock in seconds
+        :type duration: int
+        :param flags: flags
+        :type flags: int
+
+        :raises: :class:`TypeError`
+        :raises: :class:`Error`
+        """
+        self.require_ioctx_open()
+
+        key = cstr(key, 'key')
+        tag = cstr(tag, 'tag')
+        name = cstr(name, 'name')
+        cookie = cstr(cookie, 'cookie')
+        desc = cstr(desc, 'desc')
+
+        cdef:
+            char* _key = key
+            char* _tag = tag
+            char* _name = name
+            char* _cookie = cookie
+            char* _desc = desc
+            uint8_t _flags = flags
+            timeval _duration
+
+        if duration is None:
+            with nogil:
+                ret = rados_lock_shared(self.io, _key, _name, _cookie, _tag, _desc,
+                                        NULL, _flags)
+        else:
+            _duration.tv_sec = duration
+            with nogil:
+                ret = rados_lock_shared(self.io, _key, _name, _cookie, _tag, _desc,
+                                        &_duration, _flags)
+        if ret < 0:
+            raise make_ex(ret, "Ioctx.rados_lock_exclusive(%s): failed to set lock %s on %s" % (self.name, name, key))
+
+    @requires(('key', str_type), ('name', str_type), ('cookie', str_type))
+    def unlock(self, key, name, cookie):
+
+        """
+        Release a shared or exclusive lock on an object
+
+        :param key: name of the object
+        :type key: str
+        :param name: name of the lock
+        :type name: str
+        :param cookie: cookie of the lock
+        :type cookie: str
+
+        :raises: :class:`TypeError`
+        :raises: :class:`Error`
+        """
+        self.require_ioctx_open()
+
+        key = cstr(key, 'key')
+        name = cstr(name, 'name')
+        cookie = cstr(cookie, 'cookie')
+
+        cdef:
+            char* _key = key
+            char* _name = name
+            char* _cookie = cookie
+
+        with nogil:
+            ret = rados_unlock(self.io, _key, _name, _cookie)
+        if ret < 0:
+            raise make_ex(ret, "Ioctx.rados_lock_exclusive(%s): failed to set lock %s on %s" % (self.name, name, key))
+
+    def application_enable(self, app_name, force=False):
+        """
+        Enable an application on an OSD pool
+
+        :param app_name: application name
+        :type app_name: str
+        :param force: False if only a single app should exist per pool
+        :type expire_seconds: boool
+
+        :raises: :class:`Error`
+        """
+        app_name =  cstr(app_name, 'app_name')
+        cdef:
+            char *_app_name = app_name
+            int _force = (1 if force else 0)
+
+        with nogil:
+            ret = rados_application_enable(self.io, _app_name, _force)
+        if ret < 0:
+            raise make_ex(ret, "error enabling application")
+
+    def application_list(self):
+        """
+        Returns a list of enabled applications
+
+        :returns: list of app name string
+        """
+        cdef:
+            size_t length = 128
+            char *apps = NULL
+
+        try:
+            while True:
+                apps = <char *>realloc_chk(apps, length)
+                with nogil:
+                    ret = rados_application_list(self.io, apps, &length)
+                if ret == 0:
+                    return [decode_cstr(app) for app in
+                                apps[:length].split(b'\0') if app]
+                elif ret == -errno.ENOENT:
+                    return None
+                elif ret == -errno.ERANGE:
+                    pass
+                else:
+                    raise make_ex(ret, "error listing applications")
+        finally:
+            free(apps)
+
+    def application_metadata_set(self, app_name, key, value):
+        """
+        Sets application metadata on an OSD pool
+
+        :param app_name: application name
+        :type app_name: str
+        :param key: metadata key
+        :type key: str
+        :param value: metadata value
+        :type value: str
+
+        :raises: :class:`Error`
+        """
+        app_name =  cstr(app_name, 'app_name')
+        key =  cstr(key, 'key')
+        value =  cstr(value, 'value')
+        cdef:
+            char *_app_name = app_name
+            char *_key = key
+            char *_value = value
+
+        with nogil:
+            ret = rados_application_metadata_set(self.io, _app_name, _key,
+                                                 _value)
+        if ret < 0:
+            raise make_ex(ret, "error setting application metadata")
+
+    def application_metadata_remove(self, app_name, key):
+        """
+        Remove application metadata from an OSD pool
+
+        :param app_name: application name
+        :type app_name: str
+        :param key: metadata key
+        :type key: str
+
+        :raises: :class:`Error`
+        """
+        app_name =  cstr(app_name, 'app_name')
+        key =  cstr(key, 'key')
+        cdef:
+            char *_app_name = app_name
+            char *_key = key
+
+        with nogil:
+            ret = rados_application_metadata_remove(self.io, _app_name, _key)
+        if ret < 0:
+            raise make_ex(ret, "error removing application metadata")
+
+    def application_metadata_list(self, app_name):
+        """
+        Returns a list of enabled applications
+
+        :param app_name: application name
+        :type app_name: str
+        :returns: list of key/value tuples
+        """
+        app_name =  cstr(app_name, 'app_name')
+        cdef:
+            char *_app_name = app_name
+            size_t key_length = 128
+            size_t val_length = 128
+            char *c_keys = NULL
+            char *c_vals = NULL
+
+        try:
+            while True:
+                c_keys = <char *>realloc_chk(c_keys, key_length)
+                c_vals = <char *>realloc_chk(c_vals, val_length)
+                with nogil:
+                    ret = rados_application_metadata_list(self.io, _app_name,
+                                                          c_keys, &key_length,
+                                                          c_vals, &val_length)
+                if ret == 0:
+                    keys = [decode_cstr(key) for key in
+                                c_keys[:key_length].split(b'\0') if key]
+                    vals = [decode_cstr(val) for val in
+                                c_vals[:val_length].split(b'\0') if val]
+                    return zip(keys, vals)
+                elif ret == -errno.ERANGE:
+                    pass
+                else:
+                    raise make_ex(ret, "error listing application metadata")
+        finally:
+            free(c_keys)
+            free(c_vals)
+
+
+def set_object_locator(func):
+    def retfunc(self, *args, **kwargs):
+        if self.locator_key is not None:
+            old_locator = self.ioctx.get_locator_key()
+            self.ioctx.set_locator_key(self.locator_key)
+            retval = func(self, *args, **kwargs)
+            self.ioctx.set_locator_key(old_locator)
+            return retval
+        else:
+            return func(self, *args, **kwargs)
+    return retfunc
+
+
+def set_object_namespace(func):
+    def retfunc(self, *args, **kwargs):
+        if self.nspace is None:
+            raise LogicError("Namespace not set properly in context")
+        old_nspace = self.ioctx.get_namespace()
+        self.ioctx.set_namespace(self.nspace)
+        retval = func(self, *args, **kwargs)
+        self.ioctx.set_namespace(old_nspace)
+        return retval
+    return retfunc
+
+
+class Object(object):
+    """Rados object wrapper, makes the object look like a file"""
+    def __init__(self, ioctx, key, locator_key=None, nspace=None):
+        self.key = key
+        self.ioctx = ioctx
+        self.offset = 0
+        self.state = "exists"
+        self.locator_key = locator_key
+        self.nspace = "" if nspace is None else nspace
+
+    def __str__(self):
+        return "rados.Object(ioctx=%s,key=%s,nspace=%s,locator=%s)" % \
+            (str(self.ioctx), self.key, "--default--"
+             if self.nspace is "" else self.nspace, self.locator_key)
+
+    def require_object_exists(self):
+        if self.state != "exists":
+            raise ObjectStateError("The object is %s" % self.state)
+
+    @set_object_locator
+    @set_object_namespace
+    def read(self, length=1024 * 1024):
+        self.require_object_exists()
+        ret = self.ioctx.read(self.key, length, self.offset)
+        self.offset += len(ret)
+        return ret
+
+    @set_object_locator
+    @set_object_namespace
+    def write(self, string_to_write):
+        self.require_object_exists()
+        ret = self.ioctx.write(self.key, string_to_write, self.offset)
+        if ret == 0:
+            self.offset += len(string_to_write)
+        return ret
+
+    @set_object_locator
+    @set_object_namespace
+    def remove(self):
+        self.require_object_exists()
+        self.ioctx.remove_object(self.key)
+        self.state = "removed"
+
+    @set_object_locator
+    @set_object_namespace
+    def stat(self):
+        self.require_object_exists()
+        return self.ioctx.stat(self.key)
+
+    def seek(self, position):
+        self.require_object_exists()
+        self.offset = position
+
+    @set_object_locator
+    @set_object_namespace
+    def get_xattr(self, xattr_name):
+        self.require_object_exists()
+        return self.ioctx.get_xattr(self.key, xattr_name)
+
+    @set_object_locator
+    @set_object_namespace
+    def get_xattrs(self):
+        self.require_object_exists()
+        return self.ioctx.get_xattrs(self.key)
+
+    @set_object_locator
+    @set_object_namespace
+    def set_xattr(self, xattr_name, xattr_value):
+        self.require_object_exists()
+        return self.ioctx.set_xattr(self.key, xattr_name, xattr_value)
+
+    @set_object_locator
+    @set_object_namespace
+    def rm_xattr(self, xattr_name):
+        self.require_object_exists()
+        return self.ioctx.rm_xattr(self.key, xattr_name)
+
+MONITOR_LEVELS = [
+    "debug",
+    "info",
+    "warn", "warning",
+    "err", "error",
+    "sec",
+    ]
+
+
+class MonitorLog(object):
+    # NOTE(sileht): Keep this class for backward compat
+    # method moved to Rados.monitor_log()
+    """
+    For watching cluster log messages.  Instantiate an object and keep
+    it around while callback is periodically called.  Construct with
+    'level' to monitor 'level' messages (one of MONITOR_LEVELS).
+    arg will be passed to the callback.
+
+    callback will be called with:
+        arg (given to __init__)
+        line (the full line, including timestamp, who, level, msg)
+        who (which entity issued the log message)
+        timestamp_sec (sec of a struct timespec)
+        timestamp_nsec (sec of a struct timespec)
+        seq (sequence number)
+        level (string representing the level of the log message)
+        msg (the message itself)
+    callback's return value is ignored
+    """
+    def __init__(self, cluster, level, callback, arg):
+        self.level = level
+        self.callback = callback
+        self.arg = arg
+        self.cluster = cluster
+        self.cluster.monitor_log(level, callback, arg)
+