diff options
author | Josh Durgin <josh.durgin@inktank.com> | 2013-04-08 09:58:59 -0700 |
---|---|---|
committer | Josh Durgin <josh.durgin@inktank.com> | 2013-04-08 09:58:59 -0700 |
commit | c17b17229acd5090e19e46e8ebe4dfcf9a85db89 (patch) | |
tree | 5d24a0da23afeb76a5783010394d4600486a62f8 | |
parent | f5ba0fbbe73e11418634bc95e1fc36d17edccf37 (diff) | |
parent | 1734742b2c736997456ead6aa64e48e70455a537 (diff) | |
download | ceph-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.py | 44 | ||||
-rw-r--r--[-rwxr-xr-x] | src/pybind/rados.py | 585 | ||||
-rw-r--r-- | src/pybind/rbd.py | 4 |
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,)) |