From 78f69752a44f0f754ebdba2c06399c493f2be99f Mon Sep 17 00:00:00 2001 From: Dan Mick Date: Tue, 21 Aug 2012 15:58:21 -0700 Subject: [PATCH] librbd: add {rbd_}list_children() methods These iterate over all pools and check for children of a particular snapshot. Signed-off-by: Josh Durgin Reviewed-by: Dan Mick --- src/include/rbd/librbd.h | 24 +++++++ src/include/rbd/librbd.hpp | 6 ++ src/librbd/internal.cc | 52 +++++++++++++++ src/librbd/internal.h | 2 + src/librbd/librbd.cc | 48 ++++++++++++++ src/pybind/rbd.py | 27 ++++++++ src/test/pybind/test_rbd.py | 23 +++++++ src/test/test_librbd.cc | 125 ++++++++++++++++++++++++++++++++++++ 8 files changed, 307 insertions(+) diff --git a/src/include/rbd/librbd.h b/src/include/rbd/librbd.h index e237b7ff7ebf6..77ca1e3af32bc 100644 --- a/src/include/rbd/librbd.h +++ b/src/include/rbd/librbd.h @@ -133,6 +133,30 @@ int rbd_snap_set(rbd_image_t image, const char *snapname); int rbd_flatten(rbd_image_t image); +/** + * List all images that are cloned from the image at the + * snapshot that is set via rbd_snap_set(). + * + * This iterates over all pools, so it should be run by a user with + * read access to all of them. pools_len and images_len are filled in + * with the number of bytes put into the pools and images buffers. + * + * If the provided buffers are too short, the required lengths are + * still filled in, but the data is not and -ERANGE is returned. + * Otherwise, the buffers are filled with the pool and image names + * of the children, with a '\0' after each. + * + * @param image which image (and implicitly snapshot) to list clones of + * @param pools buffer in which to store pool names + * @param pools_len number of bytes in pools buffer + * @param images buffer in which to store image names + * @param images_len number of bytes in images buffer + * @returns number of children on success, negative error code on failure + * @returns -ERANGE if either buffer is too short + */ +ssize_t rbd_list_children(rbd_image_t image, char *pools, size_t *pools_len, + char *images, size_t *images_len); + /* cooperative locking */ /** * in params: diff --git a/src/include/rbd/librbd.hpp b/src/include/rbd/librbd.hpp index fbfbcc0ee3f57..1608e693850cf 100644 --- a/src/include/rbd/librbd.hpp +++ b/src/include/rbd/librbd.hpp @@ -104,6 +104,12 @@ public: int flatten(); int flatten_with_progress(ProgressContext &prog_ctx); + /** + * Returns a pair of poolname, imagename for each clone + * of this image at the currently set snapshot. + */ + int list_children(std::set > *children); + /* cooperative locking */ int list_locks(std::set > &locks, bool &exclusive); diff --git a/src/librbd/internal.cc b/src/librbd/internal.cc index 386ec36e60a64..4d8b0a4ba463a 100644 --- a/src/librbd/internal.cc +++ b/src/librbd/internal.cc @@ -26,6 +26,7 @@ using std::pair; using std::set; using std::string; using std::vector; +// list binds to list() here, so std::list is explicitly used below using ceph::bufferlist; using librados::snap_t; @@ -378,6 +379,57 @@ namespace librbd { return 0; } + int list_children(ImageCtx *ictx, set >& names) + { + CephContext *cct = ictx->cct; + ldout(cct, 20) << "children list " << ictx->name << dendl; + + int r = ictx_check(ictx); + if (r < 0) + return r; + + // no children for non-layered or old format image + if (!ictx->features & RBD_FEATURE_LAYERING) + return 0; + + parent_spec parent_spec(ictx->md_ctx.get_id(), ictx->id, ictx->snap_id); + names.clear(); + + // search all pools for children depending on this snapshot + Rados rados(ictx->md_ctx); + std::list pools; + rados.pool_list(pools); + + for (std::list::const_iterator it = pools.begin(); + it != pools.end(); ++it) { + IoCtx ioctx; + rados.ioctx_create(it->c_str(), ioctx); + set image_ids; + int r = cls_client::get_children(&ioctx, RBD_CHILDREN, + parent_spec, image_ids); + if (r < 0 && r != -ENOENT) { + lderr(cct) << "Error reading list of children from pool " << *it + << dendl; + return r; + } + + for (set::const_iterator id_it = image_ids.begin(); + id_it != image_ids.end(); ++id_it) { + string name; + r = cls_client::dir_get_name(&ioctx, RBD_DIRECTORY, + *id_it, &name); + if (r < 0) { + lderr(cct) << "Error looking up name for image id " << *id_it + << " in pool " << *it << dendl; + return r; + } + names.insert(make_pair(*it, name)); + } + } + + return 0; + } + int snap_create(ImageCtx *ictx, const char *snap_name) { ldout(ictx->cct, 20) << "snap_create " << ictx << " " << snap_name << dendl; diff --git a/src/librbd/internal.h b/src/librbd/internal.h index 4f6adcfeffaf2..2421e0bc6624a 100644 --- a/src/librbd/internal.h +++ b/src/librbd/internal.h @@ -76,6 +76,8 @@ namespace librbd { int snap_set(ImageCtx *ictx, const char *snap_name); int list(librados::IoCtx& io_ctx, std::vector& names); + int list_children(ImageCtx *ictx, + std::set > & names); int create(librados::IoCtx& io_ctx, const char *imgname, uint64_t size, bool old_format, uint64_t features, int *order); int clone(IoCtx& p_ioctx, const char *p_name, const char *p_snap_name, diff --git a/src/librbd/librbd.cc b/src/librbd/librbd.cc index 8aefe1e905022..fab565b4c9fa1 100644 --- a/src/librbd/librbd.cc +++ b/src/librbd/librbd.cc @@ -266,6 +266,12 @@ namespace librbd { return librbd::flatten(ictx, prog_ctx); } + int Image::list_children(set > *children) + { + ImageCtx *ictx = (ImageCtx *)ctx; + return librbd::list_children(ictx, *children); + } + int Image::list_locks(set > &locks, bool &exclusive) { @@ -736,6 +742,48 @@ extern "C" int rbd_snap_set(rbd_image_t image, const char *snap_name) return librbd::snap_set(ictx, snap_name); } +extern "C" ssize_t rbd_list_children(rbd_image_t image, char *pools, + size_t *pools_len, char *images, + size_t *images_len) +{ + librbd::ImageCtx *ictx = (librbd::ImageCtx *)image; + set > image_set; + + int r = librbd::list_children(ictx, image_set); + if (r < 0) + return r; + + size_t pools_total = 0; + size_t images_total = 0; + for (set >::const_iterator it = image_set.begin(); + it != image_set.end(); ++it) { + pools_total += it->first.length() + 1; + images_total += it->second.length() + 1; + } + + bool too_short = false; + if (pools_total > *pools_len) + too_short = true; + if (images_total > *images_len) + too_short = true; + *pools_len = pools_total; + *images_len = images_total; + if (too_short) + return -ERANGE; + + char *pools_p = pools; + char *images_p = images; + for (set >::const_iterator it = image_set.begin(); + it != image_set.end(); ++it) { + strcpy(pools_p, it->first.c_str()); + pools_p += it->first.length() + 1; + strcpy(images_p, it->second.c_str()); + images_p += it->second.length() + 1; + } + + return image_set.size(); +} + extern "C" int rbd_list_lockers(rbd_image_t image, int *exclusive, char **lockers_and_cookies, int *max_entries) { diff --git a/src/pybind/rbd.py b/src/pybind/rbd.py index 98524d3cb14ff..2bb911dafb33d 100644 --- a/src/pybind/rbd.py +++ b/src/pybind/rbd.py @@ -620,6 +620,33 @@ written." % (self.name, ret, length)) if (ret < 0): raise make_ex(ret, "error flattening %s" % self.name) + def list_children(self): + """ + List children of the currently set snapshot (set via set_snap()). + + :returns: list - a list of (pool name, image name) tuples + """ + pools_size = c_size_t(512) + images_size = c_size_t(512) + while True: + c_pools = create_string_buffer(pools_size.value) + c_images = create_string_buffer(images_size.value) + ret = self.librbd.rbd_list_children(self.image, + byref(c_pools), + byref(pools_size), + byref(c_images), + byref(images_size)) + if ret >= 0: + break + elif ret != -errno.ERANGE: + raise make_ex(ret, 'error listing images') + if ret == 0: + return [] + pools = c_pools.raw[:pools_size.value - 1].split('\0') + images = c_images.raw[:images_size.value - 1].split('\0') + return zip(pools, images) + + class SnapIterator(object): """ Iterator over snapshot info for an image. diff --git a/src/test/pybind/test_rbd.py b/src/test/pybind/test_rbd.py index 7e15b6f7d8ae2..d898d8e7374dc 100644 --- a/src/test/pybind/test_rbd.py +++ b/src/test/pybind/test_rbd.py @@ -520,6 +520,29 @@ class TestClone(object): parent_data = self.image.read(IMG_SIZE / 2 + 256, 256) eq(parent_data, '\0' * 256) + def test_list_children(self): + global ioctx + global features + self.image.set_snap('snap1') + eq(self.image.list_children(), [('rbd', 'clone')]) + self.rbd.remove(ioctx, 'clone') + eq(self.image.list_children(), []) + + expected_children = [] + for i in xrange(10): + self.rbd.clone(ioctx, IMG_NAME, 'snap1', ioctx, 'clone%d' % i, features) + expected_children.append(('rbd', 'clone%d' % i)) + eq(self.image.list_children(), expected_children) + + for i in xrange(10): + self.rbd.remove(ioctx, 'clone%d' % i) + expected_children.pop(0) + eq(self.image.list_children(), expected_children) + + eq(self.image.list_children(), []) + self.rbd.clone(ioctx, IMG_NAME, 'snap1', ioctx, 'clone', features) + eq(self.image.list_children(), [('rbd', 'clone')]) + class TestFlatten(TestClone): def test_errors(self): diff --git a/src/test/test_librbd.cc b/src/test/test_librbd.cc index c6ba3f8277c8f..023d39c1055e0 100644 --- a/src/test/test_librbd.cc +++ b/src/test/test_librbd.cc @@ -1104,3 +1104,128 @@ TEST(LibRBD, TestClone) rados_ioctx_destroy(ioctx); ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster)); } + +static void test_list_children(rbd_image_t image, ssize_t num_expected, ...) +{ + va_list ap; + va_start(ap, num_expected); + size_t pools_len = 100; + size_t children_len = 100; + char *pools = NULL; + char *children = NULL; + ssize_t num_children; + + do { + free(pools); + free(children); + pools = (char *) malloc(pools_len); + children = (char *) malloc(children_len); + num_children = rbd_list_children(image, pools, &pools_len, + children, &children_len); + } while (num_children == -ERANGE); + + ASSERT_EQ(num_expected, num_children); + for (ssize_t i = num_expected; i > 0; --i) { + char *expected_pool = va_arg(ap, char *); + char *expected_image = va_arg(ap, char *); + char *pool = pools; + char *image = children; + bool found = 0; + printf("\ntrying to find %s/%s\n", expected_pool, expected_image); + for (ssize_t j = 0; j < num_children; ++j) { + printf("checking %s/%s\n", pool, image); + if (strcmp(expected_pool, pool) == 0 && + strcmp(expected_image, image) == 0) { + printf("found child %s/%s\n\n", pool, image); + found = 1; + break; + } + pool += strlen(pool) + 1; + image += strlen(image) + 1; + if (j == num_children - 1) { + ASSERT_EQ(pool - pools - 1, (ssize_t) pools_len); + ASSERT_EQ(image - children - 1, (ssize_t) children_len); + } + } + ASSERT_TRUE(found); + } + va_end(ap); +} + +TEST(LibRBD, ListChildren) +{ + rados_t cluster; + rados_ioctx_t ioctx1, ioctx2; + string pool_name1 = get_temp_pool_name(); + ASSERT_EQ("", create_one_pool(pool_name1, &cluster)); + string pool_name2 = get_temp_pool_name(); + ASSERT_EQ("", create_one_pool(pool_name2, &cluster)); + rados_ioctx_create(cluster, pool_name1.c_str(), &ioctx1); + rados_ioctx_create(cluster, pool_name2.c_str(), &ioctx2); + + int features = RBD_FEATURE_LAYERING; + rbd_image_t parent; + int order = 0; + + // make a parent to clone from + ASSERT_EQ(0, create_image_full(ioctx1, "parent", 4<<20, &order, + false, features)); + ASSERT_EQ(0, rbd_open(ioctx1, "parent", &parent, NULL)); + // create a snapshot, reopen as the parent we're interested in + ASSERT_EQ(0, rbd_snap_create(parent, "parent_snap")); + ASSERT_EQ(0, rbd_snap_set(parent, "parent_snap")); + ASSERT_EQ(0, rbd_snap_protect(parent, "parent_snap")); + + ASSERT_EQ(0, rbd_close(parent)); + ASSERT_EQ(0, rbd_open(ioctx1, "parent", &parent, "parent_snap")); + + ASSERT_EQ(0, rbd_clone(ioctx1, "parent", "parent_snap", ioctx2, "child1", + features, &order)); + test_list_children(parent, 1, pool_name2.c_str(), "child1"); + + ASSERT_EQ(0, rbd_clone(ioctx1, "parent", "parent_snap", ioctx1, "child2", + features, &order)); + test_list_children(parent, 2, pool_name2.c_str(), "child1", + pool_name1.c_str(), "child2"); + + ASSERT_EQ(0, rbd_clone(ioctx1, "parent", "parent_snap", ioctx2, "child3", + features, &order)); + test_list_children(parent, 3, pool_name2.c_str(), "child1", + pool_name1.c_str(), "child2", + pool_name2.c_str(), "child3"); + + ASSERT_EQ(0, rbd_clone(ioctx1, "parent", "parent_snap", ioctx2, "child4", + features, &order)); + test_list_children(parent, 4, pool_name2.c_str(), "child1", + pool_name1.c_str(), "child2", + pool_name2.c_str(), "child3", + pool_name2.c_str(), "child4"); + + ASSERT_EQ(0, rbd_remove(ioctx2, "child1")); + test_list_children(parent, 3, + pool_name1.c_str(), "child2", + pool_name2.c_str(), "child3", + pool_name2.c_str(), "child4"); + + ASSERT_EQ(0, rbd_remove(ioctx2, "child3")); + test_list_children(parent, 2, + pool_name1.c_str(), "child2", + pool_name2.c_str(), "child4"); + + ASSERT_EQ(0, rbd_remove(ioctx2, "child4")); + test_list_children(parent, 1, + pool_name1.c_str(), "child2"); + + ASSERT_EQ(0, rbd_remove(ioctx1, "child2")); + test_list_children(parent, 0); + + ASSERT_EQ(0, rbd_snap_unprotect(parent, "parent_snap")); + ASSERT_EQ(0, rbd_snap_remove(parent, "parent_snap")); + ASSERT_EQ(0, rbd_close(parent)); + ASSERT_EQ(0, rbd_remove(ioctx1, "parent")); + rados_ioctx_destroy(ioctx1); + rados_ioctx_destroy(ioctx2); + // destroy_one_pool also closes the cluster; do this one step at a time + ASSERT_EQ(0, rados_pool_delete(cluster, pool_name1.c_str())); + ASSERT_EQ(0, destroy_one_pool(pool_name2, &cluster)); +} -- 2.39.5