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:
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<std::pair<std::string, std::string> > *children);
+
/* cooperative locking */
int list_locks(std::set<std::pair<std::string, std::string> > &locks,
bool &exclusive);
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;
return 0;
}
+ int list_children(ImageCtx *ictx, set<pair<string, string> >& 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<string> pools;
+ rados.pool_list(pools);
+
+ for (std::list<string>::const_iterator it = pools.begin();
+ it != pools.end(); ++it) {
+ IoCtx ioctx;
+ rados.ioctx_create(it->c_str(), ioctx);
+ set<string> 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<string>::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;
int snap_set(ImageCtx *ictx, const char *snap_name);
int list(librados::IoCtx& io_ctx, std::vector<std::string>& names);
+ int list_children(ImageCtx *ictx,
+ std::set<pair<std::string, std::string> > & 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,
return librbd::flatten(ictx, prog_ctx);
}
+ int Image::list_children(set<pair<string, string> > *children)
+ {
+ ImageCtx *ictx = (ImageCtx *)ctx;
+ return librbd::list_children(ictx, *children);
+ }
+
int Image::list_locks(set<pair<string, string> > &locks,
bool &exclusive)
{
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<pair<string, string> > 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<pair<string, string> >::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<pair<string, string> >::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)
{
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.
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):
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));
+}