summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosh Durgin <josh.durgin@inktank.com>2013-04-08 09:58:59 -0700
committerJosh Durgin <josh.durgin@inktank.com>2013-04-08 09:58:59 -0700
commitc17b17229acd5090e19e46e8ebe4dfcf9a85db89 (patch)
tree5d24a0da23afeb76a5783010394d4600486a62f8
parentf5ba0fbbe73e11418634bc95e1fc36d17edccf37 (diff)
parent1734742b2c736997456ead6aa64e48e70455a537 (diff)
downloadceph-c17b17229acd5090e19e46e8ebe4dfcf9a85db89.tar.gz
Merge pull request #203 from dalgaaf/wip-da-pybind-pylint-v2
Reviewed-by: Josh Durgin <josh.durgin@inktank.com>
-rw-r--r--src/pybind/cephfs.py44
-rw-r--r--[-rwxr-xr-x]src/pybind/rados.py585
-rw-r--r--src/pybind/rbd.py4
3 files changed, 598 insertions, 35 deletions
diff --git a/src/pybind/cephfs.py b/src/pybind/cephfs.py
index 7c4d020a1fe..f89f53fb194 100644
--- a/src/pybind/cephfs.py
+++ b/src/pybind/cephfs.py
@@ -1,4 +1,5 @@
-"""libcephfs Python ctypes wrapper
+"""
+This module is a thin wrapper around libcephfs.
"""
from ctypes import CDLL, c_char_p, c_size_t, c_void_p, c_int, c_long, c_uint, c_ulong, \
create_string_buffer, byref, Structure, c_uint64, c_ubyte, pointer, \
@@ -34,19 +35,27 @@ class LibCephFSStateError(Error):
pass
def make_ex(ret, msg):
+ """
+ Translate a libcephfs 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`
+ """
+
+ errors = {
+ errno.EPERM : PermissionError,
+ errno.ENOENT : ObjectNotFound,
+ errno.EIO : IOError,
+ errno.ENOSPC : NoSpace,
+ errno.EEXIST : ObjectExists,
+ errno.ENODATA : NoData
+ }
ret = abs(ret)
- if ret == errno.EPERM:
- return PermissionError(msg)
- elif ret == errno.ENOENT:
- return ObjectNotFound(msg)
- elif ret == errno.EIO:
- return IOError(msg)
- elif ret == errno.ENOSPC:
- return NoSpace(msg)
- elif ret == errno.EEXIST:
- return ObjectExists(msg)
- elif ret == errno.ENODATA:
- return NoData(msg)
+ if ret in errors:
+ return errors[ret](msg)
else:
return Error(msg + (": error code %d" % ret))
@@ -143,6 +152,9 @@ class LibCephFS(object):
raise make_ex(ret, "error calling conf_read_file")
def shutdown(self):
+ """
+ Unmount and destroy the ceph mount handle.
+ """
if self.state != "shutdown":
self.libcephfs.ceph_shutdown(self.cluster)
self.state = "shutdown"
@@ -159,6 +171,12 @@ class LibCephFS(object):
self.shutdown()
def version(self):
+ """
+ Get the version number of the ``libcephfs`` C library.
+
+ :returns: a tuple of ``(major, minor, extra)`` components of the
+ libcephfs version
+ """
major = c_int(0)
minor = c_int(0)
extra = c_int(0)
diff --git a/src/pybind/rados.py b/src/pybind/rados.py
index b0597350a4f..b465728179e 100755..100644
--- a/src/pybind/rados.py
+++ b/src/pybind/rados.py
@@ -1,4 +1,6 @@
-"""librados Python ctypes wrapper
+"""
+This module is a thin wrapper around librados.
+
Copyright 2011, Hannu Valtonen <hannu.valtonen@ormod.com>
"""
from ctypes import CDLL, c_char_p, c_size_t, c_void_p, c_int, c_long, \
@@ -14,59 +16,80 @@ ANONYMOUS_AUID = 0xffffffffffffffff
ADMIN_AUID = 0
class Error(Exception):
+ """ `Error` class, derived from `Exception` """
pass
class PermissionError(Error):
+ """ `PermissionError` class, derived from `Error` """
pass
class ObjectNotFound(Error):
+ """ `ObjectNotFound` class, derived from `Error` """
pass
class NoData(Error):
+ """ `NoData` class, derived from `Error` """
pass
class ObjectExists(Error):
+ """ `ObjectExists` class, derived from `Error` """
pass
class IOError(Error):
+ """ `IOError` class, derived from `Error` """
pass
class NoSpace(Error):
+ """ `NoSpace` class, derived from `Error` """
pass
class IncompleteWriteError(Error):
+ """ `IncompleteWriteError` class, derived from `Error` """
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
def 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`
+ """
+
+ errors = {
+ errno.EPERM : PermissionError,
+ errno.ENOENT : ObjectNotFound,
+ errno.EIO : IOError,
+ errno.ENOSPC : NoSpace,
+ errno.EEXIST : ObjectExists,
+ errno.ENODATA : NoData
+ }
ret = abs(ret)
- if (ret == errno.EPERM):
- return PermissionError(msg)
- elif (ret == errno.ENOENT):
- return ObjectNotFound(msg)
- elif (ret == errno.EIO):
- return IOError(msg)
- elif (ret == errno.ENOSPC):
- return NoSpace(msg)
- elif (ret == errno.EEXIST):
- return ObjectExists(msg)
- elif (ret == errno.ENODATA):
- return NoData(msg)
+ if ret in errors:
+ return errors[ret](msg)
else:
return Error(msg + (": error code %d" % ret))
class rados_pool_stat_t(Structure):
+ """ Usage information for a pool """
_fields_ = [("num_bytes", c_uint64),
("num_kb", c_uint64),
("num_objects", c_uint64),
@@ -81,12 +104,14 @@ class rados_pool_stat_t(Structure):
("num_wr_kb", c_uint64)]
class rados_cluster_stat_t(Structure):
+ """ Cluster-wide usage information """
_fields_ = [("kb", c_uint64),
("kb_used", c_uint64),
("kb_avail", c_uint64),
("num_objects", c_uint64)]
class Version(object):
+ """ Version information """
def __init__(self, major, minor, extra):
self.major = major
self.minor = minor
@@ -98,6 +123,11 @@ class Version(object):
class Rados(object):
"""librados python wrapper"""
def require_state(self, *args):
+ """
+ Checks if the Rados object is in a special state
+
+ :raises: RadosStateError
+ """
for a in args:
if self.state == a:
return
@@ -126,6 +156,9 @@ Rados object in state %s." % (self.state))
self.conf_set(key, value)
def shutdown(self):
+ """
+ Disconnects from the cluster.
+ """
if (self.__dict__.has_key("state") and self.state != "shutdown"):
self.librados.rados_shutdown(self.cluster)
self.state = "shutdown"
@@ -142,6 +175,12 @@ Rados object in state %s." % (self.state))
self.shutdown()
def version(self):
+ """
+ Get the version number of the ``librados`` C library.
+
+ :returns: a tuple of ``(major, minor, extra)`` components of the
+ librados version
+ """
major = c_int(0)
minor = c_int(0)
extra = c_int(0)
@@ -149,6 +188,12 @@ Rados object in state %s." % (self.state))
return Version(major.value, minor.value, extra.value)
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")
if path is not None and not isinstance(path, str):
raise TypeError('path must be a string')
@@ -157,6 +202,15 @@ Rados object in state %s." % (self.state))
raise make_ex(ret, "error calling conf_read_file")
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")
if not isinstance(option, str):
raise TypeError('option must be a string')
@@ -175,6 +229,16 @@ Rados object in state %s." % (self.state))
raise make_ex(ret, "error calling conf_get")
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")
if not isinstance(option, str):
raise TypeError('option must be a string')
@@ -186,6 +250,9 @@ Rados object in state %s." % (self.state))
raise make_ex(ret, "error calling conf_set")
def connect(self):
+ """
+ Connect to the cluster.
+ """
self.require_state("configuring")
ret = self.librados.rados_connect(self.cluster)
if (ret != 0):
@@ -193,6 +260,24 @@ Rados object in state %s." % (self.state))
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
+
+ """
stats = rados_cluster_stat_t()
ret = self.librados.rados_cluster_stat(self.cluster, byref(stats))
if ret < 0:
@@ -203,8 +288,16 @@ Rados object in state %s." % (self.state))
'kb_avail': stats.kb_avail,
'num_objects': stats.num_objects}
- # Returns true if the pool exists; false otherwise.
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")
if not isinstance(pool_name, str):
raise TypeError('pool_name must be a string')
@@ -217,6 +310,22 @@ Rados object in state %s." % (self.state))
raise make_ex(ret, "error looking up pool '%s'" % pool_name)
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: str
+
+ :raises: :class:`TypeError`, :class:`Error`
+ """
self.require_state("connected")
if not isinstance(pool_name, str):
raise TypeError('pool_name must be a string')
@@ -227,19 +336,31 @@ Rados object in state %s." % (self.state))
ret = self.librados.rados_pool_create(
self.cluster, c_char_p(pool_name))
else:
- ret = self.librados.rados_pool_create_with_all(
- self.cluster, c_char_p(pool_name), c_uint64(auid),
- c_ubyte(crush_rule))
+ ret = self.librados.rados_pool_create_with_crush_rule(
+ self.cluster, c_char_p(pool_name), c_ubyte(crush_rule))
+
elif (crush_rule == None):
ret = self.librados.rados_pool_create_with_auid(
self.cluster, c_char_p(pool_name), c_uint64(auid))
else:
- ret = self.librados.rados_pool_create_with_crush_rule(
- self.cluster, c_char_p(pool_name), c_ubyte(crush_rule))
+ ret = self.librados.rados_pool_create_with_all(
+ self.cluster, c_char_p(pool_name), c_uint64(auid),
+ c_ubyte(crush_rule))
if ret < 0:
raise make_ex(ret, "error creating pool '%s'" % pool_name)
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")
if not isinstance(pool_name, str):
raise TypeError('pool_name must be a string')
@@ -248,6 +369,11 @@ Rados object in state %s." % (self.state))
raise make_ex(ret, "error deleting pool '%s'" % pool_name)
def list_pools(self):
+ """
+ Gets a list of pool names.
+
+ :returns: list - of pool names.
+ """
self.require_state("connected")
size = c_size_t(512)
while True:
@@ -261,6 +387,12 @@ Rados object in state %s." % (self.state))
return filter(lambda name: name != '', c_names.raw.split('\0'))
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")
fsid_len = 36
fsid = create_string_buffer(fsid_len + 1)
@@ -272,6 +404,18 @@ Rados object in state %s." % (self.state))
return fsid.value
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")
if not isinstance(ioctx_name, str):
raise TypeError('ioctx_name must be a string')
@@ -296,6 +440,12 @@ class ObjectIterator(object):
return self
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()
ret = self.ioctx.librados.rados_objects_list_next(self.ctx, byref(key),
@@ -313,9 +463,17 @@ class XattrIterator(object):
self.ioctx = ioctx
self.it = it
self.oid = 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
+ """
name_ = c_char_p(0)
val_ = c_char_p(0)
len_ = c_int(0)
@@ -329,6 +487,7 @@ in '%s'" % self.oid)
name = ctypes.string_at(name_)
val = ctypes.string_at(val_, len_)
return (name, val)
+
def __del__(self):
self.ioctx.librados.rados_getxattrs_end(self.it)
@@ -356,6 +515,12 @@ ioctx '%s'" % self.ioctx.name)
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
snap_id = self.snaps[self.cur_snap]
@@ -386,6 +551,12 @@ class Snap(object):
% (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
+ """
snap_time = c_long(0)
ret = self.ioctx.librados.rados_ioctx_snap_get_stamp(
self.ioctx.io, self.snap_id,
@@ -403,20 +574,48 @@ class Completion(object):
self.ioctx = ioctx
def wait_for_safe(self):
+ """
+ Is an asynchronous operation safe?
+
+ This does not imply that the safe callback has finished.
+
+ :returns: whether the operation is safe
+ """
return self.ioctx.librados.rados_aio_is_safe(
self.rados_comp
)
def wait_for_complete(self):
+ """
+ Has an asynchronous operation completed?
+
+ This does not imply that the safe callback has finished.
+
+ :returns: whether the operation is completed
+ """
return self.ioctx.librados.rados_aio_is_complete(
self.rados_comp
)
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
+ """
return self.ioctx.librados.rados_aio_get_return_value(
self.rados_comp)
def __del__(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.
+ """
self.ioctx.librados.rados_aio_release(
self.rados_comp
)
@@ -447,6 +646,9 @@ class Ioctx(object):
self.close()
def __aio_safe_cb(self, completion, _):
+ """
+ Callback to onsafe() for asynchronous operations
+ """
cb = None
with self.lock:
cb = self.safe_cbs[completion]
@@ -455,6 +657,9 @@ class Ioctx(object):
return 0
def __aio_complete_cb(self, completion, _):
+ """
+ Callback to oncomplete() for asynchronous operations
+ """
cb = None
with self.lock:
cb = self.complete_cbs[completion]
@@ -463,6 +668,19 @@ class Ioctx(object):
return 0
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 = c_void_p(0)
complete_cb = None
safe_cb = None
@@ -488,6 +706,27 @@ class Ioctx(object):
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: 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
+ """
completion = self.__get_completion(oncomplete, onsafe)
ret = self.librados.rados_aio_write(
self.io,
@@ -502,6 +741,27 @@ class Ioctx(object):
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
+ """
completion = self.__get_completion(oncomplete, onsafe)
ret = self.librados.rados_aio_write_full(
self.io,
@@ -514,6 +774,27 @@ class Ioctx(object):
return completion
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
+ """
completion = self.__get_completion(oncomplete, onsafe)
ret = self.librados.rados_aio_append(
self.io,
@@ -526,6 +807,11 @@ class Ioctx(object):
return completion
def aio_flush(self):
+ """
+ Block until all pending writes in an io context are safe
+
+ :raises: :class:`Error`
+ """
ret = self.librados.rados_aio_flush(
self.io)
if ret < 0:
@@ -533,10 +819,24 @@ class Ioctx(object):
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
"""
buf = create_string_buffer(length)
def oncomplete_(completion):
@@ -554,10 +854,23 @@ class Ioctx(object):
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()
ret = self.librados.rados_ioctx_pool_set_auid(self.io, \
ctypes.c_uint64(auid))
@@ -566,6 +879,20 @@ class Ioctx(object):
(self.name, auid))
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()
if not isinstance(loc_key, str):
raise TypeError('loc_key must be a string')
@@ -574,15 +901,43 @@ class Ioctx(object):
self.locator_key = loc_key
def get_locator_key(self):
+ """
+ Get the locator_key of context
+
+ :returns: locator_key
+ """
return self.locator_key
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()
self.librados.rados_ioctx_destroy(self.io)
self.state = "closed"
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: str
+ :param offset: byte offset in the object to begin writing at
+ :type offset: int
+
+ :raises: :class:`TypeError`
+ :raises: :class:`IncompleteWriteError`
+ :raises: :class:`LogicError`
+ :returns: int - number of bytes written
+ """
self.require_ioctx_open()
if not isinstance(data, str):
raise TypeError('data must be a string')
@@ -603,6 +958,21 @@ returned %d, but %d was the maximum number of bytes it could have \
written." % (self.name, ret, length))
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: str
+
+ :raises: :class:`TypeError`
+ :raises: :class:`Error`
+ :returns: int - 0 on success
+ """
self.require_ioctx_open()
if not isinstance(key, str):
raise TypeError('key must be a string')
@@ -618,6 +988,20 @@ written." % (self.name, ret, length))
(self.name, key))
def read(self, key, length=8192, offset=0):
+ """
+ Write data to 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()
if not isinstance(key, str):
raise TypeError('key must be a string')
@@ -629,6 +1013,36 @@ written." % (self.name, ret, length))
return ctypes.string_at(ret_buf, ret)
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()
stats = rados_pool_stat_t()
ret = self.librados.rados_ioctx_pool_stat(self.io, byref(stats))
@@ -648,6 +1062,18 @@ written." % (self.name, ret, length))
"num_wr_kb": stats.num_wr_kb }
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()
if not isinstance(key, str):
raise TypeError('key must be a string')
@@ -657,6 +1083,22 @@ written." % (self.name, ret, length))
return True
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()
if not isinstance(key, str):
raise TypeError('key must be a string')
@@ -666,7 +1108,16 @@ written." % (self.name, ret, length))
return ret
def stat(self, key):
- """Stat object, returns, size/timestamp"""
+ """
+ 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()
if not isinstance(key, str):
raise TypeError('key must be a string')
@@ -680,6 +1131,18 @@ written." % (self.name, ret, length))
return psize.value, time.localtime(pmtime.value)
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()
if not isinstance(xattr_name, str):
raise TypeError('xattr_name must be a string')
@@ -692,6 +1155,16 @@ written." % (self.name, ret, length))
return ctypes.string_at(ret_buf, ret)
def get_xattrs(self, oid):
+ """
+ Start iterating over xattrs on an object.
+
+ :param oid: the name of the object to get xattrs from
+ :type key: str
+
+ :raises: :class:`TypeError`
+ :raises: :class:`Error`
+ :returns: XattrIterator
+ """
self.require_ioctx_open()
if not isinstance(oid, str):
raise TypeError('oid must be a string')
@@ -702,6 +1175,20 @@ written." % (self.name, ret, length))
return XattrIterator(self, it, oid)
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: str
+
+ :raises: :class:`TypeError`
+ :raises: :class:`Error`
+ :returns: bool - True on success, otherwise raise an error
+ """
self.require_ioctx_open()
if not isinstance(key, str):
raise TypeError('key must be a string')
@@ -717,6 +1204,18 @@ written." % (self.name, ret, length))
return True
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()
if not isinstance(key, str):
raise TypeError('key must be a string')
@@ -729,14 +1228,33 @@ written." % (self.name, ret, length))
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)
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()
if not isinstance(snap_name, str):
raise TypeError('snap_name must be a string')
@@ -746,6 +1264,15 @@ written." % (self.name, ret, length))
raise make_ex(ret, "Failed to create snap %s" % snap_name)
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()
if not isinstance(snap_name, str):
raise TypeError('snap_name must be a string')
@@ -755,6 +1282,16 @@ written." % (self.name, ret, length))
raise make_ex(ret, "Failed to remove snap %s" % snap_name)
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()
if not isinstance(snap_name, str):
raise TypeError('snap_name must be a string')
@@ -766,6 +1303,14 @@ written." % (self.name, ret, length))
return Snap(self, snap_name, snap_id)
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()
return self.librados.rados_get_last_version(self.io)
diff --git a/src/pybind/rbd.py b/src/pybind/rbd.py
index c5cef9dee42..b59ff63ab05 100644
--- a/src/pybind/rbd.py
+++ b/src/pybind/rbd.py
@@ -431,7 +431,7 @@ class Image(object):
def parent_info(self):
ret = -errno.ERANGE
- size = 8;
+ size = 8
while ret == -errno.ERANGE and size < 128:
pool = create_string_buffer(size)
name = create_string_buffer(size)
@@ -439,7 +439,7 @@ class Image(object):
ret = self.librbd.rbd_get_parent_info(self.image, pool, len(pool),
name, len(name), snapname, len(snapname))
if ret == -errno.ERANGE:
- size *= 2;
+ size *= 2
if (ret != 0):
raise make_ex(ret, 'error getting parent info for image %s' % (self.name,))