]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
pybind: Add Python 3 support for rados and rbd modules
authorDavid Coles <dcoles@gaikai.com>
Thu, 15 Oct 2015 21:58:53 +0000 (14:58 -0700)
committerDavid Coles <dcoles@gaikai.com>
Mon, 19 Oct 2015 22:06:52 +0000 (15:06 -0700)
Python 3 explicitly distinguishes between strings (which can contain any Unicode
character) and 8-bit byte strings. This means that we must explicitly encode and
decode strings that use C-style char* string APIs.

Functions that take a true-strings now have their values encoded to UTF-8 before
being passed to the C API, while functions that accept binary data only accept
the bytes type. To help with this the `cstr` helper function has been introduced
as a way of easily a string into a C-string compatible form.

There is also a number of general Python 3 compatibility fixes:

    - dict.iteritems() is replaced with just dict.items()
    - xrange() is replaced with just range
    - Iterators now use the __next__ magic method
    - zip() and map() now return iterators, rather than lists
    - print() is now a function
    - contextlib.nexted() is replaced with multiple manager with-statement (from Python 2.7)

This also required updating of the unit-tests for these modules, mostly due to
explicitly distinguishing between strings and byte strings.

Signed-off-by: David Coles <dcoles@gaikai.com>
src/pybind/rados.py
src/pybind/rbd.py
src/test/pybind/test_rados.py
src/test/pybind/test_rbd.py

index 804a1692020665c9bd2ffdbfb572e96f800b82b7..30be4f93789e8903ece48a5df3ba6eafdd31d537 100644 (file)
@@ -12,6 +12,7 @@ import errno
 import threading
 import time
 
+from collections import Iterator
 from datetime import datetime
 from functools import wraps
 from itertools import chain
@@ -261,6 +262,19 @@ def requires(*types):
     return wrapper
 
 
+def cstr(val, encoding="utf-8"):
+    """
+    Create a C-style string from a Python string
+
+    :param str val: Python string
+    :rtype: c_char_p
+    """
+    if val is None:
+        return c_char_p(None)
+
+    return c_char_p(val.encode(encoding))
+
+
 class Rados(object):
     """librados python wrapper"""
     def require_state(self, *args):
