#define RBD_MAX_KEYS_READ 64
#define RBD_SNAP_KEY_PREFIX "snapshot_"
+#define RBD_SNAP_CHILDREN_KEY_PREFIX "snap_children_"
#define RBD_DIR_ID_KEY_PREFIX "id_"
#define RBD_DIR_NAME_KEY_PREFIX "name_"
#define RBD_METADATA_KEY_PREFIX "metadata_"
-#define GROUP_SNAP_SEQ "snap_seq"
-
static int snap_read_header(cls_method_context_t hctx, bufferlist& bl)
{
unsigned snap_count = 0;
return 0;
}
+template <typename T>
+static int write_key(cls_method_context_t hctx, const string &key, const T &t) {
+ bufferlist bl;
+ encode(t, bl);
+
+ int r = cls_cxx_map_set_val(hctx, key, &bl);
+ if (r < 0) {
+ CLS_ERR("failed to set omap key: %s", key.c_str());
+ return r;
+ }
+ return 0;
+}
+
static int remove_key(cls_method_context_t hctx, const string &key) {
int r = cls_cxx_map_remove_key(hctx, key);
if (r < 0 && r != -ENOENT) {
namespace image {
+std::string snap_children_key_from_snap_id(snapid_t snap_id)
+{
+ ostringstream oss;
+ oss << RBD_SNAP_CHILDREN_KEY_PREFIX
+ << std::setw(16) << std::setfill('0') << std::hex << snap_id;
+ return oss.str();
+}
+
int set_op_features(cls_method_context_t hctx, uint64_t op_features,
uint64_t mask) {
uint64_t orig_features;
return r;
}
- // remove the parent from all snapshots
- if ((features & RBD_FEATURE_DEEP_FLATTEN) != 0) {
- int max_read = RBD_MAX_KEYS_READ;
- vector<snapid_t> snap_ids;
- string last_read = RBD_SNAP_KEY_PREFIX;
- bool more;
-
- do {
- set<string> keys;
- r = cls_cxx_map_get_keys(hctx, last_read, max_read, &keys, &more);
- if (r < 0) {
- return r;
- }
-
- for (std::set<string>::const_iterator it = keys.begin();
- it != keys.end(); ++it) {
- if ((*it).find(RBD_SNAP_KEY_PREFIX) != 0) {
- break;
- }
-
- uint64_t snap_id = snap_id_from_key(*it);
- cls_rbd_snap snap_meta;
- r = read_key(hctx, *it, &snap_meta);
- if (r < 0) {
- CLS_ERR("Could not read snapshot: snap_id=%" PRIu64 ": %s",
- snap_id, cpp_strerror(r).c_str());
- return r;
- }
-
- snap_meta.parent = cls_rbd_parent();
+ auto flatten_lambda = [hctx, features](const cls_rbd_snap& snap_meta) {
+ if (snap_meta.parent.pool != -1) {
+ if ((features & RBD_FEATURE_DEEP_FLATTEN) != 0ULL) {
+ // remove parent reference from snapshot
+ cls_rbd_snap snap_meta_copy = snap_meta;
+ snap_meta_copy.parent = cls_rbd_parent();
- bufferlist bl;
- encode(snap_meta, bl);
- r = cls_cxx_map_set_val(hctx, *it, &bl);
+ std::string snap_key;
+ key_from_snap_id(snap_meta_copy.id, &snap_key);
+ int r = write_key(hctx, snap_key, snap_meta_copy);
if (r < 0) {
- CLS_ERR("Could not update snapshot: snap_id=%" PRIu64 ": %s",
- snap_id, cpp_strerror(r).c_str());
return r;
}
+ } else {
+ return -EEXIST;
}
+ }
+ return 0;
+ };
- if (!keys.empty()) {
- last_read = *(keys.rbegin());
- }
- } while (more);
+ r = image::snapshot_iterate(hctx, flatten_lambda);
+ bool has_child_snaps = (r == -EEXIST);
+ if (r < 0 && r != -EEXIST) {
+ return r;
}
cls_rbd_parent parent;
CLS_ERR("error removing parent: %s", cpp_strerror(r).c_str());
return r;
}
+
+ if (!has_child_snaps) {
+ // disable clone child op feature if no longer associated
+ r = image::set_op_features(hctx, 0, RBD_OPERATION_FEATURE_CLONE_CHILD);
+ if (r < 0) {
+ return r;
+ }
+ }
+
return 0;
}
if (snap.protection_status != RBD_PROTECTION_STATUS_UNPROTECTED)
return -EBUSY;
+ auto remove_lambda = [snap_id](const cls_rbd_snap& snap_meta) {
+ if (snap_meta.id != snap_id && snap_meta.parent.pool != -1) {
+ return -EEXIST;
+ }
+ return 0;
+ };
+
+ r = image::snapshot_iterate(hctx, remove_lambda);
+ bool has_child_snaps = (r == -EEXIST);
+ if (r < 0 && r != -EEXIST) {
+ return r;
+ }
+
r = cls_cxx_map_remove_key(hctx, snapshot_key);
if (r < 0) {
CLS_ERR("error writing snapshot metadata: %s", cpp_strerror(r).c_str());
return r;
}
+ if (!has_child_snaps) {
+ // disable clone child op feature if no longer associated
+ r = image::set_op_features(hctx, 0, RBD_OPERATION_FEATURE_CLONE_CHILD);
+ if (r < 0) {
+ return r;
+ }
+ }
+
return 0;
}
}
+/**
+ * Input:
+ * @param snap id (uint64_t) parent snapshot id
+ * @param child spec (cls::rbd::ChildImageSpec) child image
+ *
+ * Output:
+ * @returns 0 on success, negative error code on failure
+ */
+int child_attach(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+ uint64_t snap_id;
+ cls::rbd::ChildImageSpec child_image;
+ try {
+ bufferlist::iterator it = in->begin();
+ decode(snap_id, it);
+ decode(child_image, it);
+ } catch (const buffer::error &err) {
+ return -EINVAL;
+ }
+
+ CLS_LOG(20, "child_attach snap_id=%" PRIu64 ", child_pool_id=%" PRIi64 ", "
+ "child_image_id=%s", snap_id, child_image.pool_id,
+ child_image.image_id.c_str());
+
+ cls_rbd_snap snap;
+ std::string snapshot_key;
+ key_from_snap_id(snap_id, &snapshot_key);
+ int r = read_key(hctx, snapshot_key, &snap);
+ if (r < 0) {
+ return r;
+ }
+
+ if (cls::rbd::get_snap_namespace_type(snap.snapshot_namespace) ==
+ cls::rbd::SNAPSHOT_NAMESPACE_TYPE_TRASH) {
+ // cannot attach to a deleted snapshot
+ return -ENOENT;
+ }
+
+ auto children_key = image::snap_children_key_from_snap_id(snap_id);
+ cls::rbd::ChildImageSpecs child_images;
+ r = read_key(hctx, children_key, &child_images);
+ if (r < 0 && r != -ENOENT) {
+ CLS_ERR("error reading snapshot children: %s", cpp_strerror(r).c_str());
+ return r;
+ }
+
+ auto it = child_images.insert(child_image);
+ if (!it.second) {
+ // child already attached to the snapshot
+ return -EEXIST;
+ }
+
+ r = write_key(hctx, children_key, child_images);
+ if (r < 0) {
+ CLS_ERR("error writing snapshot children: %s", cpp_strerror(r).c_str());
+ return r;
+ }
+
+ ++snap.child_count;
+ r = write_key(hctx, snapshot_key, snap);
+ if (r < 0) {
+ CLS_ERR("error writing snapshot: %s", cpp_strerror(r).c_str());
+ return r;
+ }
+
+ r = image::set_op_features(hctx, RBD_OPERATION_FEATURE_CLONE_PARENT,
+ RBD_OPERATION_FEATURE_CLONE_PARENT);
+ if (r < 0) {
+ return r;
+ }
+
+ return 0;
+}
+
+/**
+ * Input:
+ * @param snap id (uint64_t) parent snapshot id
+ * @param child spec (cls::rbd::ChildImageSpec) child image
+ *
+ * Output:
+ * @returns 0 on success, negative error code on failure
+ */
+int child_detach(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+ uint64_t snap_id;
+ cls::rbd::ChildImageSpec child_image;
+ try {
+ bufferlist::iterator it = in->begin();
+ decode(snap_id, it);
+ decode(child_image, it);
+ } catch (const buffer::error &err) {
+ return -EINVAL;
+ }
+
+ CLS_LOG(20, "child_detach snap_id=%" PRIu64 ", child_pool_id=%" PRIi64 ", "
+ "child_image_id=%s", snap_id, child_image.pool_id,
+ child_image.image_id.c_str());
+
+ cls_rbd_snap snap;
+ std::string snapshot_key;
+ key_from_snap_id(snap_id, &snapshot_key);
+ int r = read_key(hctx, snapshot_key, &snap);
+ if (r < 0) {
+ return r;
+ }
+
+ auto children_key = image::snap_children_key_from_snap_id(snap_id);
+ cls::rbd::ChildImageSpecs child_images;
+ r = read_key(hctx, children_key, &child_images);
+ if (r < 0 && r != -ENOENT) {
+ CLS_ERR("error reading snapshot children: %s", cpp_strerror(r).c_str());
+ return r;
+ }
+
+ if (snap.child_count != child_images.size()) {
+ // children and reference count don't match
+ CLS_ERR("children reference count mismatch: %" PRIu64, snap_id);
+ return -EINVAL;
+ }
+
+ if (child_images.erase(child_image) == 0) {
+ // child not attached to the snapshot
+ return -ENOENT;
+ }
+
+ if (child_images.empty()) {
+ r = remove_key(hctx, children_key);
+ } else {
+ r = write_key(hctx, children_key, child_images);
+ if (r < 0) {
+ CLS_ERR("error writing snapshot children: %s", cpp_strerror(r).c_str());
+ return r;
+ }
+ }
+
+ --snap.child_count;
+ r = write_key(hctx, snapshot_key, snap);
+ if (r < 0) {
+ CLS_ERR("error writing snapshot: %s", cpp_strerror(r).c_str());
+ return r;
+ }
+
+ if (snap.child_count == 0) {
+ auto clone_in_use_lambda = [snap_id](const cls_rbd_snap& snap_meta) {
+ if (snap_meta.id != snap_id && snap_meta.child_count > 0) {
+ return -EEXIST;
+ }
+ return 0;
+ };
+
+ r = image::snapshot_iterate(hctx, clone_in_use_lambda);
+ if (r < 0 && r != -EEXIST) {
+ return r;
+ }
+
+ if (r != -EEXIST) {
+ // remove the clone_v2 op feature if not in-use by any other snapshots
+ r = image::set_op_features(hctx, 0, RBD_OPERATION_FEATURE_CLONE_PARENT);
+ if (r < 0) {
+ return r;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * Input:
+ * @param snap id (uint64_t) parent snapshot id
+ *
+ * Output:
+ * @param (cls::rbd::ChildImageSpecs) child images
+ * @returns 0 on success, negative error code on failure
+ */
+int children_list(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+ uint64_t snap_id;
+ try {
+ bufferlist::iterator it = in->begin();
+ decode(snap_id, it);
+ } catch (const buffer::error &err) {
+ return -EINVAL;
+ }
+
+ CLS_LOG(20, "child_detach snap_id=%" PRIu64, snap_id);
+
+ cls_rbd_snap snap;
+ std::string snapshot_key;
+ key_from_snap_id(snap_id, &snapshot_key);
+ int r = read_key(hctx, snapshot_key, &snap);
+ if (r < 0) {
+ return r;
+ }
+
+ auto children_key = image::snap_children_key_from_snap_id(snap_id);
+ cls::rbd::ChildImageSpecs child_images;
+ r = read_key(hctx, children_key, &child_images);
+ if (r == -ENOENT) {
+ return r;
+ } else if (r < 0) {
+ CLS_ERR("error reading snapshot children: %s", cpp_strerror(r).c_str());
+ return r;
+ }
+
+ encode(child_images, *out);
+ return 0;
+}
+
/****************************** Old format *******************************/
int old_snapshots_list(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
cls_method_handle_t h_metadata_get;
cls_method_handle_t h_snapshot_get_limit;
cls_method_handle_t h_snapshot_set_limit;
+ cls_method_handle_t h_child_attach;
+ cls_method_handle_t h_child_detach;
+ cls_method_handle_t h_children_list;
cls_method_handle_t h_old_snapshots_list;
cls_method_handle_t h_old_snapshot_add;
cls_method_handle_t h_old_snapshot_remove;
cls_register_cxx_method(h_class, "snapshot_set_limit",
CLS_METHOD_RD | CLS_METHOD_WR,
snapshot_set_limit, &h_snapshot_set_limit);
+ cls_register_cxx_method(h_class, "child_attach",
+ CLS_METHOD_RD | CLS_METHOD_WR,
+ child_attach, &h_child_attach);
+ cls_register_cxx_method(h_class, "child_detach",
+ CLS_METHOD_RD | CLS_METHOD_WR,
+ child_detach, &h_child_detach);
+ cls_register_cxx_method(h_class, "children_list",
+ CLS_METHOD_RD,
+ children_list, &h_children_list);
/* methods for the rbd_children object */
cls_register_cxx_method(h_class, "add_child",
return 0;
}
+ void child_attach(librados::ObjectWriteOperation *op, snapid_t snap_id,
+ const cls::rbd::ChildImageSpec& child_image)
+ {
+ bufferlist bl;
+ encode(snap_id, bl);
+ encode(child_image, bl);
+ op->exec("rbd", "child_attach", bl);
+ }
+
+ int child_attach(librados::IoCtx *ioctx, const std::string &oid,
+ snapid_t snap_id,
+ const cls::rbd::ChildImageSpec& child_image)
+ {
+ librados::ObjectWriteOperation op;
+ child_attach(&op, snap_id, child_image);
+
+ int r = ioctx->operate(oid, &op);
+ if (r < 0) {
+ return r;
+ }
+ return 0;
+ }
+
+ void child_detach(librados::ObjectWriteOperation *op, snapid_t snap_id,
+ const cls::rbd::ChildImageSpec& child_image)
+ {
+ bufferlist bl;
+ encode(snap_id, bl);
+ encode(child_image, bl);
+ op->exec("rbd", "child_detach", bl);
+ }
+
+ int child_detach(librados::IoCtx *ioctx, const std::string &oid,
+ snapid_t snap_id,
+ const cls::rbd::ChildImageSpec& child_image)
+ {
+ librados::ObjectWriteOperation op;
+ child_detach(&op, snap_id, child_image);
+
+ int r = ioctx->operate(oid, &op);
+ if (r < 0) {
+ return r;
+ }
+ return 0;
+ }
+
+ void children_list_start(librados::ObjectReadOperation *op,
+ snapid_t snap_id)
+ {
+ bufferlist bl;
+ encode(snap_id, bl);
+ op->exec("rbd", "children_list", bl);
+ }
+
+ int children_list_finish(bufferlist::iterator *it,
+ cls::rbd::ChildImageSpecs *child_images)
+ {
+ child_images->clear();
+ try {
+ decode(*child_images, *it);
+ } catch (const buffer::error &err) {
+ return -EBADMSG;
+ }
+ return 0;
+ }
+
+ int children_list(librados::IoCtx *ioctx, const std::string &oid,
+ snapid_t snap_id,
+ cls::rbd::ChildImageSpecs *child_images)
+ {
+ librados::ObjectReadOperation op;
+ children_list_start(&op, snap_id);
+
+ bufferlist out_bl;
+ int r = ioctx->operate(oid, &op, &out_bl);
+ if (r < 0) {
+ return r;
+ }
+
+ bufferlist::iterator it = out_bl.begin();
+ r = children_list_finish(&it, child_images);
+ if (r < 0) {
+ return r;
+ }
+ return 0;
+ }
+
void mirror_uuid_get_start(librados::ObjectReadOperation *op) {
bufferlist bl;
op->exec("rbd", "mirror_uuid_get", bl);
int metadata_get(librados::IoCtx *ioctx, const std::string &oid,
const std::string &key, string *v);
+ void child_attach(librados::ObjectWriteOperation *op, snapid_t snap_id,
+ const cls::rbd::ChildImageSpec& child_image);
+ int child_attach(librados::IoCtx *ioctx, const std::string &oid,
+ snapid_t snap_id,
+ const cls::rbd::ChildImageSpec& child_image);
+ void child_detach(librados::ObjectWriteOperation *op, snapid_t snap_id,
+ const cls::rbd::ChildImageSpec& child_image);
+ int child_detach(librados::IoCtx *ioctx, const std::string &oid,
+ snapid_t snap_id,
+ const cls::rbd::ChildImageSpec& child_image);
+ void children_list_start(librados::ObjectReadOperation *op,
+ snapid_t snap_id);
+ int children_list_finish(bufferlist::iterator *it,
+ cls::rbd::ChildImageSpecs *child_images);
+ int children_list(librados::IoCtx *ioctx, const std::string &oid,
+ snapid_t snap_id,
+ cls::rbd::ChildImageSpecs *child_images);
+
// operations on rbd_id objects
void get_id_start(librados::ObjectReadOperation *op);
int get_id_finish(bufferlist::iterator *it, std::string *id);
return os;
}
+void ChildImageSpec::encode(bufferlist &bl) const {
+ ENCODE_START(1, 1, bl);
+ encode(pool_id, bl);
+ encode(image_id, bl);
+ ENCODE_FINISH(bl);
+}
+
+void ChildImageSpec::decode(bufferlist::iterator &it) {
+ DECODE_START(1, it);
+ decode(pool_id, it);
+ decode(image_id, it);
+ DECODE_FINISH(it);
+}
+
+void ChildImageSpec::dump(Formatter *f) const {
+ f->dump_int("pool_id", pool_id);
+ f->dump_string("image_id", image_id);
+}
+
+void ChildImageSpec::generate_test_instances(std::list<ChildImageSpec*> &o) {
+ o.push_back(new ChildImageSpec());
+ o.push_back(new ChildImageSpec(123, "abc"));
+}
+
void GroupImageSpec::encode(bufferlist &bl) const {
ENCODE_START(1, 1, bl);
encode(image_id, bl);
#include "include/utime.h"
#include <iosfwd>
#include <string>
+#include <set>
#define RBD_GROUP_REF "rbd_group_ref"
WRITE_CLASS_ENCODER(MirrorImageStatus);
+struct ChildImageSpec {
+ int64_t pool_id = -1;
+ std::string image_id;
+
+ ChildImageSpec() {}
+ ChildImageSpec(int64_t pool_id, const std::string& image_id)
+ : pool_id(pool_id), image_id(image_id) {
+ }
+
+ void encode(bufferlist &bl) const;
+ void decode(bufferlist::iterator &it);
+ void dump(Formatter *f) const;
+
+ static void generate_test_instances(std::list<ChildImageSpec*> &o);
+
+ inline bool operator==(const ChildImageSpec& rhs) const {
+ return (pool_id == rhs.pool_id &&
+ image_id == rhs.image_id);
+ }
+ inline bool operator<(const ChildImageSpec& rhs) const {
+ if (pool_id != rhs.pool_id) {
+ return pool_id < rhs.pool_id;
+ }
+ return image_id < rhs.image_id;
+ }
+};
+WRITE_CLASS_ENCODER(ChildImageSpec);
+
+typedef std::set<ChildImageSpec> ChildImageSpecs;
+
struct GroupImageSpec {
GroupImageSpec() {}
std::string image_key();
};
-
WRITE_CLASS_ENCODER(GroupImageSpec);
struct GroupImageStatus {
ASSERT_EQ(0, get_features(&ioctx, oid, CEPH_NOSNAP, &features));
ASSERT_EQ(0u, features);
}
+
+TEST_F(TestClsRbd, clone_parent)
+{
+ librados::IoCtx ioctx;
+ ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), ioctx));
+
+ string oid = get_temp_image_name();
+ ASSERT_EQ(0, create_image(&ioctx, oid, 0, 22, 0, oid, -1));
+ ASSERT_EQ(0, snapshot_add(&ioctx, oid, 123, "user_snap"));
+
+ ASSERT_EQ(-ENOENT, child_attach(&ioctx, oid, 345, {}));
+ ASSERT_EQ(-ENOENT, child_detach(&ioctx, oid, 123, {}));
+ ASSERT_EQ(-ENOENT, child_detach(&ioctx, oid, 345, {}));
+
+ ASSERT_EQ(0, child_attach(&ioctx, oid, 123, {1, "image1"}));
+ ASSERT_EQ(-EEXIST, child_attach(&ioctx, oid, 123, {1, "image1"}));
+ ASSERT_EQ(0, child_attach(&ioctx, oid, 123, {1, "image2"}));
+ ASSERT_EQ(0, child_attach(&ioctx, oid, 123, {2, "image2"}));
+
+ std::vector<cls::rbd::SnapshotInfo> snaps;
+ std::vector<ParentInfo> parents;
+ std::vector<uint8_t> protection_status;
+ ASSERT_EQ(0, snapshot_get(&ioctx, oid, {123}, &snaps,
+ &parents, &protection_status));
+ ASSERT_EQ(1U, snaps.size());
+ ASSERT_EQ(3U, snaps[0].child_count);
+
+ // op feature should have been enabled
+ uint64_t op_features;
+ ASSERT_EQ(0, op_features_get(&ioctx, oid, &op_features));
+ ASSERT_TRUE((op_features & RBD_OPERATION_FEATURE_CLONE_PARENT) ==
+ RBD_OPERATION_FEATURE_CLONE_PARENT);
+
+ // cannot attach to trashed snapshot
+ librados::ObjectWriteOperation op;
+ ::librbd::cls_client::snapshot_add(&op, 234, "trash_snap",
+ cls::rbd::TrashSnapshotNamespace());
+ ASSERT_EQ(0, ioctx.operate(oid, &op));
+ ASSERT_EQ(-ENOENT, child_attach(&ioctx, oid, 234, {}));
+
+ cls::rbd::ChildImageSpecs child_images;
+ ASSERT_EQ(0, children_list(&ioctx, oid, 123, &child_images));
+
+ cls::rbd::ChildImageSpecs expected_child_images = {
+ {1, "image1"}, {1, "image2"}, {2, "image2"}};
+ ASSERT_EQ(expected_child_images, child_images);
+
+ expected_child_images = {{1, "image1"}, {2, "image2"}};
+ ASSERT_EQ(0, child_detach(&ioctx, oid, 123, {1, "image2"}));
+ ASSERT_EQ(0, children_list(&ioctx, oid, 123, &child_images));
+ ASSERT_EQ(expected_child_images, child_images);
+
+ ASSERT_EQ(0, child_detach(&ioctx, oid, 123, {2, "image2"}));
+
+ ASSERT_EQ(0, op_features_get(&ioctx, oid, &op_features));
+ ASSERT_TRUE((op_features & RBD_OPERATION_FEATURE_CLONE_PARENT) ==
+ RBD_OPERATION_FEATURE_CLONE_PARENT);
+
+ ASSERT_EQ(0, child_detach(&ioctx, oid, 123, {1, "image1"}));
+ ASSERT_EQ(-ENOENT, children_list(&ioctx, oid, 123, &child_images));
+
+ ASSERT_EQ(0, op_features_get(&ioctx, oid, &op_features));
+ ASSERT_TRUE((op_features & RBD_OPERATION_FEATURE_CLONE_PARENT) == 0ULL);
+}
+
+TEST_F(TestClsRbd, clone_child)
+{
+ librados::IoCtx ioctx;
+ ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), ioctx));
+
+ string oid = get_temp_image_name();
+ ASSERT_EQ(0, create_image(&ioctx, oid, 0, 22,
+ RBD_FEATURE_LAYERING | RBD_FEATURE_DEEP_FLATTEN,
+ oid, -1));
+ ASSERT_EQ(0, set_parent(&ioctx, oid, {1, "parent", 2}, 1));
+ ASSERT_EQ(0, snapshot_add(&ioctx, oid, 123, "user_snap1"));
+ ASSERT_EQ(0, op_features_set(&ioctx, oid, RBD_OPERATION_FEATURE_CLONE_CHILD,
+ RBD_OPERATION_FEATURE_CLONE_CHILD));
+
+ // clone child should be disabled due to deep flatten
+ ASSERT_EQ(0, remove_parent(&ioctx, oid));
+ uint64_t op_features;
+ ASSERT_EQ(0, op_features_get(&ioctx, oid, &op_features));
+ ASSERT_TRUE((op_features & RBD_OPERATION_FEATURE_CLONE_CHILD) == 0ULL);
+
+ ASSERT_EQ(0, set_features(&ioctx, oid, 0, RBD_FEATURE_DEEP_FLATTEN));
+ ASSERT_EQ(0, set_parent(&ioctx, oid, {1, "parent", 2}, 1));
+ ASSERT_EQ(0, snapshot_add(&ioctx, oid, 124, "user_snap2"));
+ ASSERT_EQ(0, op_features_set(&ioctx, oid, RBD_OPERATION_FEATURE_CLONE_CHILD,
+ RBD_OPERATION_FEATURE_CLONE_CHILD));
+
+ // clone child should remain enabled w/o deep flatten
+ ASSERT_EQ(0, remove_parent(&ioctx, oid));
+ ASSERT_EQ(0, op_features_get(&ioctx, oid, &op_features));
+ ASSERT_TRUE((op_features & RBD_OPERATION_FEATURE_CLONE_CHILD) ==
+ RBD_OPERATION_FEATURE_CLONE_CHILD);
+
+ // ... but removing the last linked snapshot should disable it
+ ASSERT_EQ(0, snapshot_remove(&ioctx, oid, 124));
+ ASSERT_EQ(0, op_features_get(&ioctx, oid, &op_features));
+ ASSERT_TRUE((op_features & RBD_OPERATION_FEATURE_CLONE_CHILD) == 0ULL);
+}
+
TYPE(cls_rbd_snap)
#include "cls/rbd/cls_rbd_types.h"
+TYPE(cls::rbd::ChildImageSpec)
TYPE(cls::rbd::MirrorPeer)
TYPE(cls::rbd::MirrorImage)
TYPE(cls::rbd::MirrorImageMap)