diff --git a/src/Makefile.am b/src/Makefile.am index 58083e0cdae01..d80b2053b9268 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1421,6 +1421,7 @@ noinst_HEADERS = \ librbd/ImageCtx.h\ librbd/internal.h\ librbd/LibrbdWriteback.h\ + librbd/parent_types.h\ librbd/SnapInfo.h\ librbd/WatchCtx.h\ logrotate.conf\ diff --git a/src/cls_rbd.cc b/src/cls_rbd.cc index 1109d1676b38e..89767625e788e 100644 --- a/src/cls_rbd.cc +++ b/src/cls_rbd.cc @@ -55,6 +55,9 @@ cls_method_handle_t h_set_parent; cls_method_handle_t h_get_protection_status; cls_method_handle_t h_set_protection_status; cls_method_handle_t h_remove_parent; +cls_method_handle_t h_add_child; +cls_method_handle_t h_remove_child; +cls_method_handle_t h_get_children; cls_method_handle_t h_get_snapcontext; cls_method_handle_t h_get_object_prefix; cls_method_handle_t h_get_snapshot_name; @@ -990,6 +993,216 @@ int remove_parent(cls_method_context_t hctx, bufferlist *in, bufferlist *out) return 0; } +/** + * methods for dealing with rbd_children object + */ + +static int decode_parent_common(bufferlist::iterator& it, uint64_t *pool_id, + string *image_id, snapid_t *snap_id) +{ + try { + ::decode(*pool_id, it); + ::decode(*image_id, it); + ::decode(*snap_id, it); + } catch (const buffer::error &err) { + CLS_ERR("error decoding parent spec"); + return -EINVAL; + } + return 0; +} + +static int decode_parent(bufferlist *in, uint64_t *pool_id, + string *image_id, snapid_t *snap_id) +{ + bufferlist::iterator it = in->begin(); + return decode_parent_common(it, pool_id, image_id, snap_id); +} + +static int decode_parent_and_child(bufferlist *in, uint64_t *pool_id, + string *image_id, snapid_t *snap_id, + string *c_image_id) +{ + bufferlist::iterator it = in->begin(); + int r = decode_parent_common(it, pool_id, image_id, snap_id); + if (r < 0) + return r; + try { + ::decode(*c_image_id, it); + } catch (const buffer::error &err) { + CLS_ERR("error decoding child image id"); + return -EINVAL; + } + return 0; +} + +static string parent_key(uint64_t pool_id, string image_id, snapid_t snap_id) +{ + bufferlist key_bl; + ::encode(pool_id, key_bl); + ::encode(image_id, key_bl); + ::encode(snap_id, key_bl); + return string(key_bl.c_str(), key_bl.length()); +} + +/** + * add child to rbd_children directory object + * + * rbd_children is a map of (p_pool_id, p_image_id, p_snap_id) to + * [c_image_id, [c_image_id ... ]] + * + * Input: + * @param p_pool_id parent pool id + * @param p_image_id parent image oid + * @param p_snap_id parent snapshot id + * @param c_image_id new child image oid to add + * + * @returns 0 on success, negative error on failure + */ + +int add_child(cls_method_context_t hctx, bufferlist *in, bufferlist *out) +{ + int r; + + uint64_t p_pool_id; + snapid_t p_snap_id; + string p_image_id, c_image_id; + // Use set for ease of erase() for remove_child() + std::set children; + + r = decode_parent_and_child(in, &p_pool_id, &p_image_id, &p_snap_id, + &c_image_id); + if (r < 0) + return r; + + CLS_LOG(20, "add_child %s to (%d, %s, %d)", c_image_id.c_str(), + p_pool_id, p_image_id.c_str(), p_snap_id); + + string key = parent_key(p_pool_id, p_image_id, p_snap_id); + + // get current child list for parent, if any + r = read_key(hctx, key, &children); + if ((r < 0) && (r != -ENOENT)) { + CLS_LOG(20, "add_child: omap read failed: %d", r); + return r; + } + + if (children.find(c_image_id) != children.end()) { + CLS_LOG(20, "add_child: child already exists: %s", c_image_id.c_str()); + return -EEXIST; + } + // add new child + children.insert(c_image_id); + + // write back + bufferlist childbl; + ::encode(children, childbl); + r = cls_cxx_map_set_val(hctx, key, &childbl); + if (r < 0) + CLS_LOG(20, "add_child: omap write failed: %d", r); + return r; +} + +/** + * remove child from rbd_children directory object + * + * Input: + * @param p_pool_id parent pool id + * @param p_image_id parent image oid + * @param p_snap_id parent snapshot id + * @param c_image_id new child image oid to add + * + * @returns 0 on success, negative error on failure + */ + +int remove_child(cls_method_context_t hctx, bufferlist *in, bufferlist *out) +{ + int r; + + uint64_t p_pool_id; + snapid_t p_snap_id; + string p_image_id, c_image_id; + std::set children; + + r = decode_parent_and_child(in, &p_pool_id, &p_image_id, &p_snap_id, + &c_image_id); + if (r < 0) + return r; + + CLS_LOG(20, "remove_child %s from (%d, %s, %d)", c_image_id.c_str(), + p_pool_id, p_image_id.c_str(), p_snap_id); + + string key = parent_key(p_pool_id, p_image_id, p_snap_id); + + // get current child list for parent. Unlike add_child(), an empty list + // is an error (how can we remove something that doesn't exist?) + r = read_key(hctx, key, &children); + if (r < 0) { + CLS_LOG(20, "remove_child: read omap failed: %d", r); + return r; + } + + if (children.find(c_image_id) == children.end()) { + CLS_LOG(20, "remove_child: child not found: %s", c_image_id.c_str()); + return -ENOENT; + } + // find and remove child + children.erase(c_image_id); + + // now empty? remove key altogether + if (children.empty()) { + r = cls_cxx_map_remove_key(hctx, key); + if (r < 0) + CLS_LOG(20, "remove_child: remove key failed: %d", r); + } else { + // write back shortened children list + bufferlist childbl; + ::encode(children, childbl); + r = cls_cxx_map_set_val(hctx, key, &childbl); + if (r < 0) + CLS_LOG(20, "remove_child: write omap failed: %d ", r); + } + return r; +} + +/** + * Input: + * @param p_pool_id parent pool id + * @param p_image_id parent image oid + * @param p_snap_id parent snapshot id + * @param c_image_id new child image oid to add + * + * Output: + * @param children set of children + * + * @returns 0 on success, negative error on failure + */ +int get_children(cls_method_context_t hctx, bufferlist *in, bufferlist *out) +{ + int r; + uint64_t p_pool_id; + snapid_t p_snap_id; + string p_image_id; + std::set children; + + r = decode_parent(in, &p_pool_id, &p_image_id, &p_snap_id); + if (r < 0) + return r; + + CLS_LOG(20, "get_children of (%d, %s, %d)", + p_pool_id, p_image_id.c_str(), p_snap_id); + + string key = parent_key(p_pool_id, p_image_id, p_snap_id); + + r = read_key(hctx, key, &children); + if (r < 0) { + if (r != -ENOENT) + CLS_LOG(20, "get_children: read omap failed: %d", r); + return r; + } + ::encode(children, *out); + return 0; +} + /** * Get the information needed to create a rados snap context for doing @@ -1995,6 +2208,17 @@ void __cls_init() CLS_METHOD_RD | CLS_METHOD_PUBLIC, get_protection_status, &h_get_protection_status); + /* methods for the rbd_children object */ + cls_register_cxx_method(h_class, "add_child", + CLS_METHOD_RD | CLS_METHOD_WR | CLS_METHOD_PUBLIC, + add_child, &h_add_child); + cls_register_cxx_method(h_class, "remove_child", + CLS_METHOD_RD | CLS_METHOD_WR | CLS_METHOD_PUBLIC, + remove_child, &h_remove_child); + cls_register_cxx_method(h_class, "get_children", + CLS_METHOD_RD | CLS_METHOD_WR | CLS_METHOD_PUBLIC, + get_children, &h_get_children); + /* methods for the rbd_id.$image_name objects */ cls_register_cxx_method(h_class, "get_id", CLS_METHOD_RD | CLS_METHOD_PUBLIC, diff --git a/src/include/rbd/librbd.h b/src/include/rbd/librbd.h index c6550754d3d37..e237b7ff7ebf6 100644 --- a/src/include/rbd/librbd.h +++ b/src/include/rbd/librbd.h @@ -109,6 +109,7 @@ int rbd_snap_rollback_with_progress(rbd_image_t image, const char *snapname, * * @param snap_name which snapshot to protect * @returns 0 on success, negative error code on failure + * @returns -EBUSY if snap is already protected */ int rbd_snap_protect(rbd_image_t image, const char *snap_name); /** @@ -116,6 +117,7 @@ int rbd_snap_protect(rbd_image_t image, const char *snap_name); * * @param snap_name which snapshot to unprotect * @returns 0 on success, negative error code on failure + * @returns -EINVAL if snap is not protected */ int rbd_snap_unprotect(rbd_image_t image, const char *snap_name); /** diff --git a/src/include/rbd_types.h b/src/include/rbd_types.h index 034aece9af2a2..6a00cdca09ac9 100644 --- a/src/include/rbd_types.h +++ b/src/include/rbd_types.h @@ -45,6 +45,22 @@ #define RBD_DIRECTORY "rbd_directory" #define RBD_INFO "rbd_info" +/* + * rbd_children object in each pool contains omap entries + * that map parent (poolid, imageid, snapid) to a list of children + * (imageids; snapids aren't required because we get all the snapshot + * info from a read of the child's header object anyway). + * + * The clone operation writes a new item to this child list, and rm or + * flatten removes an item, and may remove the whole entry if no children + * exist after the rm/flatten. + * + * When attempting to remove a parent, all pools are searched for + * rbd_children objects with entries referring to that parent; if any + * exist (and those children exist), the parent removal is prevented. + */ +#define RBD_CHILDREN "rbd_children" + #define RBD_DEFAULT_OBJ_ORDER 22 /* 4MB */ #define RBD_MAX_OBJ_NAME_SIZE 96 diff --git a/src/librbd/ImageCtx.cc b/src/librbd/ImageCtx.cc index 4c9afe837c8e0..33baf5f613577 100644 --- a/src/librbd/ImageCtx.cc +++ b/src/librbd/ImageCtx.cc @@ -7,7 +7,6 @@ #include "common/errno.h" #include "common/perf_counters.h" -#include "librbd/cls_rbd.h" #include "librbd/internal.h" #include "librbd/WatchCtx.h" @@ -212,6 +211,20 @@ namespace librbd { return -ENOENT; } + int ImageCtx::get_parent_spec(snapid_t in_snap_id, parent_spec *out_pspec) + { + assert(snap_lock.is_locked()); + map::iterator it; + + for (it = snaps_by_name.begin(); it != snaps_by_name.end(); it++) { + if (it->second.id == in_snap_id) { + *out_pspec = it->second.parent.spec; + return 0; + } + } + return -ENOENT; + } + int ImageCtx::is_snap_protected(string in_snap_name, bool *is_protected) const { assert(snap_lock.is_locked()); @@ -237,7 +250,7 @@ namespace librbd { void ImageCtx::add_snap(string in_snap_name, snap_t id, uint64_t in_size, uint64_t features, - cls_client::parent_info parent, + parent_info parent, uint8_t protection_status) { assert(snap_lock.is_locked()); @@ -287,7 +300,7 @@ namespace librbd { assert(snap_lock.is_locked()); assert(parent_lock.is_locked()); if (in_snap_id == CEPH_NOSNAP) { - return parent_md.pool_id; + return parent_md.spec.pool_id; } string in_snap_name; int r = get_snap_name(in_snap_id, &in_snap_name); @@ -296,7 +309,7 @@ namespace librbd { map::const_iterator p = snaps_by_name.find(in_snap_name); if (p == snaps_by_name.end()) return -1; - return p->second.parent.pool_id; + return p->second.parent.spec.pool_id; } string ImageCtx::get_parent_image_id(snap_t in_snap_id) const @@ -304,7 +317,7 @@ namespace librbd { assert(snap_lock.is_locked()); assert(parent_lock.is_locked()); if (in_snap_id == CEPH_NOSNAP) { - return parent_md.image_id; + return parent_md.spec.image_id; } string in_snap_name; int r = get_snap_name(in_snap_id, &in_snap_name); @@ -313,7 +326,7 @@ namespace librbd { map::const_iterator p = snaps_by_name.find(in_snap_name); if (p == snaps_by_name.end()) return ""; - return p->second.parent.image_id; + return p->second.parent.spec.image_id; } uint64_t ImageCtx::get_parent_snap_id(snap_t in_snap_id) const @@ -321,7 +334,7 @@ namespace librbd { assert(snap_lock.is_locked()); assert(parent_lock.is_locked()); if (in_snap_id == CEPH_NOSNAP) { - return parent_md.snap_id; + return parent_md.spec.snap_id; } string in_snap_name; int r = get_snap_name(in_snap_id, &in_snap_name); @@ -330,7 +343,7 @@ namespace librbd { map::const_iterator p = snaps_by_name.find(in_snap_name); if (p == snaps_by_name.end()) return CEPH_NOSNAP; - return p->second.parent.snap_id; + return p->second.parent.spec.snap_id; } int ImageCtx::get_parent_overlap(snap_t in_snap_id, uint64_t *overlap) const diff --git a/src/librbd/ImageCtx.h b/src/librbd/ImageCtx.h index f909550dcabc1..766ec097c082f 100644 --- a/src/librbd/ImageCtx.h +++ b/src/librbd/ImageCtx.h @@ -21,6 +21,7 @@ #include "librbd/cls_rbd_client.h" #include "librbd/LibrbdWriteback.h" #include "librbd/SnapInfo.h" +#include "librbd/parent_types.h" class CephContext; class PerfCounters; @@ -67,7 +68,7 @@ namespace librbd { std::string object_prefix; std::string header_oid; std::string id; // only used for new-format images - cls_client::parent_info parent_md; + parent_info parent_md; ImageCtx *parent; ObjectCacher *object_cacher; @@ -89,12 +90,12 @@ namespace librbd { void snap_unset(); librados::snap_t get_snap_id(std::string in_snap_name) const; int get_snap_name(snapid_t snap_id, std::string *out_snap_name) const; + int get_parent_spec(snapid_t snap_id, parent_spec *pspec); int get_snap_size(std::string in_snap_name, uint64_t *out_size) const; int is_snap_protected(string in_snap_name, bool *is_protected) const; void add_snap(std::string in_snap_name, librados::snap_t id, uint64_t in_size, uint64_t features, - cls_client::parent_info parent, - uint8_t protection_status); + parent_info parent, uint8_t protection_status); uint64_t get_image_size(librados::snap_t in_snap_id) const; int get_features(librados::snap_t in_snap_id, uint64_t *out_features) const; diff --git a/src/librbd/SnapInfo.h b/src/librbd/SnapInfo.h index b53ce0a21d987..ef648de45ef94 100644 --- a/src/librbd/SnapInfo.h +++ b/src/librbd/SnapInfo.h @@ -8,6 +8,7 @@ #include "include/rados/librados.hpp" #include "librbd/cls_rbd_client.h" +#include "librbd/parent_types.h" namespace librbd { @@ -15,10 +16,10 @@ namespace librbd { librados::snap_t id; uint64_t size; uint64_t features; - cls_client::parent_info parent; + parent_info parent; uint8_t protection_status; SnapInfo(librados::snap_t _id, uint64_t _size, uint64_t _features, - cls_client::parent_info _parent, uint8_t _protection_status) : + parent_info _parent, uint8_t _protection_status) : id(_id), size(_size), features(_features), parent(_parent), protection_status(_protection_status) {} }; diff --git a/src/librbd/cls_rbd.h b/src/librbd/cls_rbd.h index 31fb17dc463bb..b0f6e1502ffbf 100644 --- a/src/librbd/cls_rbd.h +++ b/src/librbd/cls_rbd.h @@ -6,13 +6,7 @@ #include "include/types.h" #include "include/buffer.h" #include "common/Formatter.h" - -enum { - RBD_PROTECTION_STATUS_UNPROTECTED = 0, - RBD_PROTECTION_STATUS_UNPROTECTING = 1, - RBD_PROTECTION_STATUS_PROTECTED = 2, - RBD_PROTECTION_STATUS_LAST = 3 -}; +#include "librbd/parent_types.h" /// information about our parent image, if any struct cls_rbd_parent { diff --git a/src/librbd/cls_rbd_client.cc b/src/librbd/cls_rbd_client.cc index 43684c69e8259..a259ac95f98f6 100644 --- a/src/librbd/cls_rbd_client.cc +++ b/src/librbd/cls_rbd_client.cc @@ -1,4 +1,4 @@ -// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab #include "include/buffer.h" @@ -91,9 +91,9 @@ namespace librbd { ::decode(*lockers, iter); ::decode(*exclusive_lock, iter); // get_parent - ::decode(parent->pool_id, iter); - ::decode(parent->image_id, iter); - ::decode(parent->snap_id, iter); + ::decode(parent->spec.pool_id, iter); + ::decode(parent->spec.image_id, iter); + ::decode(parent->spec.snap_id, iter); ::decode(parent->overlap, iter); } catch (const buffer::error &err) { return -EBADMSG; @@ -184,8 +184,7 @@ namespace librbd { } int get_parent(librados::IoCtx *ioctx, const std::string &oid, - snapid_t snap_id, int64_t *parent_pool, - string *parent_image, snapid_t *parent_snap_id, + snapid_t snap_id, parent_spec *pspec, uint64_t *parent_overlap) { bufferlist inbl, outbl; @@ -197,9 +196,9 @@ namespace librbd { try { bufferlist::iterator iter = outbl.begin(); - ::decode(*parent_pool, iter); - ::decode(*parent_image, iter); - ::decode(*parent_snap_id, iter); + ::decode(pspec->pool_id, iter); + ::decode(pspec->image_id, iter); + ::decode(pspec->snap_id, iter); ::decode(*parent_overlap, iter); } catch (const buffer::error &err) { return -EBADMSG; @@ -209,13 +208,12 @@ namespace librbd { } int set_parent(librados::IoCtx *ioctx, const std::string &oid, - int64_t parent_pool, const string& parent_image, - snapid_t parent_snap_id, uint64_t parent_overlap) + parent_spec pspec, uint64_t parent_overlap) { bufferlist inbl, outbl; - ::encode(parent_pool, inbl); - ::encode(parent_image, inbl); - ::encode(parent_snap_id, inbl); + ::encode(pspec.pool_id, inbl); + ::encode(pspec.image_id, inbl); + ::encode(pspec.snap_id, inbl); ::encode(parent_overlap, inbl); return ioctx->exec(oid, "rbd", "set_parent", inbl, outbl); @@ -227,6 +225,50 @@ namespace librbd { return ioctx->exec(oid, "rbd", "remove_parent", inbl, outbl); } + int add_child(librados::IoCtx *ioctx, const std::string &oid, + parent_spec pspec, const std::string &c_imageid) + { + bufferlist in, out; + ::encode(pspec.pool_id, in); + ::encode(pspec.image_id, in); + ::encode(pspec.snap_id, in); + ::encode(c_imageid, in); + + return ioctx->exec(oid, "rbd", "add_child", in, out); + } + + int remove_child(librados::IoCtx *ioctx, const std::string &oid, + parent_spec pspec, const std::string &c_imageid) + { + bufferlist in, out; + ::encode(pspec.pool_id, in); + ::encode(pspec.image_id, in); + ::encode(pspec.snap_id, in); + ::encode(c_imageid, in); + + return ioctx->exec(oid, "rbd", "remove_child", in, out); + } + + int get_children(librados::IoCtx *ioctx, const std::string &oid, + parent_spec pspec, set& children) + { + bufferlist in, out; + ::encode(pspec.pool_id, in); + ::encode(pspec.image_id, in); + ::encode(pspec.snap_id, in); + + int r = ioctx->exec(oid, "rbd", "get_children", in, out); + if (r < 0) + return r; + bufferlist::iterator it = out.begin(); + try { + ::decode(children, it); + } catch (const buffer::error &err) { + return -EBADMSG; + } + return 0; + } + int snapshot_add(librados::IoCtx *ioctx, const std::string &oid, snapid_t snap_id, const std::string &snap_name) { @@ -323,9 +365,9 @@ namespace librbd { ::decode((*features)[i], iter); ::decode(incompat_features, iter); // get_parent - ::decode((*parents)[i].pool_id, iter); - ::decode((*parents)[i].image_id, iter); - ::decode((*parents)[i].snap_id, iter); + ::decode((*parents)[i].spec.pool_id, iter); + ::decode((*parents)[i].spec.image_id, iter); + ::decode((*parents)[i].spec.snap_id, iter); ::decode((*parents)[i].overlap, iter); // get_protection_status ::decode((*protection_statuses)[i], iter); diff --git a/src/librbd/cls_rbd_client.h b/src/librbd/cls_rbd_client.h index 81369662a1a56..3fb595adece3c 100644 --- a/src/librbd/cls_rbd_client.h +++ b/src/librbd/cls_rbd_client.h @@ -8,21 +8,13 @@ #include "include/rados.h" #include "include/rados/librados.hpp" #include "include/types.h" +#include "librbd/parent_types.h" #include #include namespace librbd { namespace cls_client { - - struct parent_info { - int64_t pool_id; - string image_id; - snapid_t snap_id; - uint64_t overlap; - parent_info() : pool_id(-1), snap_id(CEPH_NOSNAP), overlap(0) {} - }; - // high-level interface to the header int get_immutable_metadata(librados::IoCtx *ioctx, const std::string &oid, std::string *object_prefix, uint8_t *order); @@ -49,13 +41,17 @@ namespace librbd { int set_size(librados::IoCtx *ioctx, const std::string &oid, uint64_t size); int get_parent(librados::IoCtx *ioctx, const std::string &oid, - snapid_t snap_id, int64_t *parent_pool, - std::string *parent_image, snapid_t *parent_snap_id, + snapid_t snap_id, parent_spec *pspec, uint64_t *parent_overlap); int set_parent(librados::IoCtx *ioctx, const std::string &oid, - int64_t parent_pool, const std::string& parent_image, - snapid_t parent_snap_id, uint64_t parent_overlap); + parent_spec pspec, uint64_t parent_overlap); int remove_parent(librados::IoCtx *ioctx, const std::string &oid); + int add_child(librados::IoCtx *ioctx, const std::string &oid, + parent_spec pspec, const std::string &c_imageid); + int remove_child(librados::IoCtx *ioctx, const std::string &oid, + parent_spec pspec, const std::string &c_imageid); + int get_children(librados::IoCtx *ioctx, const std::string &oid, + parent_spec pspec, set& children); int snapshot_add(librados::IoCtx *ioctx, const std::string &oid, snapid_t snap_id, const std::string &snap_name); int snapshot_remove(librados::IoCtx *ioctx, const std::string &oid, diff --git a/src/librbd/internal.cc b/src/librbd/internal.cc index 51f441202323d..386ec36e60a64 100644 --- a/src/librbd/internal.cc +++ b/src/librbd/internal.cc @@ -13,6 +13,7 @@ #include "librbd/ImageCtx.h" #include "librbd/internal.h" +#include "librbd/parent_types.h" #define dout_subsys ceph_subsys_rbd #undef dout_prefix @@ -111,7 +112,8 @@ namespace librbd { info.num_objs = howmany(info.size, get_block_size(obj_order)); info.order = obj_order; memcpy(&info.block_name_prefix, ictx->object_prefix.c_str(), - min((size_t)RBD_MAX_BLOCK_NAME_SIZE, ictx->object_prefix.length())); + min((size_t)RBD_MAX_BLOCK_NAME_SIZE, + ictx->object_prefix.length() + 1)); // clear deprecated fields info.parent_pool = -1L; info.parent_name[0] = '\0'; @@ -396,6 +398,25 @@ namespace librbd { return 0; } + static int scan_for_parents(ImageCtx *ictx, parent_spec &pspec, + snapid_t oursnap_id) + { + if (pspec.pool_id != -1) { + map::iterator it; + for (it = ictx->snaps_by_name.begin(); + it != ictx->snaps_by_name.end(); ++it) { + // skip our snap id (if checking base image, CEPH_NOSNAP won't match) + if (it->second.id == oursnap_id) + continue; + if (it->second.parent.spec == pspec) + break; + } + if (it == ictx->snaps_by_name.end()) + return -ENOENT; + } + return 0; + } + int snap_remove(ImageCtx *ictx, const char *snap_name) { ldout(ictx->cct, 20) << "snap_remove " << ictx << " " << snap_name << dendl; @@ -405,11 +426,30 @@ namespace librbd { return r; Mutex::Locker l(ictx->md_lock); - ictx->snap_lock.Lock(); - snap_t snap_id = ictx->get_snap_id(snap_name); - ictx->snap_lock.Unlock(); - if (snap_id == CEPH_NOSNAP) - return -ENOENT; + snap_t snap_id; + + { + // block for purposes of auto-destruction of l2 on early return + Mutex::Locker l2(ictx->snap_lock); + snap_id = ictx->get_snap_id(snap_name); + if (snap_id == CEPH_NOSNAP) + return -ENOENT; + + parent_spec our_pspec; + r = ictx->get_parent_spec(snap_id, &our_pspec); + if (r < 0) { + lderr(ictx->cct) << "snap_remove: can't get parent spec" << dendl; + return r; + } + + if (ictx->parent_md.spec != our_pspec && + (scan_for_parents(ictx, our_pspec, snap_id) == -ENOENT)) { + r = cls_client::remove_child(&ictx->md_ctx, RBD_CHILDREN, + our_pspec, ictx->id); + if (r < 0) + return r; + } + } r = rm_snap(ictx, snap_name); if (r < 0) @@ -437,10 +477,25 @@ namespace librbd { Mutex::Locker l(ictx->md_lock); Mutex::Locker l2(ictx->snap_lock); + uint64_t features; + ictx->get_features(ictx->snap_id, &features); + if ((features & RBD_FEATURE_LAYERING) == 0) { + lderr(ictx->cct) << "snap_protect: image must support layering" + << dendl; + return -ENOSYS; + } snap_t snap_id = ictx->get_snap_id(snap_name); if (snap_id == CEPH_NOSNAP) return -ENOENT; + bool is_protected; + r = ictx->is_snap_protected(snap_name, &is_protected); + if (r < 0) + return r; + + if (is_protected) + return -EBUSY; + r = cls_client::set_protection_status(&ictx->md_ctx, ictx->header_oid, snap_id, @@ -462,20 +517,82 @@ namespace librbd { Mutex::Locker l(ictx->md_lock); Mutex::Locker l2(ictx->snap_lock); + uint64_t features; + ictx->get_features(ictx->snap_id, &features); + if ((features & RBD_FEATURE_LAYERING) == 0) { + lderr(ictx->cct) << "snap_unprotect: image must support layering" + << dendl; + return -ENOSYS; + } snap_t snap_id = ictx->get_snap_id(snap_name); if (snap_id == CEPH_NOSNAP) return -ENOENT; - // TODO: go through the unprotecting state as well, once - // rbd_children is implemented + bool is_protected; + r = ictx->is_snap_protected(snap_name, &is_protected); + if (r < 0) + return r; + + if (!is_protected) + return -EINVAL; + r = cls_client::set_protection_status(&ictx->md_ctx, ictx->header_oid, snap_id, - RBD_PROTECTION_STATUS_UNPROTECTED); + RBD_PROTECTION_STATUS_UNPROTECTING); if (r < 0) return r; notify_change(ictx->md_ctx, ictx->header_oid, NULL, ictx); + + parent_spec pspec(ictx->md_ctx.get_id(), ictx->id, + ictx->snap_id); + // search all pools for children depending on this snapshot + Rados rados(ictx->md_ctx); + std::list pools; + rados.pool_list(pools); + std::list::const_iterator it; + std::set children; + for (it = pools.begin(); it != pools.end(); it++) { + IoCtx pool_ioctx; + r = rados.ioctx_create(it->c_str(), pool_ioctx); + if (r < 0) { + lderr(ictx->cct) << "snap_unprotect: can't create ioctx for pool " + << *it << dendl; + goto reprotect_and_return_err; + } + r = cls_client::get_children(&pool_ioctx, RBD_CHILDREN, pspec, children); + // key should not exist for this parent if there is no entry + if (((r < 0) && (r != -ENOENT))) { + lderr(ictx->cct) << "can't get children for pool " << *it << dendl; + goto reprotect_and_return_err; + } + // if we found a child, can't unprotect + if (r == 0) { + lderr(ictx->cct) << "snap_unprotect: can't unprotect; at least " + << children.size() << " child(ren) in pool " << it->c_str() << dendl; + r = -EBUSY; + goto reprotect_and_return_err; + } + pool_ioctx.close(); // last one out will self-destruct + } + // didn't find any child in any pool, go ahead with unprotect + r = cls_client::set_protection_status(&ictx->md_ctx, + ictx->header_oid, + snap_id, + RBD_PROTECTION_STATUS_UNPROTECTED); + notify_change(ictx->md_ctx, ictx->header_oid, NULL, ictx); return 0; + +reprotect_and_return_err: + int proterr = cls_client::set_protection_status(&ictx->md_ctx, + ictx->header_oid, + snap_id, + RBD_PROTECTION_STATUS_PROTECTED); + if (proterr < 0) { + lderr(ictx->cct) << "snap_unprotect: can't reprotect image" << dendl; + } + notify_change(ictx->md_ctx, ictx->header_oid, NULL, ictx); + return r; } int snap_is_protected(ImageCtx *ictx, const char *snap_name, @@ -621,7 +738,6 @@ namespace librbd { int order; uint64_t size; uint64_t p_features; - uint64_t p_poolid; int remove_r; librbd::NoOpProgressContext no_op; ImageCtx *c_imctx = NULL; @@ -634,6 +750,9 @@ namespace librbd { return r; } + parent_spec pspec(p_ioctx.get_id(), p_imctx->id, + p_imctx->snap_id); + if (p_imctx->old_format) { lderr(cct) << "parent image must be in new format" << dendl; r = -EINVAL; @@ -650,13 +769,13 @@ namespace librbd { if ((p_features & RBD_FEATURE_LAYERING) != RBD_FEATURE_LAYERING) { lderr(cct) << "parent image must support layering" << dendl; - r = -EINVAL; + r = -ENOSYS; goto err_close_parent; } if (!snap_protected) { lderr(cct) << "parent snapshot must be protected" << dendl; - r = -ENOSYS; + r = -EINVAL; goto err_close_parent; } @@ -670,8 +789,6 @@ namespace librbd { goto err_close_parent; } - p_poolid = p_ioctx.get_id(); - c_imctx = new ImageCtx(c_name, "", NULL, c_ioctx); r = open_image(c_imctx, true); if (r < 0) { @@ -679,12 +796,17 @@ namespace librbd { goto err_remove; } - r = cls_client::set_parent(&c_ioctx, c_imctx->header_oid, p_poolid, - p_imctx->id, p_imctx->snap_id, size); + r = cls_client::set_parent(&c_ioctx, c_imctx->header_oid, pspec, size); if (r < 0) { lderr(cct) << "couldn't set parent: " << r << dendl; goto err_close_child; } + + r = cls_client::add_child(&c_ioctx, RBD_CHILDREN, pspec, c_imctx->id); + if (r < 0) { + lderr(cct) << "couldn't add child: " << r << dendl; + goto err_close_child; + } ldout(cct, 2) << "done." << dendl; close_image(c_imctx); close_image(p_imctx); @@ -931,24 +1053,46 @@ namespace librbd { Mutex::Locker l(ictx->snap_lock); Mutex::Locker l2(ictx->parent_lock); - if (!ictx->parent) - return -ENOENT; + parent_spec parent_spec; + + if (ictx->snap_id == CEPH_NOSNAP) { + if (!ictx->parent) + return -ENOENT; + parent_spec = ictx->parent_md.spec; + } else { + r = ictx->get_parent_spec(ictx->snap_id, &parent_spec); + if (r < 0) { + lderr(ictx->cct) << "Can't find snapshot id" << ictx->snap_id << dendl; + return r; + } + if (parent_spec.pool_id == -1) + return 0; + } if (parent_pool_name) { Rados rados(ictx->md_ctx); - r = rados.pool_reverse_lookup(ictx->parent->md_ctx.get_id(), + r = rados.pool_reverse_lookup(parent_spec.pool_id, parent_pool_name); if (r < 0) { - lderr(ictx->cct) << "error looking up pool name" << dendl; + lderr(ictx->cct) << "error looking up pool name" << cpp_strerror(r) + << dendl; } } - if (parent_snap_name) - *parent_snap_name = ictx->parent->snap_name; + if (parent_snap_name) { + Mutex::Locker l(ictx->parent->snap_lock); + r = ictx->parent->get_snap_name(parent_spec.snap_id, + parent_snap_name); + if (r < 0) { + lderr(ictx->cct) << "error finding parent snap name: " + << cpp_strerror(r) << dendl; + return r; + } + } if (parent_name) { r = cls_client::dir_get_name(&ictx->parent->md_ctx, RBD_DIRECTORY, - ictx->parent->id, parent_name); + parent_spec.image_id, parent_name); if (r < 0) { lderr(ictx->cct) << "error getting parent image name: " << cpp_strerror(r) << dendl; @@ -984,6 +1128,20 @@ namespace librbd { ictx->md_lock.Lock(); trim_image(ictx, 0, prog_ctx); ictx->md_lock.Unlock(); + + ictx->parent_lock.Lock(); + // struct assignment + parent_info parent_info = ictx->parent_md; + ictx->parent_lock.Unlock(); + + if (scan_for_parents(ictx, parent_info.spec, CEPH_NOSNAP) == -ENOENT) { + r = cls_client::remove_child(&ictx->md_ctx, RBD_CHILDREN, + parent_info.spec, id); + if (r < 0) { + lderr(cct) << "error removing child from children list" << dendl; + return r; + } + } close_image(ictx); ldout(cct, 2) << "removing header..." << dendl; @@ -1025,7 +1183,7 @@ namespace librbd { << cpp_strerror(-r) << dendl; return r; } - } + } ldout(cct, 2) << "done." << dendl; return 0; @@ -1045,7 +1203,7 @@ namespace librbd { if (size > ictx->size) { ldout(cct, 2) << "expanding image " << ictx->size << " -> " << size << dendl; - // TODO: make ictx->set_size + // TODO: make ictx->set_size } else { ldout(cct, 2) << "shrinking image " << ictx->size << " -> " << size << dendl; @@ -1250,7 +1408,7 @@ namespace librbd { vector snap_names; vector snap_sizes; vector snap_features; - vector snap_parents; + vector snap_parents; vector snap_protection; { Mutex::Locker l(ictx->snap_lock); @@ -1311,7 +1469,7 @@ namespace librbd { for (size_t i = 0; i < new_snapc.snaps.size(); ++i) { uint64_t features = ictx->old_format ? 0 : snap_features[i]; - cls_client::parent_info parent; + parent_info parent; if (!ictx->old_format) parent = snap_parents[i]; vector::const_iterator it = @@ -1332,7 +1490,7 @@ namespace librbd { uint64_t features = ictx->old_format ? 0 : snap_features[i]; uint8_t protection_status = ictx->old_format ? (uint8_t)RBD_PROTECTION_STATUS_UNPROTECTED : snap_protection[i]; - cls_client::parent_info parent; + parent_info parent; if (!ictx->old_format) parent = snap_parents[i]; ictx->add_snap(snap_names[i], new_snapc.snaps[i].val, @@ -1490,6 +1648,26 @@ namespace librbd { return r; } + // common snap_set functionality for snap_set and open_image + + int _snap_set(ImageCtx *ictx, const char *snap_name) + { + Mutex::Locker l1(ictx->snap_lock); + Mutex::Locker l2(ictx->parent_lock); + int r; + if ((snap_name != NULL) && (strlen(snap_name) != 0)) { + r = ictx->snap_set(snap_name); + } else { + ictx->snap_unset(); + r = 0; + } + if (r < 0) { + return r; + } + refresh_parent(ictx); + return 0; + } + int snap_set(ImageCtx *ictx, const char *snap_name) { ldout(ictx->cct, 20) << "snap_set " << ictx << " snap = " @@ -1497,18 +1675,7 @@ namespace librbd { // ignore return value, since we may be set to a non-existent // snapshot and the user is trying to fix that ictx_check(ictx); - - Mutex::Locker l(ictx->snap_lock); - if (snap_name) { - int r = ictx->snap_set(snap_name); - if (r < 0) { - return r; - } - } else { - ictx->snap_unset(); - } - - return 0; + return _snap_set(ictx, snap_name); } int open_image(ImageCtx *ictx, bool watch) @@ -1528,12 +1695,7 @@ namespace librbd { if (r < 0) return r; - if (ictx->snap_name.length()) { - Mutex::Locker l(ictx->snap_lock); - r = ictx->snap_set(ictx->snap_name); - if (r < 0) - return r; - } + _snap_set(ictx, ictx->snap_name.c_str()); if (watch) { r = ictx->register_watch(); @@ -1620,7 +1782,7 @@ namespace librbd { Mutex::Locker l2(ictx->snap_lock); Mutex::Locker l3(ictx->parent_lock); // can't flatten a non-clone - if (ictx->parent_md.pool_id == -1) { + if (ictx->parent_md.spec.pool_id == -1) { lderr(ictx->cct) << "image has no parent" << dendl; return -EINVAL; } @@ -1655,7 +1817,25 @@ namespace librbd { ofs += cblksize; } + // remove parent from this (base) image r = cls_client::remove_parent(&ictx->md_ctx, ictx->header_oid); + if (r < 0) { + lderr(ictx->cct) << "error removing parent" << dendl; + return r; + } + + // and if there are no snaps, remove from the children object as well + // (if snapshots remain, they have their own parent info, and the child + // will be removed when the last snap goes away) + if (ictx->snaps.empty()) { + ldout(ictx->cct, 2) << "removing child from children list..." << dendl; + int r = cls_client::remove_child(&ictx->md_ctx, RBD_CHILDREN, + ictx->parent_md.spec, ictx->id); + if (r < 0) { + lderr(ictx->cct) << "error removing child from children list" << dendl; + return r; + } + } notify_change(ictx->md_ctx, ictx->header_oid, NULL, ictx); ldout(ictx->cct, 20) << "finished flattening" << dendl; diff --git a/src/librbd/parent_types.h b/src/librbd/parent_types.h new file mode 100644 index 0000000000000..4dcc452962f95 --- /dev/null +++ b/src/librbd/parent_types.h @@ -0,0 +1,41 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +#ifndef CEPH_LIBRBD_PARENT_TYPES_H +#define CEPH_LIBRBD_PARENT_TYPES_H + +// parent_spec uniquely identifies a parent in the clone relationship +// (clone(parent) creates child, then parent_spec <-> child_imageid) + +namespace librbd { + struct parent_spec { + int64_t pool_id; + string image_id; + snapid_t snap_id; + parent_spec() : pool_id(-1), snap_id(CEPH_NOSNAP) {} + parent_spec(uint64_t pool_id, string image_id, snapid_t snap_id) : + pool_id(pool_id), image_id(image_id), snap_id(snap_id) {} + bool operator==(const parent_spec &other) { + return ((this->pool_id == other.pool_id) && + (this->image_id == other.image_id) && + (this->snap_id == other.snap_id)); + } + bool operator!=(const parent_spec &other) { + return !(*this == other); + } + }; + + struct parent_info { + parent_spec spec; + uint64_t overlap; + parent_info() : overlap(0) {} + }; +} + +enum { + RBD_PROTECTION_STATUS_UNPROTECTED = 0, + RBD_PROTECTION_STATUS_UNPROTECTING = 1, + RBD_PROTECTION_STATUS_PROTECTED = 2, + RBD_PROTECTION_STATUS_LAST = 3 +}; + +#endif diff --git a/src/rados.cc b/src/rados.cc index b75ea29b84dcd..2bdc3b20b6318 100644 --- a/src/rados.cc +++ b/src/rados.cc @@ -40,6 +40,7 @@ using namespace librados; #include #include #include +#include #include "common/errno.h" @@ -1549,7 +1550,18 @@ static int rados_tool_common(const std::map < std::string, std::string > &opts, } for (map::const_iterator it = values.begin(); it != values.end(); ++it) { - cout << it->first << " (" << it->second.length() << " bytes) :\n"; + // dump key in hex if it contains nonprintable characters + if (std::count_if(it->first.begin(), it->first.end(), + (int (*)(int))isprint) < (int)it->first.length()) { + cout << "key: (" << it->first.length() << " bytes):\n"; + bufferlist keybl; + keybl.append(it->first); + keybl.hexdump(cout); + } else { + cout << it->first; + } + cout << std::endl; + cout << "value: (" << it->second.length() << " bytes) :\n"; it->second.hexdump(cout); cout << std::endl; } diff --git a/src/test/pybind/test_rbd.py b/src/test/pybind/test_rbd.py index b71c25e2a8d0a..2f15c7eacb65a 100644 --- a/src/test/pybind/test_rbd.py +++ b/src/test/pybind/test_rbd.py @@ -8,7 +8,7 @@ from rados import Rados from rbd import (RBD, Image, ImageNotFound, InvalidArgument, ImageExists, ImageBusy, ImageHasSnapshots, ReadOnlyImage, - RBD_FEATURE_LAYERING) + FunctionNotSupported, RBD_FEATURE_LAYERING) rados = None @@ -425,9 +425,48 @@ def tearDown(self): def test_unprotected(self): self.image.create_snap('snap2') global features - assert_raises(FunctionNotSupported, self.rbd.clone, ioctx, IMG_NAME, 'snap2', ioctx, 'clone2', features) + assert_raises(InvalidArgument, self.rbd.clone, ioctx, IMG_NAME, 'snap2', ioctx, 'clone2', features) self.image.remove_snap('snap2') + def test_unprotect_with_children(self): + global features + # can't remove a snapshot that has dependent clones + assert_raises(ImageBusy, self.image.remove_snap, 'snap1') + + # validate parent info of clone created by TestClone.setUp + (pool, image, snap) = self.clone.parent_info() + eq(pool, 'rbd') + eq(image, IMG_NAME) + eq(snap, 'snap1') + + # create a new pool... + rados.create_pool('rbd2') + other_ioctx = rados.open_ioctx('rbd2') + + # ...with a clone of the same parent + self.rbd.clone(ioctx, IMG_NAME, 'snap1', other_ioctx, 'other_clone', features) + self.other_clone = Image(other_ioctx, 'other_clone') + # validate its parent info + (pool, image, snap) = self.other_clone.parent_info() + eq(pool, 'rbd') + eq(image, IMG_NAME) + eq(snap, 'snap1') + + # 2 children, check that cannot remove the parent snap + assert_raises(ImageBusy, self.image.remove_snap, 'snap1') + + # close and remove other pool's clone + self.other_clone.close() + self.rbd.remove(other_ioctx, 'other_clone') + + # check that we cannot yet remove the parent snap + assert_raises(ImageBusy, self.image.remove_snap, 'snap1') + + other_ioctx.close() + rados.delete_pool('rbd2') + + # unprotect, remove parent snap happen in cleanup, and should succeed + def test_stat(self): image_info = self.image.stat() clone_info = self.clone.stat() diff --git a/src/test/rbd/test_cls_rbd.cc b/src/test/rbd/test_cls_rbd.cc index 39011f9454814..04ace57ed6990 100644 --- a/src/test/rbd/test_cls_rbd.cc +++ b/src/test/rbd/test_cls_rbd.cc @@ -27,6 +27,9 @@ using ::librbd::cls_client::set_parent; using ::librbd::cls_client::remove_parent; using ::librbd::cls_client::snapshot_add; using ::librbd::cls_client::snapshot_remove; +using ::librbd::cls_client::add_child; +using ::librbd::cls_client::remove_child; +using ::librbd::cls_client::get_children; using ::librbd::cls_client::get_snapcontext; using ::librbd::cls_client::snapshot_list; using ::librbd::cls_client::list_locks; @@ -43,7 +46,8 @@ using ::librbd::cls_client::dir_list; using ::librbd::cls_client::dir_add_image; using ::librbd::cls_client::dir_remove_image; using ::librbd::cls_client::dir_rename_image; -using ::librbd::cls_client::parent_info; +using ::librbd::parent_info; +using ::librbd::parent_spec; using ::librbd::cls_client::get_protection_status; using ::librbd::cls_client::set_protection_status; @@ -141,6 +145,57 @@ TEST(cls_rbd, get_and_set_id) ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados)); } +TEST(cls_rbd, add_remove_child) +{ + librados::Rados rados; + librados::IoCtx ioctx; + string pool_name = get_temp_pool_name(); + + ASSERT_EQ("", create_one_pool_pp(pool_name, rados)); + ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx)); + + string oid = "rbd_children_test"; + ASSERT_EQ(0, ioctx.create(oid, true)); + + string snapname = "parent_snap"; + snapid_t snapid(10); + string parent_image = "parent_id"; + setchildren; + parent_spec pspec(ioctx.get_id(), parent_image, snapid); + + // nonexistent children cannot be listed or removed + ASSERT_EQ(-ENOENT, get_children(&ioctx, oid, pspec, children)); + ASSERT_EQ(-ENOENT, remove_child(&ioctx, oid, pspec, "child1")); + + // create the parent and snapshot + ASSERT_EQ(0, create_image(&ioctx, parent_image, 2<<20, 0, + RBD_FEATURE_LAYERING, parent_image)); + ASSERT_EQ(0, snapshot_add(&ioctx, parent_image, snapid, snapname)); + + // add child to it, verify it showed up + ASSERT_EQ(0, add_child(&ioctx, oid, pspec, "child1")); + ASSERT_EQ(0, get_children(&ioctx, oid, pspec, children)); + ASSERT_TRUE(children.find("child1") != children.end()); + // add another child to it, verify it showed up + ASSERT_EQ(0, add_child(&ioctx, oid, pspec, "child2")); + ASSERT_EQ(0, get_children(&ioctx, oid, pspec, children)); + ASSERT_TRUE(children.find("child2") != children.end()); + // add child2 again, expect -EEXIST + ASSERT_EQ(-EEXIST, add_child(&ioctx, oid, pspec, "child2")); + // remove first, verify it's gone + ASSERT_EQ(0, remove_child(&ioctx, oid, pspec, "child1")); + ASSERT_EQ(0, get_children(&ioctx, oid, pspec, children)); + ASSERT_FALSE(children.find("child1") != children.end()); + // remove second, verify list empty + ASSERT_EQ(0, remove_child(&ioctx, oid, pspec, "child2")); + ASSERT_EQ(-ENOENT, get_children(&ioctx, oid, pspec, children)); + // try to remove again, validate -ENOENT to that as well + ASSERT_EQ(-ENOENT, remove_child(&ioctx, oid, pspec, "child2")); + + ioctx.close(); + ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados)); +} + TEST(cls_rbd, directory_methods) { librados::Rados rados; @@ -564,147 +619,152 @@ TEST(cls_rbd, parents) ASSERT_EQ("", create_one_pool_pp(pool_name, rados)); ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx)); - int64_t pool; - string parent; - snapid_t snapid; + parent_spec pspec; uint64_t size; - ASSERT_EQ(-ENOENT, get_parent(&ioctx, "doesnotexist", CEPH_NOSNAP, &pool, &parent, &snapid, &size)); + ASSERT_EQ(-ENOENT, get_parent(&ioctx, "doesnotexist", CEPH_NOSNAP, &pspec, &size)); // old image should fail ASSERT_EQ(0, create_image(&ioctx, "old", 33<<20, 22, 0, "old_blk.")); - ASSERT_EQ(-ENOEXEC, get_parent(&ioctx, "old", CEPH_NOSNAP, &pool, &parent, &snapid, &size)); - ASSERT_EQ(-ENOEXEC, set_parent(&ioctx, "old", -1, "parent", 3, 10<<20)); + // get nonexistent parent: succeed, return (-1, "", CEPH_NOSNAP), overlap 0 + ASSERT_EQ(0, get_parent(&ioctx, "old", CEPH_NOSNAP, &pspec, &size)); + ASSERT_EQ(pspec.pool_id, -1); + ASSERT_STREQ("", pspec.image_id.c_str()); + ASSERT_EQ(pspec.snap_id, CEPH_NOSNAP); + ASSERT_EQ(size, 0ULL); + pspec = parent_spec(-1, "parent", 3); + ASSERT_EQ(-ENOEXEC, set_parent(&ioctx, "old", parent_spec(-1, "parent", 3), 10<<20)); ASSERT_EQ(-ENOEXEC, remove_parent(&ioctx, "old")); // new image will work ASSERT_EQ(0, create_image(&ioctx, "foo", 33<<20, 22, RBD_FEATURE_LAYERING, "foo.")); - ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pool, &parent, &snapid, &size)); - ASSERT_EQ(-1, pool); - ASSERT_EQ(0, get_parent(&ioctx, "foo", 123, &pool, &parent, &snapid, &size)); - ASSERT_EQ(-1, pool); + ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size)); + ASSERT_EQ(-1, pspec.pool_id); + ASSERT_EQ(0, get_parent(&ioctx, "foo", 123, &pspec, &size)); + ASSERT_EQ(-1, pspec.pool_id); - ASSERT_EQ(-EINVAL, set_parent(&ioctx, "foo", -1, "parent", 3, 10<<20)); - ASSERT_EQ(-EINVAL, set_parent(&ioctx, "foo", 1, "", 3, 10<<20)); - ASSERT_EQ(-EINVAL, set_parent(&ioctx, "foo", 1, "parent", CEPH_NOSNAP, 10<<20)); - ASSERT_EQ(-EINVAL, set_parent(&ioctx, "foo", 1, "parent", 3, 0)); + ASSERT_EQ(-EINVAL, set_parent(&ioctx, "foo", parent_spec(-1, "parent", 3), 10<<20)); + ASSERT_EQ(-EINVAL, set_parent(&ioctx, "foo", parent_spec(1, "", 3), 10<<20)); + ASSERT_EQ(-EINVAL, set_parent(&ioctx, "foo", parent_spec(1, "parent", CEPH_NOSNAP), 10<<20)); + ASSERT_EQ(-EINVAL, set_parent(&ioctx, "foo", parent_spec(1, "parent", 3), 0)); - ASSERT_EQ(0, set_parent(&ioctx, "foo", 1, "parent", 3, 10<<20)); - ASSERT_EQ(-EEXIST, set_parent(&ioctx, "foo", 1, "parent", 3, 10<<20)); - ASSERT_EQ(-EEXIST, set_parent(&ioctx, "foo", 2, "parent", 34, 10<<20)); + pspec = parent_spec(1, "parent", 3); + ASSERT_EQ(0, set_parent(&ioctx, "foo", pspec, 10<<20)); + ASSERT_EQ(-EEXIST, set_parent(&ioctx, "foo", pspec, 10<<20)); + ASSERT_EQ(-EEXIST, set_parent(&ioctx, "foo", parent_spec(2, "parent", 34), 10<<20)); - ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pool, &parent, &snapid, &size)); - ASSERT_EQ(pool, 1); - ASSERT_EQ(parent, "parent"); - ASSERT_EQ(snapid, snapid_t(3)); + ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size)); + ASSERT_EQ(pspec.pool_id, 1); + ASSERT_EQ(pspec.image_id, "parent"); + ASSERT_EQ(pspec.snap_id, snapid_t(3)); ASSERT_EQ(0, remove_parent(&ioctx, "foo")); ASSERT_EQ(-ENOENT, remove_parent(&ioctx, "foo")); - ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pool, &parent, &snapid, &size)); - ASSERT_EQ(-1, pool); + ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size)); + ASSERT_EQ(-1, pspec.pool_id); // snapshots - ASSERT_EQ(0, set_parent(&ioctx, "foo", 1, "parent", 3, 10<<20)); + ASSERT_EQ(0, set_parent(&ioctx, "foo", parent_spec(1, "parent", 3), 10<<20)); ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 10, "snap1")); - ASSERT_EQ(0, get_parent(&ioctx, "foo", 10, &pool, &parent, &snapid, &size)); - ASSERT_EQ(pool, 1); - ASSERT_EQ(parent, "parent"); - ASSERT_EQ(snapid, snapid_t(3)); + ASSERT_EQ(0, get_parent(&ioctx, "foo", 10, &pspec, &size)); + ASSERT_EQ(pspec.pool_id, 1); + ASSERT_EQ(pspec.image_id, "parent"); + ASSERT_EQ(pspec.snap_id, snapid_t(3)); ASSERT_EQ(size, 10ull<<20); ASSERT_EQ(0, remove_parent(&ioctx, "foo")); - ASSERT_EQ(0, set_parent(&ioctx, "foo", 4, "parent2", 6, 5<<20)); + ASSERT_EQ(0, set_parent(&ioctx, "foo", parent_spec(4, "parent2", 6), 5<<20)); ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 11, "snap2")); - ASSERT_EQ(0, get_parent(&ioctx, "foo", 10, &pool, &parent, &snapid, &size)); - ASSERT_EQ(pool, 1); - ASSERT_EQ(parent, "parent"); - ASSERT_EQ(snapid, snapid_t(3)); + ASSERT_EQ(0, get_parent(&ioctx, "foo", 10, &pspec, &size)); + ASSERT_EQ(pspec.pool_id, 1); + ASSERT_EQ(pspec.image_id, "parent"); + ASSERT_EQ(pspec.snap_id, snapid_t(3)); ASSERT_EQ(size, 10ull<<20); - ASSERT_EQ(0, get_parent(&ioctx, "foo", 11, &pool, &parent, &snapid, &size)); - ASSERT_EQ(pool, 4); - ASSERT_EQ(parent, "parent2"); - ASSERT_EQ(snapid, snapid_t(6)); + ASSERT_EQ(0, get_parent(&ioctx, "foo", 11, &pspec, &size)); + ASSERT_EQ(pspec.pool_id, 4); + ASSERT_EQ(pspec.image_id, "parent2"); + ASSERT_EQ(pspec.snap_id, snapid_t(6)); ASSERT_EQ(size, 5ull<<20); ASSERT_EQ(0, remove_parent(&ioctx, "foo")); ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 12, "snap3")); - ASSERT_EQ(0, get_parent(&ioctx, "foo", 10, &pool, &parent, &snapid, &size)); - ASSERT_EQ(pool, 1); - ASSERT_EQ(parent, "parent"); - ASSERT_EQ(snapid, snapid_t(3)); + ASSERT_EQ(0, get_parent(&ioctx, "foo", 10, &pspec, &size)); + ASSERT_EQ(pspec.pool_id, 1); + ASSERT_EQ(pspec.image_id, "parent"); + ASSERT_EQ(pspec.snap_id, snapid_t(3)); ASSERT_EQ(size, 10ull<<20); - ASSERT_EQ(0, get_parent(&ioctx, "foo", 11, &pool, &parent, &snapid, &size)); - ASSERT_EQ(pool, 4); - ASSERT_EQ(parent, "parent2"); - ASSERT_EQ(snapid, snapid_t(6)); + ASSERT_EQ(0, get_parent(&ioctx, "foo", 11, &pspec, &size)); + ASSERT_EQ(pspec.pool_id, 4); + ASSERT_EQ(pspec.image_id, "parent2"); + ASSERT_EQ(pspec.snap_id, snapid_t(6)); ASSERT_EQ(size, 5ull<<20); - ASSERT_EQ(0, get_parent(&ioctx, "foo", 12, &pool, &parent, &snapid, &size)); - ASSERT_EQ(-1, pool); + ASSERT_EQ(0, get_parent(&ioctx, "foo", 12, &pspec, &size)); + ASSERT_EQ(-1, pspec.pool_id); // make sure set_parent takes min of our size and parent's size - ASSERT_EQ(0, set_parent(&ioctx, "foo", 1, "parent", 3, 1<<20)); - ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pool, &parent, &snapid, &size)); - ASSERT_EQ(pool, 1); - ASSERT_EQ(parent, "parent"); - ASSERT_EQ(snapid, snapid_t(3)); + ASSERT_EQ(0, set_parent(&ioctx, "foo", parent_spec(1, "parent", 3), 1<<20)); + ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size)); + ASSERT_EQ(pspec.pool_id, 1); + ASSERT_EQ(pspec.image_id, "parent"); + ASSERT_EQ(pspec.snap_id, snapid_t(3)); ASSERT_EQ(size, 1ull<<20); ASSERT_EQ(0, remove_parent(&ioctx, "foo")); - ASSERT_EQ(0, set_parent(&ioctx, "foo", 1, "parent", 3, 100<<20)); - ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pool, &parent, &snapid, &size)); - ASSERT_EQ(pool, 1); - ASSERT_EQ(parent, "parent"); - ASSERT_EQ(snapid, snapid_t(3)); + ASSERT_EQ(0, set_parent(&ioctx, "foo", parent_spec(1, "parent", 3), 100<<20)); + ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size)); + ASSERT_EQ(pspec.pool_id, 1); + ASSERT_EQ(pspec.image_id, "parent"); + ASSERT_EQ(pspec.snap_id, snapid_t(3)); ASSERT_EQ(size, 33ull<<20); ASSERT_EQ(0, remove_parent(&ioctx, "foo")); // make sure resize adjust parent overlap - ASSERT_EQ(0, set_parent(&ioctx, "foo", 1, "parent", 3, 10<<20)); + ASSERT_EQ(0, set_parent(&ioctx, "foo", parent_spec(1, "parent", 3), 10<<20)); ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 14, "snap4")); ASSERT_EQ(0, set_size(&ioctx, "foo", 3 << 20)); - ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pool, &parent, &snapid, &size)); - ASSERT_EQ(pool, 1); - ASSERT_EQ(parent, "parent"); - ASSERT_EQ(snapid, snapid_t(3)); + ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size)); + ASSERT_EQ(pspec.pool_id, 1); + ASSERT_EQ(pspec.image_id, "parent"); + ASSERT_EQ(pspec.snap_id, snapid_t(3)); ASSERT_EQ(size, 3ull<<20); - ASSERT_EQ(0, get_parent(&ioctx, "foo", 14, &pool, &parent, &snapid, &size)); - ASSERT_EQ(pool, 1); - ASSERT_EQ(parent, "parent"); - ASSERT_EQ(snapid, snapid_t(3)); + ASSERT_EQ(0, get_parent(&ioctx, "foo", 14, &pspec, &size)); + ASSERT_EQ(pspec.pool_id, 1); + ASSERT_EQ(pspec.image_id, "parent"); + ASSERT_EQ(pspec.snap_id, snapid_t(3)); ASSERT_EQ(size, 10ull<<20); ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 15, "snap5")); ASSERT_EQ(0, set_size(&ioctx, "foo", 30 << 20)); - ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pool, &parent, &snapid, &size)); - ASSERT_EQ(pool, 1); - ASSERT_EQ(parent, "parent"); - ASSERT_EQ(snapid, snapid_t(3)); + ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size)); + ASSERT_EQ(pspec.pool_id, 1); + ASSERT_EQ(pspec.image_id, "parent"); + ASSERT_EQ(pspec.snap_id, snapid_t(3)); ASSERT_EQ(size, 3ull<<20); - ASSERT_EQ(0, get_parent(&ioctx, "foo", 14, &pool, &parent, &snapid, &size)); - ASSERT_EQ(pool, 1); - ASSERT_EQ(parent, "parent"); - ASSERT_EQ(snapid, snapid_t(3)); + ASSERT_EQ(0, get_parent(&ioctx, "foo", 14, &pspec, &size)); + ASSERT_EQ(pspec.pool_id, 1); + ASSERT_EQ(pspec.image_id, "parent"); + ASSERT_EQ(pspec.snap_id, snapid_t(3)); ASSERT_EQ(size, 10ull<<20); - ASSERT_EQ(0, get_parent(&ioctx, "foo", 15, &pool, &parent, &snapid, &size)); - ASSERT_EQ(pool, 1); - ASSERT_EQ(parent, "parent"); - ASSERT_EQ(snapid, snapid_t(3)); + ASSERT_EQ(0, get_parent(&ioctx, "foo", 15, &pspec, &size)); + ASSERT_EQ(pspec.pool_id, 1); + ASSERT_EQ(pspec.image_id, "parent"); + ASSERT_EQ(pspec.snap_id, snapid_t(3)); ASSERT_EQ(size, 3ull<<20); ASSERT_EQ(0, set_size(&ioctx, "foo", 2 << 20)); - ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pool, &parent, &snapid, &size)); - ASSERT_EQ(pool, 1); - ASSERT_EQ(parent, "parent"); - ASSERT_EQ(snapid, snapid_t(3)); + ASSERT_EQ(0, get_parent(&ioctx, "foo", CEPH_NOSNAP, &pspec, &size)); + ASSERT_EQ(pspec.pool_id, 1); + ASSERT_EQ(pspec.image_id, "parent"); + ASSERT_EQ(pspec.snap_id, snapid_t(3)); ASSERT_EQ(size, 2ull<<20); ASSERT_EQ(0, snapshot_add(&ioctx, "foo", 16, "snap6")); - ASSERT_EQ(0, get_parent(&ioctx, "foo", 16, &pool, &parent, &snapid, &size)); - ASSERT_EQ(pool, 1); - ASSERT_EQ(parent, "parent"); - ASSERT_EQ(snapid, snapid_t(3)); + ASSERT_EQ(0, get_parent(&ioctx, "foo", 16, &pspec, &size)); + ASSERT_EQ(pspec.pool_id, 1); + ASSERT_EQ(pspec.image_id, "parent"); + ASSERT_EQ(pspec.snap_id, snapid_t(3)); ASSERT_EQ(size, 2ull<<20); ioctx.close(); diff --git a/src/test/run-rbd-tests b/src/test/run-rbd-tests index 229a437b8e87a..6b82bede3b721 100755 --- a/src/test/run-rbd-tests +++ b/src/test/run-rbd-tests @@ -5,7 +5,7 @@ CEPH_SRC=$(pwd) export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$CEPH_SRC/.libs" export PYTHONPATH="$CEPH_SRC/pybind:$CEPH_SRC/test/pybind" -PATH="$PATH:$CEPH_SRC" +PATH="$CEPH_SRC:$PATH" recreate_pool() { POOL_NAME=$1 diff --git a/src/test/test_librbd.cc b/src/test/test_librbd.cc index 2022298afb26b..c6ba3f8277c8f 100644 --- a/src/test/test_librbd.cc +++ b/src/test/test_librbd.cc @@ -1026,14 +1026,21 @@ TEST(LibRBD, TestClone) ASSERT_EQ(0, rbd_close(parent)); ASSERT_EQ(0, rbd_open(ioctx, "parent", &parent, "parent_snap")); - ASSERT_EQ(-ENOSYS, rbd_clone(ioctx, "parent", "parent_snap", ioctx, "child", features, - &order)); + ASSERT_EQ(-EINVAL, rbd_clone(ioctx, "parent", "parent_snap", ioctx, "child", + features, &order)); + + // unprotected image should fail unprotect + ASSERT_EQ(-EINVAL, rbd_snap_unprotect(parent, "parent_snap")); + printf("can't unprotect an unprotected snap\n"); ASSERT_EQ(0, rbd_snap_protect(parent, "parent_snap")); + // protecting again should fail + ASSERT_EQ(-EBUSY, rbd_snap_protect(parent, "parent_snap")); + printf("can't protect a protected snap\n"); // This clone and open should work - ASSERT_EQ(0, rbd_clone(ioctx, "parent", "parent_snap", ioctx, "child", features, - &order)); + ASSERT_EQ(0, rbd_clone(ioctx, "parent", "parent_snap", ioctx, "child", + features, &order)); ASSERT_EQ(0, rbd_open(ioctx, "child", &child, NULL)); printf("made and opened clone \"child\"\n"); @@ -1083,6 +1090,16 @@ TEST(LibRBD, TestClone) printf("sized up clone, changed size but not overlap or parent's size\n"); ASSERT_EQ(0, rbd_close(child)); + + ASSERT_EQ(-EBUSY, rbd_snap_remove(parent, "parent_snap")); + printf("can't remove parent while child still exists\n"); + ASSERT_EQ(0, rbd_remove(ioctx, "child")); + ASSERT_EQ(-EBUSY, rbd_snap_remove(parent, "parent_snap")); + printf("can't remove parent while still protected\n"); + ASSERT_EQ(0, rbd_snap_unprotect(parent, "parent_snap")); + ASSERT_EQ(0, rbd_snap_remove(parent, "parent_snap")); + printf("removed parent snap after unprotecting\n"); + ASSERT_EQ(0, rbd_close(parent)); rados_ioctx_destroy(ioctx); ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));