@@ -297,15 +311,15 @@ Rados object in state %s." % self.state)
         if clustername is None:
             clustername = 'ceph'
         ret = run_in_thread(self.librados.rados_create2,
-                            (byref(self.cluster), c_char_p(clustername),
-                             c_char_p(name), c_uint64(flags)))
+                            (byref(self.cluster), cstr(clustername),
+                            cstr(name), c_uint64(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.iteritems():
+            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
@@ -313,7 +327,7 @@ Rados object in state %s." % self.state)
                 conffile = None
             self.conf_read_file(conffile)
         if conf:
-            for key, value in conf.iteritems():
+            for key, value in conf.items():
                 self.conf_set(key, value)
 
     def shutdown(self):
@@ -358,7 +372,7 @@ Rados object in state %s." % self.state)
         """
         self.require_state("configuring", "connected")
         ret = run_in_thread(self.librados.rados_conf_read_file,
-                            (self.cluster, c_char_p(path)))
+                            (self.cluster, cstr(path)))
         if (ret != 0):
             raise make_ex(ret, "error calling conf_read_file")
 
@@ -372,7 +386,7 @@ Rados object in state %s." % self.state)
             return
         # create instances of arrays of c_char_p's, both len(args) long
         # cretargs will always be a subset of cargs (perhaps identical)
-        cargs = (c_char_p * len(args))(*args)
+        cargs = (c_char_p * len(args))(*map(cstr, args))
         cretargs = (c_char_p * len(args))()
         ret = run_in_thread(self.librados.rados_conf_parse_argv_remainder,
                             (self.cluster, len(args), cargs, cretargs))
@@ -395,7 +409,7 @@ Rados object in state %s." % self.state)
         if not var:
             return
         ret = run_in_thread(self.librados.rados_conf_parse_env,
-                            (self.cluster, c_char_p(var)))
+                            (self.cluster, cstr(var)))
         if (ret != 0):
             raise make_ex(ret, "error calling conf_parse_env")
 
@@ -415,10 +429,10 @@ Rados object in state %s." % self.state)
         while True:
             ret_buf = create_string_buffer(length)
             ret = run_in_thread(self.librados.rados_conf_get,
-                                (self.cluster, c_char_p(option), ret_buf,
-                                 c_size_t(length)))
+                                (self.cluster, cstr(option), ret_buf,
+                                c_size_t(length)))
             if (ret == 0):
-                return ret_buf.value
+                return ret_buf.value.decode("utf-8")
             elif (ret == -errno.ENAMETOOLONG):
                 length = length * 2
             elif (ret == -errno.ENOENT):
@@ -440,7 +454,7 @@ Rados object in state %s." % self.state)
         """
         self.require_state("configuring", "connected")
         ret = run_in_thread(self.librados.rados_conf_set,
-                            (self.cluster, c_char_p(option), c_char_p(val)))
+                            (self.cluster, cstr(option), cstr(val)))
         if (ret != 0):
             raise make_ex(ret, "error calling conf_set")
 
@@ -463,7 +477,7 @@ Rados object in state %s." % self.state)
         outstrlen = c_long()
 
         ret = run_in_thread(self.librados.rados_ping_monitor,
-                            (self.cluster, c_char_p(mon_id),
+                            (self.cluster, cstr(mon_id),
                              outstrp, byref(outstrlen)))
 
         my_outstr = outstrp.contents[:(outstrlen.value)]
@@ -528,7 +542,7 @@ Rados object in state %s." % self.state)
         """
         self.require_state("connected")
         ret = run_in_thread(self.librados.rados_pool_lookup,
-                            (self.cluster, c_char_p(pool_name)))
+                            (self.cluster, cstr(pool_name)))
         if (ret >= 0):
             return True
         elif (ret == -errno.ENOENT):
@@ -549,7 +563,7 @@ Rados object in state %s." % self.state)
         """
         self.require_state("connected")
         ret = run_in_thread(self.librados.rados_pool_lookup,
-                            (self.cluster, c_char_p(pool_name)))
+                            (self.cluster, cstr(pool_name)))
         if (ret >= 0):
             return int(ret)
         elif (ret == -errno.ENOENT):
@@ -606,20 +620,20 @@ Rados object in state %s." % self.state)
         if auid is None:
             if crush_rule is None:
                 ret = run_in_thread(self.librados.rados_pool_create,
-                                    (self.cluster, c_char_p(pool_name)))
+                                    (self.cluster, cstr(pool_name)))
             else:
                 ret = run_in_thread(self.librados.
                                     rados_pool_create_with_crush_rule,
-                                    (self.cluster, c_char_p(pool_name),
+                                    (self.cluster, cstr(pool_name),
                                      c_ubyte(crush_rule)))
 
         elif crush_rule is None:
             ret = run_in_thread(self.librados.rados_pool_create_with_auid,
-                                (self.cluster, c_char_p(pool_name),
+                                (self.cluster, cstr(pool_name),
                                  c_uint64(auid)))
         else:
             ret = run_in_thread(self.librados.rados_pool_create_with_all,
-                                (self.cluster, c_char_p(pool_name),
+                                (self.cluster, cstr(pool_name),
                                  c_uint64(auid), c_ubyte(crush_rule)))
         if ret < 0:
             raise make_ex(ret, "error creating pool '%s'" % pool_name)
@@ -654,7 +668,7 @@ Rados object in state %s." % self.state)
         """
         self.require_state("connected")
         ret = run_in_thread(self.librados.rados_pool_delete,
-                            (self.cluster, c_char_p(pool_name)))
+                            (self.cluster, cstr(pool_name)))
         if ret < 0:
             raise make_ex(ret, "error deleting pool '%s'" % pool_name)
 
@@ -674,7 +688,8 @@ Rados object in state %s." % self.state)
                 size = c_size_t(ret)
             else:
                 break
-        return filter(lambda name: name != '', c_names.raw.split('\0'))
+
+        return [name for name in c_names.raw.decode("utf-8").split('\0') if len(name) > 0]
 
     def get_fsid(self):
         """
@@ -709,7 +724,7 @@ Rados object in state %s." % self.state)
         self.require_state("connected")
         ioctx = c_void_p()
         ret = run_in_thread(self.librados.rados_ioctx_create,
-                            (self.cluster, c_char_p(ioctx_name), byref(ioctx)))
+                            (self.cluster, cstr(ioctx_name), byref(ioctx)))
         if ret < 0:
             raise make_ex(ret, "error opening pool '%s'" % ioctx_name)
         return Ioctx(ioctx_name, self.librados, ioctx)
@@ -724,11 +739,11 @@ Rados object in state %s." % self.state)
         outbuflen = c_long()
         outsp = pointer(pointer(c_char()))
         outslen = c_long()
-        cmdarr = (c_char_p * len(cmd))(*cmd)
+        cmdarr = (c_char_p * len(cmd))(*map(cstr, cmd))
 
         if target:
             ret = run_in_thread(self.librados.rados_mon_command_target,
-                                (self.cluster, c_char_p(target), cmdarr,
+                                (self.cluster, cstr(target), cmdarr,
                                  len(cmd), c_char_p(inbuf), len(inbuf),
                                  outbufp, byref(outbuflen), outsp,
                                  byref(outslen)), timeout)
@@ -761,7 +776,7 @@ Rados object in state %s." % self.state)
         outbuflen = c_long()
         outsp = pointer(pointer(c_char()))
         outslen = c_long()
-        cmdarr = (c_char_p * len(cmd))(*cmd)
+        cmdarr = (c_char_p * len(cmd))(*map(cstr, cmd))
         ret = run_in_thread(self.librados.rados_osd_command,
                             (self.cluster, osdid, cmdarr, len(cmd),
                              c_char_p(inbuf), len(inbuf),
@@ -790,9 +805,9 @@ Rados object in state %s." % self.state)
         outbuflen = c_long()
         outsp = pointer(pointer(c_char()))
         outslen = c_long()
-        cmdarr = (c_char_p * len(cmd))(*cmd)
+        cmdarr = (c_char_p * len(cmd))(*map(cstr, cmd))
         ret = run_in_thread(self.librados.rados_pg_command,
-                            (self.cluster, c_char_p(pgid), cmdarr, len(cmd),
+                            (self.cluster, cstr(pgid), cmdarr, len(cmd),
                              c_char_p(inbuf), len(inbuf),
                              outbufp, byref(outbuflen), outsp, byref(outslen)),
                             timeout)
@@ -826,13 +841,13 @@ Rados object in state %s." % self.state)
         """
         self.require_state("connected")
         ret = run_in_thread(self.librados.rados_blacklist_add,
-                            (self.cluster, c_char_p(client_address),
+                            (self.cluster, cstr(client_address),
                              c_uint32(expire_seconds)))
         if ret < 0:
             raise make_ex(ret, "error blacklisting client '%s'" % client_address)
 
 
-class OmapIterator(object):
+class OmapIterator(Iterator):
     """Omap iterator"""
     def __init__(self, ioctx, ctx):
         self.ioctx = ioctx
@@ -842,6 +857,9 @@ class OmapIterator(object):
         return self
 
     def next(self):
+        return self.__next__()
+
+    def __next__(self):
         """
         Get the next key-value pair in the object
         :returns: next rados.OmapItem
@@ -855,7 +873,7 @@ class OmapIterator(object):
             raise make_ex(ret, "error iterating over the omap")
         if key_.value is None:
             raise StopIteration()
-        key = ctypes.string_at(key_)
+        key = key_.value.decode("utf-8")
         val = None
         if val_.value is not None:
             val = ctypes.string_at(val_, len_)
@@ -865,7 +883,7 @@ class OmapIterator(object):
         run_in_thread(self.ioctx.librados.rados_omap_get_end, (self.ctx,))
 
 
-class ObjectIterator(object):
+class ObjectIterator(Iterator):
     """rados.Ioctx Object iterator"""
     def __init__(self, ioctx):
         self.ioctx = ioctx
@@ -880,26 +898,33 @@ class ObjectIterator(object):
         return self
 
     def next(self):
+        return self.__next__()
+
+    def __next__(self):
         """
         Get the next object name and locator in the pool
 
         :raises: StopIteration
         :returns: next rados.Ioctx Object
         """
-        key = c_char_p()
-        locator = c_char_p()
-        nspace = c_char_p()
+        key_ = c_char_p()
+        locator_ = c_char_p()
+        nspace_ = c_char_p()
         ret = run_in_thread(self.ioctx.librados.rados_nobjects_list_next,
-                            (self.ctx, byref(key), byref(locator), byref(nspace)))
+                            (self.ctx, byref(key_), byref(locator_), byref(nspace_)))
         if ret < 0:
             raise StopIteration()
-        return Object(self.ioctx, key.value, locator.value, nspace.value)
+
+        key = None if key_.value is None else key_.value.decode("utf-8")
+        locator = None if locator_.value is None else locator_.value.decode("utf-8")
+        nspace = None if nspace_.value is None else nspace_.value.decode("utf-8")
+        return Object(self.ioctx, key, locator, nspace)
 
     def __del__(self):
         run_in_thread(self.ioctx.librados.rados_nobjects_list_close, (self.ctx,))
 
 
-class XattrIterator(object):
+class XattrIterator(Iterator):
     """Extended attribute iterator"""
     def __init__(self, ioctx, it, oid):
         self.ioctx = ioctx
@@ -910,6 +935,9 @@ class XattrIterator(object):
         return self
 
     def next(self):
+        return self.__next__()
+
+    def __next__(self):
         """
         Get the next xattr on the object
 
@@ -926,7 +954,7 @@ class XattrIterator(object):
 in '%s'" % self.oid)
         if name_.value is None:
             raise StopIteration()
-        name = ctypes.string_at(name_)
+        name = ctypes.string_at(name_).decode("utf-8")
         val = ctypes.string_at(val_, len_)
         return (name, val)
 
@@ -934,7 +962,7 @@ in '%s'" % self.oid)
         run_in_thread(self.ioctx.librados.rados_getxattrs_end, (self.it,))
 
 
-class SnapIterator(object):
+class SnapIterator(Iterator):
     """Snapshot iterator"""
     def __init__(self, ioctx):
         self.ioctx = ioctx
@@ -958,6 +986,9 @@ ioctx '%s'" % self.ioctx.name)
         return self
 
     def next(self):
+        return self.__next__()
+
+    def __next__(self):
         """
         Get the next Snapshot
 
@@ -979,7 +1010,7 @@ ioctx '%s'" % self.ioctx.name)
             elif (ret != -errno.ERANGE):
                 raise make_ex(ret, "rados_snap_get_name error")
             name_len = name_len * 2
-        snap = Snap(self.ioctx, name.value, snap_id)
+        snap = Snap(self.ioctx, name.value.decode("utf-8"), snap_id)
         self.cur_snap = self.cur_snap + 1
         return snap
 
@@ -1244,7 +1275,7 @@ class Ioctx(object):
         """
         completion = self.__get_completion(oncomplete, onsafe)
         ret = run_in_thread(self.librados.rados_aio_write,
-                            (self.io, c_char_p(object_name),
+                            (self.io, cstr(object_name),
                              completion.rados_comp, c_char_p(to_write),
                              c_size_t(len(to_write)), c_uint64(offset)))
         if ret < 0:
@@ -1276,7 +1307,7 @@ class Ioctx(object):
         """
         completion = self.__get_completion(oncomplete, onsafe)
         ret = run_in_thread(self.librados.rados_aio_write_full,
-                            (self.io, c_char_p(object_name),
+                            (self.io, cstr(object_name),
                              completion.rados_comp, c_char_p(to_write),
                              c_size_t(len(to_write))))
         if ret < 0:
@@ -1307,7 +1338,7 @@ class Ioctx(object):
         """
         completion = self.__get_completion(oncomplete, onsafe)
         ret = run_in_thread(self.librados.rados_aio_append,
-                            (self.io, c_char_p(object_name),
+                            (self.io, cstr(object_name),
                              completion.rados_comp, c_char_p(to_append),
                              c_size_t(len(to_append))))
         if ret < 0:
@@ -1354,7 +1385,7 @@ class Ioctx(object):
 
         completion = self.__get_completion(oncomplete_, None)
         ret = run_in_thread(self.librados.rados_aio_read,
-                            (self.io, c_char_p(object_name),
+                            (self.io, cstr(object_name),
                              completion.rados_comp, buf, c_size_t(length),
                              c_uint64(offset)))
         if ret < 0:
@@ -1379,7 +1410,7 @@ class Ioctx(object):
         """
         completion = self.__get_completion(oncomplete, onsafe)
         ret = run_in_thread(self.librados.rados_aio_remove,
-                            (self.io, c_char_p(object_name),
+                            (self.io, cstr(object_name),
                              completion.rados_comp))
         if ret < 0:
             raise make_ex(ret, "error removing %s" % object_name)
@@ -1428,7 +1459,7 @@ class Ioctx(object):
         """
         self.require_ioctx_open()
         run_in_thread(self.librados.rados_ioctx_locator_set_key,
-                      (self.io, c_char_p(loc_key)))
+                      (self.io, cstr(loc_key)))
         self.locator_key = loc_key
 
     def get_locator_key(self):
@@ -1459,7 +1490,7 @@ class Ioctx(object):
         if nspace is None:
             nspace = ""
         run_in_thread(self.librados.rados_ioctx_set_namespace,
-                      (self.io, c_char_p(nspace)))
+                      (self.io, cstr(nspace)))
         self.nspace = nspace
 
     def get_namespace(self):
@@ -1485,7 +1516,7 @@ class Ioctx(object):
             self.state = "closed"
 
 
-    @requires(('key', str), ('data', str))
+    @requires(('key', str), ('data', bytes))
     def write(self, key, data, offset=0):
         """
         Write data to an object synchronously
@@ -1493,7 +1524,7 @@ class Ioctx(object):
         :param key: name of the object
         :type key: str
         :param data: data to write
-        :type data: str
+        :type data: bytes
         :param offset: byte offset in the object to begin writing at
         :type offset: int
 
@@ -1504,7 +1535,7 @@ class Ioctx(object):
         self.require_ioctx_open()
         length = len(data)
         ret = run_in_thread(self.librados.rados_write,
-                            (self.io, c_char_p(key), c_char_p(data),
+                            (self.io, cstr(key), c_char_p(data),
                              c_size_t(length), c_uint64(offset)))
         if ret == 0:
             return ret
@@ -1515,7 +1546,7 @@ class Ioctx(object):
             raise LogicError("Ioctx.write(%s): rados_write \
 returned %d, but should return zero on success." % (self.name, ret))
 
-    @requires(('key', str), ('data', str))
+    @requires(('key', str), ('data', bytes))
     def write_full(self, key, data):
         """
         Write an entire object synchronously.
@@ -1526,7 +1557,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         :param key: name of the object
         :type key: str
         :param data: data to write
-        :type data: str
+        :type data: bytes
 
         :raises: :class:`TypeError`
         :raises: :class:`Error`
@@ -1535,7 +1566,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         self.require_ioctx_open()
         length = len(data)
         ret = run_in_thread(self.librados.rados_write_full,
-                            (self.io, c_char_p(key), c_char_p(data),
+                            (self.io, cstr(key), c_char_p(data),
                              c_size_t(length)))
         if ret == 0:
             return ret
@@ -1546,7 +1577,7 @@ returned %d, but should return zero on success." % (self.name, ret))
             raise LogicError("Ioctx.write_full(%s): rados_write_full \
 returned %d, but should return zero on success." % (self.name, ret))
 
-    @requires(('key', str), ('data', str))
+    @requires(('key', str), ('data', bytes))
     def append(self, key, data):
         """
         Append data to an object synchronously
@@ -1554,7 +1585,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         :param key: name of the object
         :type key: str
         :param data: data to write
-        :type data: str
+        :type data: bytes
 
         :raises: :class:`TypeError`
         :raises: :class:`LogicError`
@@ -1563,7 +1594,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         self.require_ioctx_open()
         length = len(data)
         ret = run_in_thread(self.librados.rados_append,
-                            (self.io, c_char_p(key), c_char_p(data),
+                            (self.io, cstr(key), c_char_p(data),
                              c_size_t(length)))
         if ret == 0:
             return ret
@@ -1593,7 +1624,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         self.require_ioctx_open()
         ret_buf = create_string_buffer(length)
         ret = run_in_thread(self.librados.rados_read,
-                            (self.io, c_char_p(key), ret_buf, c_size_t(length),
+                            (self.io, cstr(key), ret_buf, c_size_t(length),
                              c_uint64(offset)))
         if ret < 0:
             raise make_ex(ret, "Ioctx.read(%s): failed to read %s" % (self.name, key))
@@ -1665,7 +1696,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         """
         self.require_ioctx_open()
         ret = run_in_thread(self.librados.rados_remove,
-                            (self.io, c_char_p(key)))
+                            (self.io, cstr(key)))
         if ret < 0:
             raise make_ex(ret, "Failed to remove '%s'" % key)
         return True
@@ -1690,7 +1721,7 @@ returned %d, but should return zero on success." % (self.name, ret))
 
         self.require_ioctx_open()
         ret = run_in_thread(self.librados.rados_trunc,
-                            (self.io, c_char_p(key), c_uint64(size)))
+                            (self.io, cstr(key), c_uint64(size)))
         if ret < 0:
             raise make_ex(ret, "Ioctx.trunc(%s): failed to truncate %s" % (self.name, key))
         return ret
@@ -1712,7 +1743,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         pmtime = c_uint64()
 
         ret = run_in_thread(self.librados.rados_stat,
-                            (self.io, c_char_p(key), pointer(psize),
+                            (self.io, cstr(key), pointer(psize),
                              pointer(pmtime)))
         if ret < 0:
             raise make_ex(ret, "Failed to stat %r" % key)
@@ -1737,7 +1768,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         while ret_length < 4096 * 1024 * 1024:
             ret_buf = create_string_buffer(ret_length)
             ret = run_in_thread(self.librados.rados_getxattr,
-                                (self.io, c_char_p(key), c_char_p(xattr_name),
+                                (self.io, cstr(key), cstr(xattr_name),
                                  ret_buf, c_size_t(ret_length)))
             if (ret == -errno.ERANGE):
                 ret_length *= 2
@@ -1753,7 +1784,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         Start iterating over xattrs on an object.
 
         :param oid: the name of the object to get xattrs from
-        :type key: str
+        :type oid: str
 
         :raises: :class:`TypeError`
         :raises: :class:`Error`
@@ -1762,12 +1793,12 @@ returned %d, but should return zero on success." % (self.name, ret))
         self.require_ioctx_open()
         it = c_void_p(0)
         ret = run_in_thread(self.librados.rados_getxattrs,
-                            (self.io, oid, byref(it)))
+                            (self.io, cstr(oid), byref(it)))
         if ret != 0:
             raise make_ex(ret, "Failed to get rados xattrs for object %r" % oid)
         return XattrIterator(self, it, oid)
 
-    @requires(('key', str), ('xattr_name', str), ('xattr_value', str))
+    @requires(('key', str), ('xattr_name', str), ('xattr_value', bytes))
     def set_xattr(self, key, xattr_name, xattr_value):
         """
         Set an extended attribute on an object.
@@ -1777,7 +1808,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         :param xattr_name: which extended attribute to set
         :type xattr_name: str
         :param xattr_value: the value of the  extended attribute
-        :type xattr_value: str
+        :type xattr_value: bytes
 
         :raises: :class:`TypeError`
         :raises: :class:`Error`
@@ -1785,7 +1816,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         """
         self.require_ioctx_open()
         ret = run_in_thread(self.librados.rados_setxattr,
-                            (self.io, c_char_p(key), c_char_p(xattr_name),
+                            (self.io, cstr(key), cstr(xattr_name),
                              c_char_p(xattr_value), c_size_t(len(xattr_value))))
         if ret < 0:
             raise make_ex(ret, "Failed to set xattr %r" % xattr_name)
@@ -1807,7 +1838,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         """
         self.require_ioctx_open()
         ret = run_in_thread(self.librados.rados_rmxattr,
-                            (self.io, c_char_p(key), c_char_p(xattr_name)))
+                            (self.io, cstr(key), cstr(xattr_name)))
         if ret < 0:
             raise make_ex(ret, "Failed to delete key %r xattr %r" %
                           (key, xattr_name))
@@ -1844,7 +1875,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         """
         self.require_ioctx_open()
         ret = run_in_thread(self.librados.rados_ioctx_snap_create,
-                            (self.io, c_char_p(snap_name)))
+                            (self.io, cstr(snap_name)))
         if (ret != 0):
             raise make_ex(ret, "Failed to create snap %s" % snap_name)
 
@@ -1861,7 +1892,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         """
         self.require_ioctx_open()
         ret = run_in_thread(self.librados.rados_ioctx_snap_remove,
-                            (self.io, c_char_p(snap_name)))
+                            (self.io, cstr(snap_name)))
         if (ret != 0):
             raise make_ex(ret, "Failed to remove snap %s" % snap_name)
 
@@ -1880,7 +1911,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         self.require_ioctx_open()
         snap_id = c_uint64()
         ret = run_in_thread(self.librados.rados_ioctx_snap_lookup,
-                            (self.io, c_char_p(snap_name), byref(snap_id)))
+                            (self.io, cstr(snap_name), byref(snap_id)))
         if (ret != 0):
             raise make_ex(ret, "Failed to lookup snap %s" % snap_name)
         return Snap(self, snap_name, snap_id)
@@ -1945,7 +1976,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         key_num = len(keys)
         key_array_type = c_char_p*key_num
         key_array = key_array_type()
-        key_array[:] = keys
+        key_array[:] = [cstr(key) for key in keys]
 
         value_array_type = c_char_p*key_num
         value_array = value_array_type()
@@ -1974,7 +2005,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         :type flags: int
         """
         run_in_thread(self.librados.rados_write_op_operate,
-                      (c_void_p(write_op), self.io, c_char_p(oid),
+                      (c_void_p(write_op), self.io, cstr(oid),
                        c_long(mtime), c_int(flags),))
 
     @requires(('read_op', int), ('oid', str), ('flag', opt(int)))
@@ -1989,7 +2020,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         :type flag: int
         """
         run_in_thread(self.librados.rados_read_op_operate,
-                      (c_void_p(read_op), self.io, c_char_p(oid), c_int(flag),))
+                      (c_void_p(read_op), self.io, cstr(oid), c_int(flag),))
 
     @requires(('read_op', int), ('start_after', str), ('filter_prefix', str), ('max_return', int))
     def get_omap_vals(self, read_op, start_after, filter_prefix, max_return):
@@ -2008,8 +2039,8 @@ returned %d, but should return zero on success." % (self.name, ret))
         prval = c_int()
         iter_addr = c_void_p()
         run_in_thread(self.librados.rados_read_op_omap_get_vals,
-                      (c_void_p(read_op), c_char_p(start_after),
-                       c_char_p(filter_prefix), c_int(max_return),
+                      (c_void_p(read_op), cstr(start_after),
+                       cstr(filter_prefix), c_int(max_return),
                        byref(iter_addr), pointer(prval)))
         return OmapIterator(self, iter_addr), prval.value
 
@@ -2028,7 +2059,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         prval = c_int()
         iter_addr = c_void_p()
         run_in_thread(self.librados.rados_read_op_omap_get_keys,
-                      (c_void_p(read_op), c_char_p(start_after),
+                      (c_void_p(read_op), cstr(start_after),
                        c_int(max_return), byref(iter_addr), pointer(prval)))
         return OmapIterator(self, iter_addr), prval.value
 
@@ -2047,7 +2078,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         key_num = len(keys)
         key_array_type = c_char_p*key_num
         key_array = key_array_type()
-        key_array[:] = keys
+        key_array[:] = [cstr(key) for key in keys]
         run_in_thread(self.librados.rados_read_op_omap_get_vals_by_keys,
                       (c_void_p(read_op), byref(key_array), c_int(key_num),
                        byref(iter_addr), pointer(prval)))
@@ -2065,7 +2096,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         key_num = len(keys)
         key_array_type = c_char_p*key_num
         key_array = key_array_type()
-        key_array[:] = keys
+        key_array[:] = [cstr(key) for key in keys]
         run_in_thread(self.librados.rados_write_op_omap_rm_keys,
                       (c_void_p(write_op), byref(key_array), c_int(key_num)))
 
@@ -2105,8 +2136,8 @@ returned %d, but should return zero on success." % (self.name, ret))
         self.require_ioctx_open()
 
         ret = run_in_thread(self.librados.rados_lock_exclusive,
-                            (self.io, c_char_p(key), c_char_p(name), c_char_p(cookie),
-                             c_char_p(desc),
+                            (self.io, cstr(key), cstr(name), cstr(cookie),
+                             cstr(desc),
                              timeval(duration, None) if duration is None else None,
                              c_uint8(flags)))
         if ret < 0:
@@ -2140,8 +2171,8 @@ returned %d, but should return zero on success." % (self.name, ret))
         self.require_ioctx_open()
 
         ret = run_in_thread(self.librados.rados_lock_shared,
-                            (self.io, c_char_p(key), c_char_p(name), c_char_p(cookie),
-                             c_char_p(tag), c_char_p(desc),
+                            (self.io, cstr(key), cstr(name), cstr(cookie),
+                             cstr(tag), cstr(desc),
                              timeval(duration, None) if duration is None else None,
                              c_uint8(flags)))
         if ret < 0:
@@ -2166,7 +2197,7 @@ returned %d, but should return zero on success." % (self.name, ret))
         self.require_ioctx_open()
 
         ret = run_in_thread(self.librados.rados_unlock,
-                            (self.io, c_char_p(key), c_char_p(name), c_char_p(cookie)))
+                            (self.io, cstr(key), cstr(name), cstr(cookie)))
         if ret < 0:
             raise make_ex(ret, "Ioctx.rados_lock_exclusive(%s): failed to set lock %s on %s" % (self.name, name, key))
 
index b570a00ebd636880311109c2580b49718faf2031..b04e8453f2576ceb724b046ca47f15b09aa90c9d 100644 (file)
@@ -15,6 +15,7 @@ to interact correctly with librbd. If unicode is passed to these
 methods, a :class:`TypeError` will be raised.
 """
 # Copyright 2011 Josh Durgin
+from collections import Iterable
 from ctypes import CDLL, c_char, c_char_p, c_size_t, c_void_p, c_int, \
     create_string_buffer, byref, Structure, c_uint64, c_int64, c_uint8, \
     CFUNCTYPE
@@ -194,6 +195,19 @@ def load_librbd():
         raise EnvironmentError("Unable to load librbd: %s" % e)
 
 
+def cstr(val, encoding="utf-8"):
+    """
+    Create a C-style string from a Python string
+
+    :param str val: Python string
+    :rtype: c_char_p
+    """
+    if val is None:
+        return c_char_p(None)
+
+    return c_char_p(val.encode(encoding))
+
+
 class RBD(object):
     """
     This class wraps librbd CRUD functions.
@@ -250,7 +264,7 @@ class RBD(object):
             if features != 0 or stripe_unit != 0 or stripe_count != 0:
                 raise InvalidArgument('format 1 images do not support feature'
                                       ' masks or non-default striping')
-            ret = self.librbd.rbd_create(ioctx.io, c_char_p(name),
+            ret = self.librbd.rbd_create(ioctx.io, cstr(name),
                                          c_uint64(size),
                                          byref(c_int(order)))
         else:
@@ -262,14 +276,14 @@ class RBD(object):
                 raise FunctionNotSupported('installed version of librbd does'
                                            ' not support stripe unit or count')
             if has_create3:
-                ret = self.librbd.rbd_create3(ioctx.io, c_char_p(name),
+                ret = self.librbd.rbd_create3(ioctx.io, cstr(name),
                                               c_uint64(size),
                                               c_uint64(features),
                                               byref(c_int(order)),
                                               c_uint64(stripe_unit),
                                               c_uint64(stripe_count))
             else:
-                ret = self.librbd.rbd_create2(ioctx.io, c_char_p(name),
+                ret = self.librbd.rbd_create2(ioctx.io, cstr(name),
                                               c_uint64(size),
                                               c_uint64(features),
                                               byref(c_int(order)))
@@ -308,9 +322,9 @@ class RBD(object):
         if not isinstance(c_name, str):
             raise TypeError('child name must be a string')
 
-        ret = self.librbd.rbd_clone(p_ioctx.io, c_char_p(p_name),
-                                    c_char_p(p_snapname),
-                                    c_ioctx.io, c_char_p(c_name),
+        ret = self.librbd.rbd_clone(p_ioctx.io, cstr(p_name),
+                                    cstr(p_snapname),
+                                    c_ioctx.io, cstr(c_name),
                                     c_uint64(features),
                                     byref(c_int(order)))
         if ret < 0:
@@ -332,7 +346,8 @@ class RBD(object):
                 break
             elif ret != -errno.ERANGE:
                 raise make_ex(ret, 'error listing images')
-        return filter(lambda name: name != '', c_names.raw.split('\0'))
+
+        return [name for name in c_names.raw.decode("utf-8").split('\0') if len(name) > 0]
 
     def remove(self, ioctx, name):
         """
@@ -353,7 +368,7 @@ class RBD(object):
         """
         if not isinstance(name, str):
             raise TypeError('name must be a string')
-        ret = self.librbd.rbd_remove(ioctx.io, c_char_p(name))
+        ret = self.librbd.rbd_remove(ioctx.io, cstr(name))
         if ret != 0:
             raise make_ex(ret, 'error removing image')
 
@@ -371,7 +386,7 @@ class RBD(object):
         """
         if not isinstance(src, str) or not isinstance(dest, str):
             raise TypeError('src and dest must be strings')
-        ret = self.librbd.rbd_rename(ioctx.io, c_char_p(src), c_char_p(dest))
+        ret = self.librbd.rbd_rename(ioctx.io, cstr(src), cstr(dest))
         if ret != 0:
             raise make_ex(ret, 'error renaming image')
 
@@ -420,12 +435,12 @@ class Image(object):
             if not hasattr(self.librbd, 'rbd_open_read_only'):
                 raise FunctionNotSupported('installed version of librbd does '
                                            'not support open in read-only mode')
-            ret = self.librbd.rbd_open_read_only(ioctx.io, c_char_p(name),
+            ret = self.librbd.rbd_open_read_only(ioctx.io, cstr(name),
                                                  byref(self.image),
-                                                 c_char_p(snapshot))
+                                                 cstr(snapshot))
         else:
-            ret = self.librbd.rbd_open(ioctx.io, c_char_p(name),
-                                       byref(self.image), c_char_p(snapshot))
+            ret = self.librbd.rbd_open(ioctx.io, cstr(name),
+                                       byref(self.image), cstr(snapshot))
         if ret != 0:
             raise make_ex(ret, 'error opening image %s at snapshot %s' % (name, snapshot))
         self.closed = False
@@ -644,7 +659,7 @@ class Image(object):
         """
         if not isinstance(dest_name, str):
             raise TypeError('dest_name must be a string')
-        ret = self.librbd.rbd_copy(self.image, dest_ioctx.io, c_char_p(dest_name))
+        ret = self.librbd.rbd_copy(self.image, dest_ioctx.io, cstr(dest_name))
         if ret < 0:
             raise make_ex(ret, 'error copying image %s to %s' % (self.name, dest_name))
 
@@ -666,7 +681,7 @@ class Image(object):
         """
         if not isinstance(name, str):
             raise TypeError('name must be a string')
-        ret = self.librbd.rbd_snap_create(self.image, c_char_p(name))
+        ret = self.librbd.rbd_snap_create(self.image, cstr(name))
         if ret != 0:
             raise make_ex(ret, 'error creating snapshot %s from %s' % (name, self.name))
 
@@ -680,7 +695,7 @@ class Image(object):
         """
         if not isinstance(name, str):
             raise TypeError('name must be a string')
-        ret = self.librbd.rbd_snap_remove(self.image, c_char_p(name))
+        ret = self.librbd.rbd_snap_remove(self.image, cstr(name))
         if ret != 0:
             raise make_ex(ret, 'error removing snapshot %s from %s' % (name, self.name))
 
@@ -696,7 +711,7 @@ class Image(object):
         """
         if not isinstance(name, str):
             raise TypeError('name must be a string')
-        ret = self.librbd.rbd_snap_rollback(self.image, c_char_p(name))
+        ret = self.librbd.rbd_snap_rollback(self.image, cstr(name))
         if ret != 0:
             raise make_ex(ret, 'error rolling back image %s to snapshot %s' % (self.name, name))
 
@@ -711,7 +726,7 @@ class Image(object):
         """
         if not isinstance(name, str):
             raise TypeError('name must be a string')
-        ret = self.librbd.rbd_snap_protect(self.image, c_char_p(name))
+        ret = self.librbd.rbd_snap_protect(self.image, cstr(name))
         if ret != 0:
             raise make_ex(ret, 'error protecting snapshot %s@%s' % (self.name, name))
 
@@ -726,7 +741,7 @@ class Image(object):
         """
         if not isinstance(name, str):
             raise TypeError('name must be a string')
-        ret = self.librbd.rbd_snap_unprotect(self.image, c_char_p(name))
+        ret = self.librbd.rbd_snap_unprotect(self.image, cstr(name))
         if ret != 0:
             raise make_ex(ret, 'error unprotecting snapshot %s@%s' % (self.name, name))
 
@@ -742,7 +757,7 @@ class Image(object):
         if not isinstance(name, str):
             raise TypeError('name must be a string')
         is_protected = c_int()
-        ret = self.librbd.rbd_snap_is_protected(self.image, c_char_p(name),
+        ret = self.librbd.rbd_snap_is_protected(self.image, cstr(name),
                                                 byref(is_protected))
         if ret != 0:
             raise make_ex(ret, 'error checking if snapshot %s@%s is protected' % (self.name, name))
@@ -759,7 +774,7 @@ class Image(object):
         """
         if name is not None and not isinstance(name, str):
             raise TypeError('name must be a string')
-        ret = self.librbd.rbd_snap_set(self.image, c_char_p(name))
+        ret = self.librbd.rbd_snap_set(self.image, cstr(name))
         if ret != 0:
             raise make_ex(ret, 'error setting image %s to snapshot %s' % (self.name, name))
 
@@ -838,7 +853,7 @@ class Image(object):
         cb_holder = DiffIterateCB(iterate_cb)
         cb = RBD_DIFF_CB(cb_holder.callback)
         ret = self.librbd.rbd_diff_iterate2(self.image,
-                                            c_char_p(from_snapshot),
+                                            cstr(from_snapshot),
                                             c_uint64(offset),
                                             c_uint64(length),
                                             c_uint8(include_parent),
@@ -855,7 +870,7 @@ class Image(object):
         part of the write would fall outside the image.
 
         :param data: the data to be written
-        :type data: str
+        :type data: bytes
         :param offset: where to start writing data
         :type offset: int
         :param fadvise_flags: fadvise flags for this write
@@ -864,8 +879,8 @@ class Image(object):
         :raises: :class:`IncompleteWriteError`, :class:`LogicError`,
                  :class:`InvalidArgument`, :class:`IOError`
         """
-        if not isinstance(data, str):
-            raise TypeError('data must be a string')
+        if not isinstance(data, bytes):
+            raise TypeError('data must be a byte string')
         length = len(data)
 
         if fadvise_flags == 0:
@@ -967,7 +982,7 @@ written." % (self.name, ret, length))
             return []
         pools = c_pools.raw[:pools_size.value - 1].split('\0')
         images = c_images.raw[:images_size.value - 1].split('\0')
-        return zip(pools, images)
+        return list(zip(pools, images))
 
     def list_lockers(self):
         """
@@ -1010,13 +1025,13 @@ written." % (self.name, ret, length))
                 raise make_ex(ret, 'error listing images')
         if ret == 0:
             return []
-        clients = c_clients.raw[:clients_size.value - 1].split('\0')
-        cookies = c_cookies.raw[:cookies_size.value - 1].split('\0')
-        addrs = c_addrs.raw[:addrs_size.value - 1].split('\0')
+        clients = [client.decode("utf-8") for client in c_clients.raw[:clients_size.value - 1].split(b'\0')]
+        cookies = [cookie.decode("utf-8") for cookie in c_cookies.raw[:cookies_size.value - 1].split(b'\0')]
+        addrs = [addr.decode("utf-8") for addr in c_addrs.raw[:addrs_size.value - 1].split(b'\0')]
         return {
-            'tag'       : c_tag.value,
+            'tag'       : c_tag.value.decode("utf-8"),
             'exclusive' : exclusive.value == 1,
-            'lockers'   : zip(clients, cookies, addrs),
+            'lockers'   : list(zip(clients, cookies, addrs)),
             }
 
     def lock_exclusive(self, cookie):
@@ -1028,7 +1043,7 @@ written." % (self.name, ret, length))
         """
         if not isinstance(cookie, str):
             raise TypeError('cookie must be a string')
-        ret = self.librbd.rbd_lock_exclusive(self.image, c_char_p(cookie))
+        ret = self.librbd.rbd_lock_exclusive(self.image, cstr(cookie))
         if ret < 0:
             raise make_ex(ret, 'error acquiring exclusive lock on image')
 
@@ -1044,8 +1059,8 @@ written." % (self.name, ret, length))
             raise TypeError('cookie must be a string')
         if not isinstance(tag, str):
             raise TypeError('tag must be a string')
-        ret = self.librbd.rbd_lock_shared(self.image, c_char_p(cookie),
-                                          c_char_p(tag))
+        ret = self.librbd.rbd_lock_shared(self.image, cstr(cookie),
+                                          cstr(tag))
         if ret < 0:
             raise make_ex(ret, 'error acquiring shared lock on image')
 
@@ -1055,7 +1070,7 @@ written." % (self.name, ret, length))
         """
         if not isinstance(cookie, str):
             raise TypeError('cookie must be a string')
-        ret = self.librbd.rbd_unlock(self.image, c_char_p(cookie))
+        ret = self.librbd.rbd_unlock(self.image, cstr(cookie))
         if ret < 0:
             raise make_ex(ret, 'error unlocking image')
 
@@ -1067,8 +1082,8 @@ written." % (self.name, ret, length))
             raise TypeError('client must be a string')
         if not isinstance(cookie, str):
             raise TypeError('cookie must be a string')
-        ret = self.librbd.rbd_break_lock(self.image, c_char_p(client),
-                                         c_char_p(cookie))
+        ret = self.librbd.rbd_break_lock(self.image, cstr(client),
+                                         cstr(cookie))
         if ret < 0:
             raise make_ex(ret, 'error unlocking image')
 
@@ -1082,7 +1097,7 @@ class DiffIterateCB(object):
         return 0
 
 
-class SnapIterator(object):
+class SnapIterator(Iterable):
     """
     Iterator over snapshot info for an image.
 
@@ -1110,11 +1125,11 @@ class SnapIterator(object):
                 raise make_ex(ret, 'error listing snapshots for image %s' % (image.name,))
 
     def __iter__(self):
-        for i in xrange(self.num_snaps):
+        for i in range(self.num_snaps):
             yield {
                 'id'   : self.snaps[i].id,
                 'size' : self.snaps[i].size,
-                'name' : self.snaps[i].name,
+                'name' : self.snaps[i].name.decode("utf-8"),
                 }
 
     def __del__(self):
index d05c25bd864f6fb3a11a3eb8b7d26b35bd4f39bb..11457a7bc3bd01d7eae7cd41b0b624c0d205e015 100644 (file)
@@ -1,3 +1,4 @@
+from __future__ import print_function
 from nose.tools import eq_ as eq, ok_ as ok, assert_raises
 from rados import (Rados, Error, RadosStateError, Object, ObjectExists,
                    ObjectNotFound, ObjectBusy, requires, opt,
@@ -13,16 +14,6 @@ def test_rados_init_error():
     assert_raises(Error, Rados, conffile='', name='invalid')
     assert_raises(Error, Rados, conffile='', name='bad.invalid')
 
-def test_rados_init_type_error():
-    assert_raises(TypeError, Rados, rados_id=u'admin')
-    assert_raises(TypeError, Rados, rados_id=u'')
-    assert_raises(TypeError, Rados, name=u'client.admin')
-    assert_raises(TypeError, Rados, name=u'')
-    assert_raises(TypeError, Rados, conffile=u'blah')
-    assert_raises(TypeError, Rados, conffile=u'')
-    assert_raises(TypeError, Rados, clusternaem=u'blah')
-    assert_raises(TypeError, Rados, clustername=u'')
-
 def test_rados_init():
     with Rados(conffile='', rados_id='admin'):
         pass
@@ -91,9 +82,9 @@ class TestRadosStateError(object):
         assert_raises(RadosStateError, rados.list_pools)
         assert_raises(RadosStateError, rados.get_fsid)
         assert_raises(RadosStateError, rados.open_ioctx, 'foo')
-        assert_raises(RadosStateError, rados.mon_command, '', '')
-        assert_raises(RadosStateError, rados.osd_command, 0, '', '')
-        assert_raises(RadosStateError, rados.pg_command, '', '', '')
+        assert_raises(RadosStateError, rados.mon_command, '', b'')
+        assert_raises(RadosStateError, rados.osd_command, 0, '', b'')
+        assert_raises(RadosStateError, rados.pg_command, '', '', b'')
         assert_raises(RadosStateError, rados.wait_for_latest_osdmap)
         assert_raises(RadosStateError, rados.blacklist_add, '127.0.0.1/123', 0)
 
@@ -179,12 +170,12 @@ class TestRados(object):
                 tier_pool_id = self.rados.pool_lookup('foo-cache')
 
                 cmd = {"prefix":"osd tier add", "pool":"foo", "tierpool":"foo-cache", "force_nonempty":""}
-                ret, buf, errs = self.rados.mon_command(json.dumps(cmd), '', timeout=30)
+                ret, buf, errs = self.rados.mon_command(json.dumps(cmd), b'', timeout=30)
                 eq(ret, 0)
 
                 try:
                     cmd = {"prefix":"osd tier cache-mode", "pool":"foo-cache", "tierpool":"foo-cache", "mode":"readonly"}
-                    ret, buf, errs = self.rados.mon_command(json.dumps(cmd), '', timeout=30)
+                    ret, buf, errs = self.rados.mon_command(json.dumps(cmd), b'', timeout=30)
                     eq(ret, 0)
 
                     eq(self.rados.wait_for_latest_osdmap(), 0)
@@ -193,7 +184,7 @@ class TestRados(object):
                     eq(pool_id, self.rados.get_pool_base_tier(tier_pool_id))
                 finally:
                     cmd = {"prefix":"osd tier remove", "pool":"foo", "tierpool":"foo-cache"}
-                    ret, buf, errs = self.rados.mon_command(json.dumps(cmd), '', timeout=30)
+                    ret, buf, errs = self.rados.mon_command(json.dumps(cmd), b'', timeout=30)
                     eq(ret, 0)
             finally:
                 self.rados.delete_pool('foo-cache')
@@ -218,7 +209,7 @@ class TestIoctx(object):
 
     def tearDown(self):
         cmd = {"prefix":"osd unset", "key":"noup"}
-        self.rados.mon_command(json.dumps(cmd), '')
+        self.rados.mon_command(json.dumps(cmd), b'')
         self.ioctx.close()
         self.rados.delete_pool('test_pool')
         self.rados.shutdown()
@@ -228,29 +219,29 @@ class TestIoctx(object):
         self.ioctx.change_auid(ADMIN_AUID)
 
     def test_write(self):
-        self.ioctx.write('abc', 'abc')
-        eq(self.ioctx.read('abc'), 'abc')
+        self.ioctx.write('abc', b'abc')
+        eq(self.ioctx.read('abc'), b'abc')
 
     def test_write_full(self):
-        self.ioctx.write('abc', 'abc')
-        eq(self.ioctx.read('abc'), 'abc')
-        self.ioctx.write_full('abc', 'd')
-        eq(self.ioctx.read('abc'), 'd')
+        self.ioctx.write('abc', b'abc')
+        eq(self.ioctx.read('abc'), b'abc')
+        self.ioctx.write_full('abc', b'd')
+        eq(self.ioctx.read('abc'), b'd')
 
     def test_append(self):
-        self.ioctx.write('abc', 'a')
-        self.ioctx.append('abc', 'b')
-        self.ioctx.append('abc', 'c')
-        eq(self.ioctx.read('abc'), 'abc')
+        self.ioctx.write('abc', b'a')
+        self.ioctx.append('abc', b'b')
+        self.ioctx.append('abc', b'c')
+        eq(self.ioctx.read('abc'), b'abc')
 
     def test_write_zeros(self):
-        self.ioctx.write('abc', 'a\0b\0c')
-        eq(self.ioctx.read('abc'), 'a\0b\0c')
+        self.ioctx.write('abc', b'a\0b\0c')
+        eq(self.ioctx.read('abc'), b'a\0b\0c')
 
     def test_trunc(self):
-        self.ioctx.write('abc', 'abc')
+        self.ioctx.write('abc', b'abc')
         self.ioctx.trunc('abc', 2)
-        eq(self.ioctx.read('abc'), 'ab')
+        eq(self.ioctx.read('abc'), b'ab')
         size = self.ioctx.stat('abc')[0]
         eq(size, 2)
 
@@ -258,24 +249,24 @@ class TestIoctx(object):
         eq(list(self.ioctx.list_objects()), [])
 
     def test_list_objects(self):
-        self.ioctx.write('a', '')
-        self.ioctx.write('b', 'foo')
-        self.ioctx.write_full('c', 'bar')
-        self.ioctx.append('d', 'jazz')
+        self.ioctx.write('a', b'')
+        self.ioctx.write('b', b'foo')
+        self.ioctx.write_full('c', b'bar')
+        self.ioctx.append('d', b'jazz')
         object_names = [obj.key for obj in self.ioctx.list_objects()]
         eq(sorted(object_names), ['a', 'b', 'c', 'd'])
 
     def test_list_ns_objects(self):
-        self.ioctx.write('a', '')
-        self.ioctx.write('b', 'foo')
-        self.ioctx.write_full('c', 'bar')
-        self.ioctx.append('d', 'jazz')
+        self.ioctx.write('a', b'')
+        self.ioctx.write('b', b'foo')
+        self.ioctx.write_full('c', b'bar')
+        self.ioctx.append('d', b'jazz')
         self.ioctx.set_namespace("ns1")
-        self.ioctx.write('ns1-a', '')
-        self.ioctx.write('ns1-b', 'foo')
-        self.ioctx.write_full('ns1-c', 'bar')
-        self.ioctx.append('ns1-d', 'jazz')
-        self.ioctx.append('d', 'jazz')
+        self.ioctx.write('ns1-a', b'')
+        self.ioctx.write('ns1-b', b'foo')
+        self.ioctx.write_full('ns1-c', b'bar')
+        self.ioctx.append('ns1-d', b'jazz')
+        self.ioctx.append('d', b'jazz')
         self.ioctx.set_namespace(LIBRADOS_ALL_NSPACES)
         object_names = [(obj.nspace, obj.key) for obj in self.ioctx.list_objects()]
         eq(sorted(object_names), [('', 'a'), ('','b'), ('','c'), ('','d'),\
@@ -283,9 +274,9 @@ class TestIoctx(object):
                 ('ns1', 'ns1-c'), ('ns1', 'ns1-d')])
 
     def test_xattrs(self):
-        xattrs = dict(a='1', b='2', c='3', d='a\0b', e='\0')
-        self.ioctx.write('abc', '')
-        for key, value in xattrs.iteritems():
+        xattrs = dict(a=b'1', b=b'2', c=b'3', d=b'a\0b', e=b'\0')
+        self.ioctx.write('abc', b'')
+        for key, value in xattrs.items():
             self.ioctx.set_xattr('abc', key, value)
             eq(self.ioctx.get_xattr('abc', key), value)
         stored_xattrs = {}
@@ -294,10 +285,10 @@ class TestIoctx(object):
         eq(stored_xattrs, xattrs)
 
     def test_obj_xattrs(self):
-        xattrs = dict(a='1', b='2', c='3', d='a\0b', e='\0')
-        self.ioctx.write('abc', '')
+        xattrs = dict(a=b'1', b=b'2', c=b'3', d=b'a\0b', e=b'\0')
+        self.ioctx.write('abc', b'')
         obj = list(self.ioctx.list_objects())[0]
-        for key, value in xattrs.iteritems():
+        for key, value in xattrs.items():
             obj.set_xattr(key, value)
             eq(obj.get_xattr(key), value)
         stored_xattrs = {}
@@ -339,7 +330,7 @@ class TestIoctx(object):
 
     def test_set_omap(self):
         keys = ("1", "2", "3", "4")
-        values = ("aaa", "bbb", "ccc", "\x04\x04\x04\x04")
+        values = (b"aaa", b"bbb", b"ccc", b"\x04\x04\x04\x04")
         with WriteOpCtx(self.ioctx) as write_op:
             self.ioctx.set_omap(write_op, keys, values)
             self.ioctx.operate_write_op(write_op, "hw")
@@ -347,22 +338,22 @@ class TestIoctx(object):
             iter, ret = self.ioctx.get_omap_vals(read_op, "", "", 4)
             self.ioctx.operate_read_op(read_op, "hw")
             iter.next()
-            eq(list(iter), [("2", "bbb"), ("3", "ccc"), ("4", "\x04\x04\x04\x04")])
+            eq(list(iter), [("2", b"bbb"), ("3", b"ccc"), ("4", b"\x04\x04\x04\x04")])
 
     def test_get_omap_vals_by_keys(self):
         keys = ("1", "2", "3", "4")
-        values = ("aaa", "bbb", "ccc", "\x04\x04\x04\x04")
+        values = (b"aaa", b"bbb", b"ccc", b"\x04\x04\x04\x04")
         with WriteOpCtx(self.ioctx) as write_op:
             self.ioctx.set_omap(write_op, keys, values)
             self.ioctx.operate_write_op(write_op, "hw")
         with ReadOpCtx(self.ioctx) as read_op:
             iter, ret = self.ioctx.get_omap_vals_by_keys(read_op,("3","4",))
             self.ioctx.operate_read_op(read_op, "hw")
-            eq(list(iter), [("3", "ccc"), ("4", "\x04\x04\x04\x04")])
+            eq(list(iter), [("3", b"ccc"), ("4", b"\x04\x04\x04\x04")])
 
     def test_get_omap_keys(self):
         keys = ("1", "2", "3")
-        values = ("aaa", "bbb", "ccc")
+        values = (b"aaa", b"bbb", b"ccc")
         with WriteOpCtx(self.ioctx) as write_op:
             self.ioctx.set_omap(write_op, keys, values)
             self.ioctx.operate_write_op(write_op, "hw")
@@ -373,7 +364,7 @@ class TestIoctx(object):
 
     def test_clear_omap(self):
         keys = ("1", "2", "3")
-        values = ("aaa", "bbb", "ccc")
+        values = (b"aaa", b"bbb", b"ccc")
         with WriteOpCtx(self.ioctx) as write_op:
             self.ioctx.set_omap(write_op, keys, values)
             self.ioctx.operate_write_op(write_op, "hw")
@@ -387,17 +378,17 @@ class TestIoctx(object):
 
     def test_locator(self):
         self.ioctx.set_locator_key("bar")
-        self.ioctx.write('foo', 'contents1')
+        self.ioctx.write('foo', b'contents1')
         objects = [i for i in self.ioctx.list_objects()]
         eq(len(objects), 1)
         eq(self.ioctx.get_locator_key(), "bar")
         self.ioctx.set_locator_key("")
         objects[0].seek(0)
-        objects[0].write("contents2")
+        objects[0].write(b"contents2")
         eq(self.ioctx.get_locator_key(), "")
         self.ioctx.set_locator_key("bar")
         contents = self.ioctx.read("foo")
-        eq(contents, "contents2")
+        eq(contents, b"contents2")
         eq(self.ioctx.get_locator_key(), "bar")
         objects[0].remove()
         objects = [i for i in self.ioctx.list_objects()]
@@ -412,7 +403,7 @@ class TestIoctx(object):
                 count[0] += 1
                 lock.notify()
             return 0
-        comp = self.ioctx.aio_write("foo", "bar", 0, cb, cb)
+        comp = self.ioctx.aio_write("foo", b"bar", 0, cb, cb)
         comp.wait_for_complete()
         comp.wait_for_safe()
         with lock:
@@ -420,7 +411,7 @@ class TestIoctx(object):
                 lock.wait()
         eq(comp.get_return_value(), 0)
         contents = self.ioctx.read("foo")
-        eq(contents, "bar")
+        eq(contents, b"bar")
         [i.remove() for i in self.ioctx.list_objects()]
 
     def test_aio_append(self):
@@ -431,11 +422,11 @@ class TestIoctx(object):
                 count[0] += 1
                 lock.notify()
             return 0
-        comp = self.ioctx.aio_write("foo", "bar", 0, cb, cb)
-        comp2 = self.ioctx.aio_append("foo", "baz", cb, cb)
+        comp = self.ioctx.aio_write("foo", b"bar", 0, cb, cb)
+        comp2 = self.ioctx.aio_append("foo", b"baz", cb, cb)
         comp.wait_for_complete()
         contents = self.ioctx.read("foo")
-        eq(contents, "barbaz")
+        eq(contents, b"barbaz")
         with lock:
             while count[0] < 4:
                 lock.wait()
@@ -451,8 +442,8 @@ class TestIoctx(object):
                 count[0] += 1
                 lock.notify()
             return 0
-        self.ioctx.aio_write("foo", "barbaz", 0, cb, cb)
-        comp = self.ioctx.aio_write_full("foo", "bar", cb, cb)
+        self.ioctx.aio_write("foo", b"barbaz", 0, cb, cb)
+        comp = self.ioctx.aio_write_full("foo", b"bar", cb, cb)
         comp.wait_for_complete()
         comp.wait_for_safe()
         with lock:
@@ -460,7 +451,7 @@ class TestIoctx(object):
                 lock.wait()
         eq(comp.get_return_value(), 0)
         contents = self.ioctx.read("foo")
-        eq(contents, "bar")
+        eq(contents, b"bar")
         [i.remove() for i in self.ioctx.list_objects()]
 
     def _take_down_acting_set(self, pool, objectname):
@@ -472,14 +463,14 @@ class TestIoctx(object):
             "object":objectname,
             "format":"json",
         }
-        r, jsonout, _ = self.rados.mon_command(json.dumps(cmd), '')
-        objmap = json.loads(jsonout)
+        r, jsonout, _ = self.rados.mon_command(json.dumps(cmd), b'')
+        objmap = json.loads(jsonout.decode("utf-8"))
         acting_set = objmap['acting']
         cmd = {"prefix":"osd set", "key":"noup"}
-        r, _, _ = self.rados.mon_command(json.dumps(cmd), '')
+        r, _, _ = self.rados.mon_command(json.dumps(cmd), b'')
         eq(r, 0)
         cmd = {"prefix":"osd down", "ids":[str(i) for i in acting_set]}
-        r, _, _ = self.rados.mon_command(json.dumps(cmd), '')
+        r, _, _ = self.rados.mon_command(json.dumps(cmd), b'')
         eq(r, 0)
 
         # wait for OSDs to acknowledge the down
@@ -487,7 +478,7 @@ class TestIoctx(object):
 
     def _let_osds_back_up(self):
         cmd = {"prefix":"osd unset", "key":"noup"}
-        r, _, _ = self.rados.mon_command(json.dumps(cmd), '')
+        r, _, _ = self.rados.mon_command(json.dumps(cmd), b'')
         eq(r, 0)
 
     def test_aio_read(self):
@@ -498,7 +489,7 @@ class TestIoctx(object):
             with lock:
                 retval[0] = buf
                 lock.notify()
-        payload = "bar\000frob"
+        payload = b"bar\000frob"
         self.ioctx.write("foo", payload)
 
         # test1: use wait_for_complete() and wait for cb by
@@ -570,7 +561,7 @@ class TestObject(object):
         self.rados.create_pool('test_pool')
         assert self.rados.pool_exists('test_pool')
         self.ioctx = self.rados.open_ioctx('test_pool')
-        self.ioctx.write('foo', 'bar')
+        self.ioctx.write('foo', b'bar')
         self.object = Object(self.ioctx, 'foo')
 
     def tearDown(self):
@@ -579,21 +570,21 @@ class TestObject(object):
         self.rados.shutdown()
 
     def test_read(self):
-        eq(self.object.read(3), 'bar')
-        eq(self.object.read(100), '')
+        eq(self.object.read(3), b'bar')
+        eq(self.object.read(100), b'')
 
     def test_seek(self):
-        self.object.write('blah')
+        self.object.write(b'blah')
         self.object.seek(0)
-        eq(self.object.read(4), 'blah')
+        eq(self.object.read(4), b'blah')
         self.object.seek(1)
-        eq(self.object.read(3), 'lah')
+        eq(self.object.read(3), b'lah')
 
     def test_write(self):
-        self.object.write('barbaz')
+        self.object.write(b'barbaz')
         self.object.seek(0)
-        eq(self.object.read(3), 'bar')
-        eq(self.object.read(3), 'baz')
+        eq(self.object.read(3), b'bar')
+        eq(self.object.read(3), b'baz')
 
 class TestCommand(object):
 
@@ -608,53 +599,53 @@ class TestCommand(object):
 
         # check for success and some plain output with epoch in it
         cmd = {"prefix":"mon dump"}
-        ret, buf, errs = self.rados.mon_command(json.dumps(cmd), '', timeout=30)
+        ret, buf, errs = self.rados.mon_command(json.dumps(cmd), b'', timeout=30)
         eq(ret, 0)
         assert len(buf) > 0
-        assert('epoch' in buf)
+        assert(b'epoch' in buf)
 
         # JSON, and grab current epoch
         cmd['format'] = 'json'
-        ret, buf, errs = self.rados.mon_command(json.dumps(cmd), '', timeout=30)
+        ret, buf, errs = self.rados.mon_command(json.dumps(cmd), b'', timeout=30)
         eq(ret, 0)
         assert len(buf) > 0
-        d = json.loads(buf)
+        d = json.loads(buf.decode("utf-8"))
         assert('epoch' in d)
         epoch = d['epoch']
 
         # assume epoch + 1000 does not exist; test for ENOENT
         cmd['epoch'] = epoch + 1000
-        ret, buf, errs = self.rados.mon_command(json.dumps(cmd), '', timeout=30)
+        ret, buf, errs = self.rados.mon_command(json.dumps(cmd), b'', timeout=30)
         eq(ret, -errno.ENOENT)
         eq(len(buf), 0)
         del cmd['epoch']
 
         # send to specific target by name
         target = d['mons'][0]['name']
-        print target
-        ret, buf, errs = self.rados.mon_command(json.dumps(cmd), '', timeout=30,
+        print(target)
+        ret, buf, errs = self.rados.mon_command(json.dumps(cmd), b'', timeout=30,
                                                 target=target)
         eq(ret, 0)
         assert len(buf) > 0
-        d = json.loads(buf)
+        d = json.loads(buf.decode("utf-8"))
         assert('epoch' in d)
 
         # and by rank
         target = d['mons'][0]['rank']
-        print target
-        ret, buf, errs = self.rados.mon_command(json.dumps(cmd), '', timeout=30,
+        print(target)
+        ret, buf, errs = self.rados.mon_command(json.dumps(cmd), b'', timeout=30,
                                                 target=target)
         eq(ret, 0)
         assert len(buf) > 0
-        d = json.loads(buf)
+        d = json.loads(buf.decode("utf-8"))
         assert('epoch' in d)
 
     def test_osd_bench(self):
         cmd = dict(prefix='bench', size=4096, count=8192)
-        ret, buf, err = self.rados.osd_command(0, json.dumps(cmd), '',
+        ret, buf, err = self.rados.osd_command(0, json.dumps(cmd), b'',
                                                timeout=30)
         eq(ret, 0)
         assert len(err) > 0
-        out = json.loads(err)
+        out = json.loads(err.decode("utf-8"))
         eq(out['blocksize'], cmd['size'])
         eq(out['bytes_written'], cmd['count'])
index a98b5dcea9f52d3a583995c78c8f2cb8b9fe264c..1498524ee9b632dda9d4f240355d5f3cdad0a203 100644 (file)
@@ -1,10 +1,9 @@
 # vim: expandtab smarttab shiftwidth=4 softtabstop=4
 import functools
 import socket
-import struct
 import os
+import time
 
-from contextlib import nested
 from nose import with_setup, SkipTest
 from nose.tools import eq_ as eq, assert_raises
 from rados import (Rados,
@@ -17,7 +16,6 @@ from rbd import (RBD, Image, ImageNotFound, InvalidArgument, ImageExists,
                  RBD_FEATURE_LAYERING, RBD_FEATURE_STRIPINGV2,
                  RBD_FEATURE_EXCLUSIVE_LOCK)
 
-
 rados = None
 ioctx = None
 features = None
@@ -166,7 +164,7 @@ def check_default_params(format, order=None, features=None, stripe_count=None,
         else:
             assert_raises(exception, RBD().create, ioctx, image_name, IMG_SIZE)
     finally:
-        for k, v in orig_vals.iteritems():
+        for k, v in orig_vals.items():
             rados.conf_set(k, v)
 
 def test_create_defaults():
@@ -240,13 +238,13 @@ def test_open_read_only():
             eq(data, read)
 
 def test_open_dne():
-    for i in xrange(100):
+    for i in range(100):
         image_name = get_temp_image_name()
         assert_raises(ImageNotFound, Image, ioctx, image_name + 'dne')
         assert_raises(ImageNotFound, Image, ioctx, image_name, 'snap')
 
 def test_open_readonly_dne():
-    for i in xrange(100):
+    for i in range(100):
         image_name = get_temp_image_name()
         assert_raises(ImageNotFound, Image, ioctx, image_name + 'dne',
                       read_only=True)
@@ -301,10 +299,10 @@ class TestImage(object):
         eq(features | RBD_FEATURE_EXCLUSIVE_LOCK, self.image.features())
 
     def test_invalidate_cache(self):
-        self.image.write('abc', 0)
-        eq('abc', self.image.read(0, 3))
+        self.image.write(b'abc', 0)
+        eq(b'abc', self.image.read(0, 3))
         self.image.invalidate_cache()
-        eq('abc', self.image.read(0, 3))
+        eq(b'abc', self.image.read(0, 3))
 
     def test_stat(self):
         info = self.image.stat()
@@ -325,13 +323,13 @@ class TestImage(object):
 
     def test_read(self):
         data = self.image.read(0, 20)
-        eq(data, '\0' * 20)
+        eq(data, b'\0' * 20)
 
     def test_read_with_fadvise_flags(self):
         data = self.image.read(0, 20, LIBRADOS_OP_FLAG_FADVISE_DONTNEED)
-        eq(data, '\0' * 20)
+        eq(data, b'\0' * 20)
         data = self.image.read(0, 20, LIBRADOS_OP_FLAG_FADVISE_RANDOM)
-        eq(data, '\0' * 20)
+        eq(data, b'\0' * 20)
 
     def test_large_write(self):
         data = rand_data(IMG_SIZE)
@@ -339,7 +337,7 @@ class TestImage(object):
 
     def test_large_read(self):
         data = self.image.read(0, IMG_SIZE)
-        eq(data, '\0' * IMG_SIZE)
+        eq(data, b'\0' * IMG_SIZE)
 
     def test_write_read(self):
         data = rand_data(256)
@@ -374,24 +372,24 @@ class TestImage(object):
         self.image.remove_snap('snap2')
 
     def test_resize_down(self):
-        new_size = IMG_SIZE / 2
+        new_size = IMG_SIZE // 2
         data = rand_data(256)
-        self.image.write(data, IMG_SIZE / 2);
+        self.image.write(data, IMG_SIZE // 2);
         self.image.resize(new_size)
         self.image.resize(IMG_SIZE)
-        read = self.image.read(IMG_SIZE / 2, 256)
-        eq('\0' * 256, read)
+        read = self.image.read(IMG_SIZE // 2, 256)
+        eq(b'\0' * 256, read)
 
     def test_resize_bytes(self):
-        new_size = IMG_SIZE / 2 - 5
+        new_size = IMG_SIZE // 2 - 5
         data = rand_data(256)
-        self.image.write(data, IMG_SIZE / 2 - 10);
+        self.image.write(data, IMG_SIZE // 2 - 10);
         self.image.resize(new_size)
         self.image.resize(IMG_SIZE)
-        read = self.image.read(IMG_SIZE / 2 - 10, 5)
+        read = self.image.read(IMG_SIZE // 2 - 10, 5)
         eq(data[:5], read)
-        read = self.image.read(IMG_SIZE / 2 - 5, 251)
-        eq('\0' * 251, read)
+        read = self.image.read(IMG_SIZE // 2 - 5, 251)
+        eq(b'\0' * 251, read)
 
     def test_copy(self):
         global ioctx
@@ -410,7 +408,7 @@ class TestImage(object):
         global ioctx
         self.image.create_snap('snap1')
         read = self.image.read(0, 256)
-        eq(read, '\0' * 256)
+        eq(read, b'\0' * 256)
         data = rand_data(256)
         self.image.write(data, 0)
         read = self.image.read(0, 256)
@@ -418,22 +416,22 @@ class TestImage(object):
         at_snapshot = Image(ioctx, image_name, 'snap1')
         snap_data = at_snapshot.read(0, 256)
         at_snapshot.close()
-        eq(snap_data, '\0' * 256)
+        eq(snap_data, b'\0' * 256)
         self.image.remove_snap('snap1')
 
     def test_list_snaps(self):
         eq([], list(self.image.list_snaps()))
         self.image.create_snap('snap1')
-        eq(['snap1'], map(lambda snap: snap['name'], self.image.list_snaps()))
+        eq(['snap1'], [snap['name'] for snap in self.image.list_snaps()])
         self.image.create_snap('snap2')
-        eq(['snap1', 'snap2'], map(lambda snap: snap['name'], self.image.list_snaps()))
+        eq(['snap1', 'snap2'], [snap['name'] for snap in self.image.list_snaps()])
         self.image.remove_snap('snap1')
         self.image.remove_snap('snap2')
 
     def test_remove_snap(self):
         eq([], list(self.image.list_snaps()))
         self.image.create_snap('snap1')
-        eq(['snap1'], map(lambda snap: snap['name'], self.image.list_snaps()))
+        eq(['snap1'], [snap['name'] for snap in self.image.list_snaps()])
         self.image.remove_snap('snap1')
         eq([], list(self.image.list_snaps()))
 
@@ -469,35 +467,35 @@ class TestImage(object):
         eq(read, data)
 
     def test_rollback_to_snap(self):
-        self.image.write('\0' * 256, 0)
+        self.image.write(b'\0' * 256, 0)
         self.image.create_snap('snap1')
         read = self.image.read(0, 256)
-        eq(read, '\0' * 256)
+        eq(read, b'\0' * 256)
         data = rand_data(256)
         self.image.write(data, 0)
         read = self.image.read(0, 256)
         eq(read, data)
         self.image.rollback_to_snap('snap1')
         read = self.image.read(0, 256)
-        eq(read, '\0' * 256)
+        eq(read, b'\0' * 256)
         self.image.remove_snap('snap1')
 
     def test_rollback_to_snap_sparse(self):
         self.image.create_snap('snap1')
         read = self.image.read(0, 256)
-        eq(read, '\0' * 256)
+        eq(read, b'\0' * 256)
         data = rand_data(256)
         self.image.write(data, 0)
         read = self.image.read(0, 256)
         eq(read, data)
         self.image.rollback_to_snap('snap1')
         read = self.image.read(0, 256)
-        eq(read, '\0' * 256)
+        eq(read, b'\0' * 256)
         self.image.remove_snap('snap1')
 
     def test_rollback_with_resize(self):
         read = self.image.read(0, 256)
-        eq(read, '\0' * 256)
+        eq(read, b'\0' * 256)
         data = rand_data(256)
         self.image.write(data, 0)
         self.image.create_snap('snap1')
@@ -521,31 +519,31 @@ class TestImage(object):
         self.image.remove_snap('snap2')
 
     def test_set_snap(self):
-        self.image.write('\0' * 256, 0)
+        self.image.write(b'\0' * 256, 0)
         self.image.create_snap('snap1')
         read = self.image.read(0, 256)
-        eq(read, '\0' * 256)
+        eq(read, b'\0' * 256)
         data = rand_data(256)
         self.image.write(data, 0)
         read = self.image.read(0, 256)
         eq(read, data)
         self.image.set_snap('snap1')
         read = self.image.read(0, 256)
-        eq(read, '\0' * 256)
+        eq(read, b'\0' * 256)
         self.image.remove_snap('snap1')
 
     def test_set_no_snap(self):
-        self.image.write('\0' * 256, 0)
+        self.image.write(b'\0' * 256, 0)
         self.image.create_snap('snap1')
         read = self.image.read(0, 256)
-        eq(read, '\0' * 256)
+        eq(read, b'\0' * 256)
         data = rand_data(256)
         self.image.write(data, 0)
         read = self.image.read(0, 256)
         eq(read, data)
         self.image.set_snap('snap1')
         read = self.image.read(0, 256)
-        eq(read, '\0' * 256)
+        eq(read, b'\0' * 256)
         self.image.set_snap(None)
         read = self.image.read(0, 256)
         eq(read, data)
@@ -554,19 +552,19 @@ class TestImage(object):
     def test_set_snap_sparse(self):
         self.image.create_snap('snap1')
         read = self.image.read(0, 256)
-        eq(read, '\0' * 256)
+        eq(read, b'\0' * 256)
         data = rand_data(256)
         self.image.write(data, 0)
         read = self.image.read(0, 256)
         eq(read, data)
         self.image.set_snap('snap1')
         read = self.image.read(0, 256)
-        eq(read, '\0' * 256)
+        eq(read, b'\0' * 256)
         self.image.remove_snap('snap1')
 
     def test_many_snaps(self):
         num_snaps = 200
-        for i in xrange(num_snaps):
+        for i in range(num_snaps):
             self.image.create_snap(str(i))
         snaps = sorted(self.image.list_snaps(),
                        key=lambda snap: int(snap['name']))
@@ -574,14 +572,14 @@ class TestImage(object):
         for i, snap in enumerate(snaps):
             eq(snap['size'], IMG_SIZE)
             eq(snap['name'], str(i))
-        for i in xrange(num_snaps):
+        for i in range(num_snaps):
             self.image.remove_snap(str(i))
 
     def test_set_snap_deleted(self):
-        self.image.write('\0' * 256, 0)
+        self.image.write(b'\0' * 256, 0)
         self.image.create_snap('snap1')
         read = self.image.read(0, 256)
-        eq(read, '\0' * 256)
+        eq(read, b'\0' * 256)
         data = rand_data(256)
         self.image.write(data, 0)
         read = self.image.read(0, 256)
@@ -594,10 +592,10 @@ class TestImage(object):
         eq(read, data)
 
     def test_set_snap_recreated(self):
-        self.image.write('\0' * 256, 0)
+        self.image.write(b'\0' * 256, 0)
         self.image.create_snap('snap1')
         read = self.image.read(0, 256)
-        eq(read, '\0' * 256)
+        eq(read, b'\0' * 256)
         data = rand_data(256)
         self.image.write(data, 0)
         read = self.image.read(0, 256)
@@ -633,14 +631,14 @@ class TestImage(object):
         eq([], self.image.list_lockers())
 
         num_shared = 10
-        for i in xrange(num_shared):
+        for i in range(num_shared):
             self.image.lock_shared(str(i), 'tag')
         lockers = self.image.list_lockers()
         eq('tag', lockers['tag'])
         assert not lockers['exclusive']
         eq(num_shared, len(lockers['lockers']))
         cookies = sorted(map(lambda x: x[1], lockers['lockers']))
-        for i in xrange(num_shared):
+        for i in range(num_shared):
             eq(str(i), cookies[i])
             self.image.unlock(str(i))
         eq([], self.image.list_lockers())
@@ -680,7 +678,7 @@ class TestClone(object):
         create_image()
         self.image = Image(ioctx, image_name)
         data = rand_data(256)
-        self.image.write(data, IMG_SIZE / 2)
+        self.image.write(data, IMG_SIZE // 2)
         self.image.create_snap('snap1')
         global features
         self.image.protect_snap('snap1')
@@ -758,52 +756,52 @@ class TestClone(object):
         eq(clone_info['size'], self.clone.overlap())
 
     def test_resize_stat(self):
-        self.clone.resize(IMG_SIZE / 2)
+        self.clone.resize(IMG_SIZE // 2)
         image_info = self.image.stat()
         clone_info = self.clone.stat()
-        eq(clone_info['size'], IMG_SIZE / 2)
+        eq(clone_info['size'], IMG_SIZE // 2)
         eq(image_info['size'], IMG_SIZE)
-        eq(self.clone.overlap(), IMG_SIZE / 2)
+        eq(self.clone.overlap(), IMG_SIZE // 2)
 
         self.clone.resize(IMG_SIZE * 2)
         image_info = self.image.stat()
         clone_info = self.clone.stat()
         eq(clone_info['size'], IMG_SIZE * 2)
         eq(image_info['size'], IMG_SIZE)
-        eq(self.clone.overlap(), IMG_SIZE / 2)
+        eq(self.clone.overlap(), IMG_SIZE // 2)
 
     def test_resize_io(self):
-        parent_data = self.image.read(IMG_SIZE / 2, 256)
+        parent_data = self.image.read(IMG_SIZE // 2, 256)
         self.image.resize(0)
-        self.clone.resize(IMG_SIZE / 2 + 128)
-        child_data = self.clone.read(IMG_SIZE / 2, 128)
+        self.clone.resize(IMG_SIZE // 2 + 128)
+        child_data = self.clone.read(IMG_SIZE // 2, 128)
         eq(child_data, parent_data[:128])
         self.clone.resize(IMG_SIZE)
-        child_data = self.clone.read(IMG_SIZE / 2, 256)
-        eq(child_data, parent_data[:128] + ('\0' * 128))
-        self.clone.resize(IMG_SIZE / 2 + 1)
-        child_data = self.clone.read(IMG_SIZE / 2, 1)
+        child_data = self.clone.read(IMG_SIZE // 2, 256)
+        eq(child_data, parent_data[:128] + (b'\0' * 128))
+        self.clone.resize(IMG_SIZE // 2 + 1)
+        child_data = self.clone.read(IMG_SIZE // 2, 1)
         eq(child_data, parent_data[0])
         self.clone.resize(0)
         self.clone.resize(IMG_SIZE)
-        child_data = self.clone.read(IMG_SIZE / 2, 256)
-        eq(child_data, '\0' * 256)
+        child_data = self.clone.read(IMG_SIZE // 2, 256)
+        eq(child_data, b'\0' * 256)
 
     def test_read(self):
-        parent_data = self.image.read(IMG_SIZE / 2, 256)
-        child_data = self.clone.read(IMG_SIZE / 2, 256)
+        parent_data = self.image.read(IMG_SIZE // 2, 256)
+        child_data = self.clone.read(IMG_SIZE // 2, 256)
         eq(child_data, parent_data)
 
     def test_write(self):
-        parent_data = self.image.read(IMG_SIZE / 2, 256)
+        parent_data = self.image.read(IMG_SIZE // 2, 256)
         new_data = rand_data(256)
-        self.clone.write(new_data, IMG_SIZE / 2 + 256)
-        child_data = self.clone.read(IMG_SIZE / 2 + 256, 256)
+        self.clone.write(new_data, IMG_SIZE // 2 + 256)
+        child_data = self.clone.read(IMG_SIZE // 2 + 256, 256)
         eq(child_data, new_data)
-        child_data = self.clone.read(IMG_SIZE / 2, 256)
+        child_data = self.clone.read(IMG_SIZE // 2, 256)
         eq(child_data, parent_data)
-        parent_data = self.image.read(IMG_SIZE / 2 + 256, 256)
-        eq(parent_data, '\0' * 256)
+        parent_data = self.image.read(IMG_SIZE // 2 + 256, 256)
+        eq(parent_data, b'\0' * 256)
 
     def check_children(self, expected):
         actual = self.image.list_children()
@@ -823,13 +821,13 @@ class TestClone(object):
 
         clone_name = get_temp_image_name() + '_'
         expected_children = []
-        for i in xrange(10):
+        for i in range(10):
             self.rbd.clone(ioctx, image_name, 'snap1', ioctx,
                            clone_name + str(i), features)
             expected_children.append((pool_name, clone_name + str(i)))
             self.check_children(expected_children)
 
-        for i in xrange(10):
+        for i in range(10):
             self.rbd.remove(ioctx, clone_name + str(i))
             expected_children.pop(0)
             self.check_children(expected_children)
@@ -899,15 +897,15 @@ class TestClone(object):
         with Image(ioctx, clone_name2) as clone:
             with Image(ioctx, clone_name2) as clone2:
                 # cache object non-existence
-                data = clone.read(IMG_SIZE / 2, 256)
-                clone2_data = clone2.read(IMG_SIZE / 2, 256)
+                data = clone.read(IMG_SIZE // 2, 256)
+                clone2_data = clone2.read(IMG_SIZE // 2, 256)
                 eq(data, clone2_data)
                 clone.flatten()
                 assert_raises(ImageNotFound, clone.parent_info)
                 assert_raises(ImageNotFound, clone2.parent_info)
-                after_flatten = clone.read(IMG_SIZE / 2, 256)
+                after_flatten = clone.read(IMG_SIZE // 2, 256)
                 eq(data, after_flatten)
-                after_flatten = clone2.read(IMG_SIZE / 2, 256)
+                after_flatten = clone2.read(IMG_SIZE // 2, 256)
                 eq(data, after_flatten)
         self.rbd.remove(ioctx, clone_name2)
 
@@ -962,8 +960,7 @@ class TestExclusiveLock(object):
         rados2.shutdown()
 
     def test_ownership(self):
-        with nested(Image(ioctx, image_name), Image(ioctx2, image_name)) as (
-                image1, image2):
+        with Image(ioctx, image_name) as image1, Image(ioctx2, image_name) as image2:
             image1.write('0'*256, 0)
             eq(image1.is_exclusive_lock_owner(), True)
             eq(image2.is_exclusive_lock_owner(), False)
@@ -974,7 +971,7 @@ class TestExclusiveLock(object):
             eq(image.is_exclusive_lock_owner(), True)
         try:
             with Image(ioctx, image_name) as image:
-                image.write('0'*256, 0)
+                image.write(b'0'*256, 0)
                 eq(image.is_exclusive_lock_owner(), True)
                 image.set_snap('snap')
                 eq(image.is_exclusive_lock_owner(), False)
@@ -994,14 +991,13 @@ class TestExclusiveLock(object):
             image.protect_snap('snap')
         try:
             RBD().clone(ioctx, image_name, 'snap', ioctx, 'clone', features)
-            with nested(Image(ioctx, 'clone'), Image(ioctx2, 'clone')) as (
-                    image1, image2):
+            with Image(ioctx, 'clone') as image1, Image(ioctx2, 'clone') as image2:
                 data = rand_data(256)
                 image1.write(data, 0)
                 image2.flatten()
                 assert_raises(ImageNotFound, image1.parent_info)
                 parent = True
-                for x in xrange(30):
+                for x in range(30):
                     try:
                         image2.parent_info()
                     except ImageNotFound:
@@ -1015,27 +1011,24 @@ class TestExclusiveLock(object):
                 image.remove_snap('snap')
 
     def test_follower_resize(self):
-        with nested(Image(ioctx, image_name), Image(ioctx2, image_name)) as (
-                image1, image2):
-            image1.write('0'*256, 0)
+        with Image(ioctx, image_name) as image1, Image(ioctx2, image_name) as image2:
+            image1.write(b'0'*256, 0)
             for new_size in [IMG_SIZE * 2, IMG_SIZE / 2]:
                 image2.resize(new_size);
                 eq(new_size, image1.size())
-                for x in xrange(30):
+                for x in range(30):
                     if new_size == image2.size():
                         break
                     time.sleep(1)
                 eq(new_size, image2.size())
 
     def test_follower_snap_create(self):
-        with nested(Image(ioctx, image_name), Image(ioctx2, image_name)) as (
-                image1, image2):
+        with Image(ioctx, image_name) as image1, Image(ioctx2, image_name) as image2:
             image2.create_snap('snap1')
             image1.remove_snap('snap1')
 
     def test_follower_snap_rollback(self):
-        with nested(Image(ioctx, image_name), Image(ioctx2, image_name)) as (
-                image1, image2):
+        with Image(ioctx, image_name) as image1, Image(ioctx2, image_name) as image2:
             image1.create_snap('snap')
             try:
                 assert_raises(ReadOnlyImage, image2.rollback_to_snap, 'snap')
@@ -1044,8 +1037,7 @@ class TestExclusiveLock(object):
                 image1.remove_snap('snap')
 
     def test_follower_discard(self):
-        with nested(Image(ioctx, image_name), Image(ioctx2, image_name)) as (
-                image1, image2):
+        with Image(ioctx, image_name) as image1, Image(ioctx2, image_name) as image2:
             data = rand_data(256)
             image1.write(data, 0)
             image2.discard(0, 256)
@@ -1055,8 +1047,7 @@ class TestExclusiveLock(object):
             eq(256*'\0', read)
 
     def test_follower_write(self):
-        with nested(Image(ioctx, image_name), Image(ioctx2, image_name)) as (
-                image1, image2):
+        with Image(ioctx, image_name) as image1, Image(ioctx2, image_name) as image2:
             data = rand_data(256)
             image1.write(data, 0)
             image2.write(data, IMG_SIZE / 2)