return 0;
}
-int detach(cls_method_context_t hctx) {
+int detach(cls_method_context_t hctx, bool legacy_api) {
int r = check_exists(hctx);
if (r < 0) {
CLS_LOG(20, "cls_rbd::parent::detach: child doesn't exist");
r = read_key(hctx, "parent", &on_disk_parent);
if (r < 0) {
return r;
+ } else if (legacy_api && !on_disk_parent.pool_namespace.empty()) {
+ return -EXDEV;
} else if (!on_disk_parent.head_overlap) {
return -ENOENT;
}
r = read_key(hctx, "parent", &parent);
if (r < 0 && r != -ENOENT) {
return r;
+ } else if (!parent.pool_namespace.empty()) {
+ return -EXDEV;
}
if (snap_id != CEPH_NOSNAP) {
*/
int remove_parent(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
{
- int r = image::parent::detach(hctx);
+ int r = image::parent::detach(hctx, true);
if (r < 0) {
return r;
}
return 0;
}
+/**
+ * Input:
+ * none
+ *
+ * Output:
+ * @param parent spec (cls::rbd::ParentImageSpec)
+ * @returns 0 on success, negative error code on failure
+ */
+int parent_get(cls_method_context_t hctx, bufferlist *in, bufferlist *out) {
+ int r = check_exists(hctx);
+ if (r < 0) {
+ return r;
+ }
+
+ CLS_LOG(20, "parent_get");
+
+ cls_rbd_parent parent;
+ r = image::require_feature(hctx, RBD_FEATURE_LAYERING);
+ if (r == 0) {
+ r = read_key(hctx, "parent", &parent);
+ if (r < 0 && r != -ENOENT) {
+ return r;
+ } else if (r == -ENOENT) {
+ // examine oldest snapshot to see if it has a denormalized parent
+ auto parent_lambda = [hctx, &parent](const cls_rbd_snap& snap_meta) {
+ if (snap_meta.parent.exists()) {
+ parent = snap_meta.parent;
+ }
+ return 0;
+ };
+
+ r = image::snapshot::iterate(hctx, parent_lambda);
+ if (r < 0) {
+ return r;
+ }
+ }
+ }
+
+ cls::rbd::ParentImageSpec parent_image_spec{
+ parent.pool_id, parent.pool_namespace, parent.image_id,
+ parent.snap_id};
+ encode(parent_image_spec, *out);
+ return 0;
+}
+
+/**
+ * Input:
+ * @param snap id (uint64_t) parent snapshot id
+ *
+ * Output:
+ * @param byte overlap of parent image (std::optional<uint64_t>)
+ * @returns 0 on success, negative error code on failure
+ */
+int parent_overlap_get(cls_method_context_t hctx, bufferlist *in,
+ bufferlist *out) {
+ uint64_t snap_id;
+ auto iter = in->cbegin();
+ try {
+ decode(snap_id, iter);
+ } catch (const buffer::error &err) {
+ return -EINVAL;
+ }
+
+ int r = check_exists(hctx);
+ CLS_LOG(20, "parent_overlap_get");
+
+ std::optional<uint64_t> parent_overlap = std::nullopt;
+ r = image::require_feature(hctx, RBD_FEATURE_LAYERING);
+ if (r == 0) {
+ if (snap_id == CEPH_NOSNAP) {
+ cls_rbd_parent parent;
+ r = read_key(hctx, "parent", &parent);
+ if (r < 0 && r != -ENOENT) {
+ return r;
+ } else if (r == 0) {
+ parent_overlap = parent.head_overlap;
+ }
+ } else {
+ cls_rbd_snap snap;
+ std::string snapshot_key;
+ key_from_snap_id(snap_id, &snapshot_key);
+ r = read_key(hctx, snapshot_key, &snap);
+ if (r < 0) {
+ return r;
+ }
+
+ if (snap.parent_overlap) {
+ parent_overlap = snap.parent_overlap;
+ } else if (snap.parent.exists()) {
+ // legacy format where full parent spec is written within
+ // each snapshot record
+ parent_overlap = snap.parent.head_overlap;
+ }
+ }
+ };
+
+ encode(parent_overlap, *out);
+ return 0;
+}
+
+/**
+ * Input:
+ * @param parent spec (cls::rbd::ParentImageSpec)
+ * @param size parent size (uint64_t)
+ *
+ * Output:
+ * @returns 0 on success, negative error code on failure
+ */
+int parent_attach(cls_method_context_t hctx, bufferlist *in, bufferlist *out) {
+ cls::rbd::ParentImageSpec parent_image_spec;
+ uint64_t parent_overlap;
+
+ auto iter = in->cbegin();
+ try {
+ decode(parent_image_spec, iter);
+ decode(parent_overlap, iter);
+ } catch (const buffer::error &err) {
+ CLS_LOG(20, "cls_rbd::parent_attach: invalid decode");
+ return -EINVAL;
+ }
+
+ int r = image::parent::attach(hctx, {parent_image_spec, parent_overlap});
+ if (r < 0) {
+ return r;
+ }
+
+ return 0;
+}
+
+/**
+ * Input:
+ * none
+ *
+ * Output:
+ * @returns 0 on success, negative error code on failure
+ */
+int parent_detach(cls_method_context_t hctx, bufferlist *in, bufferlist *out) {
+ int r = image::parent::detach(hctx, false);
+ if (r < 0) {
+ return r;
+ }
+
+ return 0;
+}
+
+
/**
* methods for dealing with rbd_children object
*/
cls_method_handle_t h_set_size;
cls_method_handle_t h_get_parent;
cls_method_handle_t h_set_parent;
+ cls_method_handle_t h_remove_parent;
+ cls_method_handle_t h_parent_get;
+ cls_method_handle_t h_parent_overlap_get;
+ cls_method_handle_t h_parent_attach;
+ cls_method_handle_t h_parent_detach;
cls_method_handle_t h_get_protection_status;
cls_method_handle_t h_set_protection_status;
cls_method_handle_t h_get_stripe_unit_count;
cls_method_handle_t h_set_flags;
cls_method_handle_t h_op_features_get;
cls_method_handle_t h_op_features_set;
- 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_register_cxx_method(h_class, "copyup",
CLS_METHOD_RD | CLS_METHOD_WR,
copyup, &h_copyup);
+
+ // NOTE: deprecate v1 parent APIs after mimic EOLed
cls_register_cxx_method(h_class, "get_parent",
CLS_METHOD_RD,
get_parent, &h_get_parent);
cls_register_cxx_method(h_class, "remove_parent",
CLS_METHOD_RD | CLS_METHOD_WR,
remove_parent, &h_remove_parent);
+
+ cls_register_cxx_method(h_class, "parent_get",
+ CLS_METHOD_RD, parent_get, &h_parent_get);
+ cls_register_cxx_method(h_class, "parent_overlap_get",
+ CLS_METHOD_RD, parent_overlap_get,
+ &h_parent_overlap_get);
+ cls_register_cxx_method(h_class, "parent_attach",
+ CLS_METHOD_RD | CLS_METHOD_WR,
+ parent_attach, &h_parent_attach);
+ cls_register_cxx_method(h_class, "parent_detach",
+ CLS_METHOD_RD | CLS_METHOD_WR,
+ parent_detach, &h_parent_detach);
+
cls_register_cxx_method(h_class, "set_protection_status",
CLS_METHOD_RD | CLS_METHOD_WR,
set_protection_status, &h_set_protection_status);
op->exec("rbd", "set_size", bl);
}
+void get_flags_start(librados::ObjectReadOperation *op, snapid_t snap_id) {
+ bufferlist in_bl;
+ encode(static_cast<snapid_t>(snap_id), in_bl);
+ op->exec("rbd", "get_flags", in_bl);
+}
+
+int get_flags_finish(bufferlist::const_iterator *it, uint64_t *flags) {
+ try {
+ decode(*flags, *it);
+ } catch (const buffer::error &err) {
+ return -EBADMSG;
+ }
+ return 0;
+}
+
+int get_flags(librados::IoCtx *ioctx, const std::string &oid,
+ snapid_t snap_id, uint64_t *flags)
+{
+ librados::ObjectReadOperation op;
+ get_flags_start(&op, snap_id);
+
+ bufferlist out_bl;
+ int r = ioctx->operate(oid, &op, &out_bl);
+ if (r < 0) {
+ return r;
+ }
+
+ auto it = out_bl.cbegin();
+ return get_flags_finish(&it, flags);
+}
+
+void set_flags(librados::ObjectWriteOperation *op, snapid_t snap_id,
+ uint64_t flags, uint64_t mask)
+{
+ bufferlist inbl;
+ encode(flags, inbl);
+ encode(mask, inbl);
+ encode(snap_id, inbl);
+ op->exec("rbd", "set_flags", inbl);
+}
+
+void op_features_get_start(librados::ObjectReadOperation *op)
+{
+ bufferlist in_bl;
+ op->exec("rbd", "op_features_get", in_bl);
+}
+
+int op_features_get_finish(bufferlist::const_iterator *it, uint64_t *op_features)
+{
+ try {
+ decode(*op_features, *it);
+ } catch (const buffer::error &err) {
+ return -EBADMSG;
+ }
+ return 0;
+}
+
+int op_features_get(librados::IoCtx *ioctx, const std::string &oid,
+ uint64_t *op_features)
+{
+ librados::ObjectReadOperation op;
+ op_features_get_start(&op);
+
+ bufferlist out_bl;
+ int r = ioctx->operate(oid, &op, &out_bl);
+ if (r < 0) {
+ return r;
+ }
+
+ auto it = out_bl.cbegin();
+ return op_features_get_finish(&it, op_features);
+}
+
+void op_features_set(librados::ObjectWriteOperation *op,
+ uint64_t op_features, uint64_t mask)
+{
+ bufferlist inbl;
+ encode(op_features, inbl);
+ encode(mask, inbl);
+ op->exec("rbd", "op_features_set", inbl);
+}
+
+int op_features_set(librados::IoCtx *ioctx, const std::string &oid,
+ uint64_t op_features, uint64_t mask)
+{
+ librados::ObjectWriteOperation op;
+ op_features_set(&op, op_features, mask);
+
+ return ioctx->operate(oid, &op);
+}
+
void get_parent_start(librados::ObjectReadOperation *op, snapid_t snap_id)
{
bufferlist bl;
op->exec("rbd", "set_parent", in_bl);
}
-void get_flags_start(librados::ObjectReadOperation *op, snapid_t snap_id) {
+int remove_parent(librados::IoCtx *ioctx, const std::string &oid)
+{
+ librados::ObjectWriteOperation op;
+ remove_parent(&op);
+ return ioctx->operate(oid, &op);
+}
+
+void remove_parent(librados::ObjectWriteOperation *op)
+{
+ bufferlist inbl;
+ op->exec("rbd", "remove_parent", inbl);
+}
+
+void parent_get_start(librados::ObjectReadOperation* op) {
bufferlist in_bl;
- encode(static_cast<snapid_t>(snap_id), in_bl);
- op->exec("rbd", "get_flags", in_bl);
+ op->exec("rbd", "parent_get", in_bl);
}
-int get_flags_finish(bufferlist::const_iterator *it, uint64_t *flags) {
+int parent_get_finish(bufferlist::const_iterator* it,
+ cls::rbd::ParentImageSpec* parent_image_spec) {
try {
- decode(*flags, *it);
- } catch (const buffer::error &err) {
+ decode(*parent_image_spec, *it);
+ } catch (const buffer::error &) {
return -EBADMSG;
}
return 0;
}
-int get_flags(librados::IoCtx *ioctx, const std::string &oid,
- snapid_t snap_id, uint64_t *flags)
-{
+int parent_get(librados::IoCtx* ioctx, const std::string &oid,
+ cls::rbd::ParentImageSpec* parent_image_spec) {
librados::ObjectReadOperation op;
- get_flags_start(&op, snap_id);
+ parent_get_start(&op);
bufferlist out_bl;
int r = ioctx->operate(oid, &op, &out_bl);
}
auto it = out_bl.cbegin();
- return get_flags_finish(&it, flags);
-}
-
-void set_flags(librados::ObjectWriteOperation *op, snapid_t snap_id,
- uint64_t flags, uint64_t mask)
-{
- bufferlist inbl;
- encode(flags, inbl);
- encode(mask, inbl);
- encode(snap_id, inbl);
- op->exec("rbd", "set_flags", inbl);
+ r = parent_get_finish(&it, parent_image_spec);
+ if (r < 0) {
+ return r;
+ }
+ return 0;
}
-void op_features_get_start(librados::ObjectReadOperation *op)
-{
+void parent_overlap_get_start(librados::ObjectReadOperation* op,
+ snapid_t snap_id) {
bufferlist in_bl;
- op->exec("rbd", "op_features_get", in_bl);
+ encode(snap_id, in_bl);
+ op->exec("rbd", "parent_overlap_get", in_bl);
}
-int op_features_get_finish(bufferlist::const_iterator *it, uint64_t *op_features)
-{
+int parent_overlap_get_finish(bufferlist::const_iterator* it,
+ std::optional<uint64_t>* parent_overlap) {
try {
- decode(*op_features, *it);
- } catch (const buffer::error &err) {
+ decode(*parent_overlap, *it);
+ } catch (const buffer::error &) {
return -EBADMSG;
}
return 0;
}
-int op_features_get(librados::IoCtx *ioctx, const std::string &oid,
- uint64_t *op_features)
-{
+int parent_overlap_get(librados::IoCtx* ioctx, const std::string &oid,
+ snapid_t snap_id,
+ std::optional<uint64_t>* parent_overlap) {
librados::ObjectReadOperation op;
- op_features_get_start(&op);
+ parent_overlap_get_start(&op, snap_id);
bufferlist out_bl;
int r = ioctx->operate(oid, &op, &out_bl);
}
auto it = out_bl.cbegin();
- return op_features_get_finish(&it, op_features);
+ r = parent_overlap_get_finish(&it, parent_overlap);
+ if (r < 0) {
+ return r;
+ }
+ return 0;
}
-void op_features_set(librados::ObjectWriteOperation *op,
- uint64_t op_features, uint64_t mask)
-{
- bufferlist inbl;
- encode(op_features, inbl);
- encode(mask, inbl);
- op->exec("rbd", "op_features_set", inbl);
+void parent_attach(librados::ObjectWriteOperation* op,
+ const cls::rbd::ParentImageSpec& parent_image_spec,
+ uint64_t parent_overlap) {
+ bufferlist in_bl;
+ encode(parent_image_spec, in_bl);
+ encode(parent_overlap, in_bl);
+ op->exec("rbd", "parent_attach", in_bl);
}
-int op_features_set(librados::IoCtx *ioctx, const std::string &oid,
- uint64_t op_features, uint64_t mask)
-{
+int parent_attach(librados::IoCtx *ioctx, const std::string &oid,
+ const cls::rbd::ParentImageSpec& parent_image_spec,
+ uint64_t parent_overlap) {
librados::ObjectWriteOperation op;
- op_features_set(&op, op_features, mask);
-
+ parent_attach(&op, parent_image_spec, parent_overlap);
return ioctx->operate(oid, &op);
}
-int remove_parent(librados::IoCtx *ioctx, const std::string &oid)
-{
- librados::ObjectWriteOperation op;
- remove_parent(&op);
- return ioctx->operate(oid, &op);
+void parent_detach(librados::ObjectWriteOperation* op) {
+ bufferlist in_bl;
+ op->exec("rbd", "parent_detach", in_bl);
}
-void remove_parent(librados::ObjectWriteOperation *op)
-{
- bufferlist inbl;
- op->exec("rbd", "remove_parent", inbl);
+int parent_detach(librados::IoCtx *ioctx, const std::string &oid) {
+ librados::ObjectWriteOperation op;
+ parent_detach(&op);
+ return ioctx->operate(oid, &op);
}
int add_child(librados::IoCtx *ioctx, const std::string &oid,
uint64_t size);
void set_size(librados::ObjectWriteOperation *op, uint64_t size);
-void get_parent_start(librados::ObjectReadOperation *op, snapid_t snap_id);
-int get_parent_finish(bufferlist::const_iterator *it, ParentSpec *pspec,
- uint64_t *parent_overlap);
-int get_parent(librados::IoCtx *ioctx, const std::string &oid,
- snapid_t snap_id, ParentSpec *pspec,
- uint64_t *parent_overlap);
-
-int set_parent(librados::IoCtx *ioctx, const std::string &oid,
- const ParentSpec &pspec, uint64_t parent_overlap);
-void set_parent(librados::ObjectWriteOperation *op,
- const ParentSpec &pspec, uint64_t parent_overlap);
-
void get_flags_start(librados::ObjectReadOperation *op, snapid_t snap_id);
int get_flags_finish(bufferlist::const_iterator *it, uint64_t *flags);
int get_flags(librados::IoCtx *ioctx, const std::string &oid,
uint64_t op_features, uint64_t mask);
int op_features_set(librados::IoCtx *ioctx, const std::string &oid,
uint64_t op_features, uint64_t mask);
+
+// NOTE: deprecate v1 parent APIs after mimic EOLed
+void get_parent_start(librados::ObjectReadOperation *op, snapid_t snap_id);
+int get_parent_finish(bufferlist::const_iterator *it, ParentSpec *pspec,
+ uint64_t *parent_overlap);
+int get_parent(librados::IoCtx *ioctx, const std::string &oid,
+ snapid_t snap_id, ParentSpec *pspec,
+ uint64_t *parent_overlap);
+int set_parent(librados::IoCtx *ioctx, const std::string &oid,
+ const ParentSpec &pspec, uint64_t parent_overlap);
+void set_parent(librados::ObjectWriteOperation *op,
+ const ParentSpec &pspec, uint64_t parent_overlap);
int remove_parent(librados::IoCtx *ioctx, const std::string &oid);
void remove_parent(librados::ObjectWriteOperation *op);
+
+// v2 parent APIs
+void parent_get_start(librados::ObjectReadOperation* op);
+int parent_get_finish(bufferlist::const_iterator* it,
+ cls::rbd::ParentImageSpec* parent_image_spec);
+int parent_get(librados::IoCtx* ioctx, const std::string &oid,
+ cls::rbd::ParentImageSpec* parent_image_spec);
+
+void parent_overlap_get_start(librados::ObjectReadOperation* op,
+ snapid_t snap_id);
+int parent_overlap_get_finish(bufferlist::const_iterator* it,
+ std::optional<uint64_t>* parent_overlap);
+int parent_overlap_get(librados::IoCtx* ioctx, const std::string &oid,
+ snapid_t snap_id,
+ std::optional<uint64_t>* parent_overlap);
+
+void parent_attach(librados::ObjectWriteOperation* op,
+ const cls::rbd::ParentImageSpec& parent_image_spec,
+ uint64_t parent_overlap);
+int parent_attach(librados::IoCtx *ioctx, const std::string &oid,
+ const cls::rbd::ParentImageSpec& parent_image_spec,
+ uint64_t parent_overlap);
+
+void parent_detach(librados::ObjectWriteOperation* op);
+int parent_detach(librados::IoCtx *ioctx, const std::string &oid);
+
int add_child(librados::IoCtx *ioctx, const std::string &oid,
const ParentSpec &pspec, const std::string &c_imageid);
void add_child(librados::ObjectWriteOperation *op,
ioctx.close();
}
-TEST_F(TestClsRbd, parents)
+TEST_F(TestClsRbd, parents_v1)
{
librados::IoCtx ioctx;
ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), ioctx));
- string oid = get_temp_image_name();
ParentSpec pspec;
uint64_t 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.", -1));
+ std::string oid = get_temp_image_name();
+ ASSERT_EQ(0, create_image(&ioctx, oid, 33<<20, 22, 0, "old_blk.", -1));
// get nonexistent parent: succeed, return (-1, "", CEPH_NOSNAP), overlap 0
- ASSERT_EQ(0, get_parent(&ioctx, "old", CEPH_NOSNAP, &pspec, &size));
+ ASSERT_EQ(0, get_parent(&ioctx, oid, 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 = ParentSpec(-1, "parent", 3);
- ASSERT_EQ(-ENOEXEC, set_parent(&ioctx, "old", ParentSpec(-1, "parent", 3), 10<<20));
- ASSERT_EQ(-ENOEXEC, remove_parent(&ioctx, "old"));
+ ASSERT_EQ(-ENOEXEC, set_parent(&ioctx, oid, ParentSpec(-1, "parent", 3), 10<<20));
+ ASSERT_EQ(-ENOEXEC, remove_parent(&ioctx, oid));
// new image will work
+ oid = get_temp_image_name();
ASSERT_EQ(0, create_image(&ioctx, oid, 33<<20, 22, RBD_FEATURE_LAYERING,
"foo.", -1));
ioctx.close();
}
+TEST_F(TestClsRbd, parents_v2)
+{
+ librados::IoCtx ioctx;
+ ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), ioctx));
+
+ std::string oid = get_temp_image_name();
+ cls::rbd::ParentImageSpec parent_image_spec;
+ std::optional<uint64_t> parent_overlap;
+
+ ASSERT_EQ(-ENOENT, parent_get(&ioctx, oid, &parent_image_spec));
+ ASSERT_EQ(-ENOENT, parent_overlap_get(&ioctx, oid, CEPH_NOSNAP,
+ &parent_overlap));
+ ASSERT_EQ(-ENOENT, parent_attach(&ioctx, oid, parent_image_spec, 0ULL));
+ ASSERT_EQ(-ENOENT, parent_detach(&ioctx, oid));
+
+ // no layering support should fail
+ oid = get_temp_image_name();
+ ASSERT_EQ(0, create_image(&ioctx, oid, 33<<20, 22, 0, "old_blk.", -1));
+ ASSERT_EQ(0, parent_get(&ioctx, oid, &parent_image_spec));
+ ASSERT_FALSE(parent_image_spec.exists());
+ ASSERT_EQ(0, parent_overlap_get(&ioctx, oid, CEPH_NOSNAP, &parent_overlap));
+ ASSERT_EQ(std::nullopt, parent_overlap);
+ ASSERT_EQ(-ENOEXEC, parent_attach(&ioctx, oid, parent_image_spec, 0ULL));
+ ASSERT_EQ(-ENOEXEC, parent_detach(&ioctx, oid));
+
+ // layering support available -- no pool namespaces
+ oid = get_temp_image_name();
+ ASSERT_EQ(0, create_image(&ioctx, oid, 33<<20, 22, RBD_FEATURE_LAYERING,
+ "foo.", -1));
+
+ ASSERT_EQ(0, parent_get(&ioctx, oid, &parent_image_spec));
+ ASSERT_FALSE(parent_image_spec.exists());
+ ASSERT_EQ(0, parent_overlap_get(&ioctx, oid, CEPH_NOSNAP, &parent_overlap));
+ ASSERT_EQ(std::nullopt, parent_overlap);
+ ASSERT_EQ(-EINVAL, parent_attach(&ioctx, oid, parent_image_spec, 0ULL));
+ ASSERT_EQ(-ENOENT, parent_detach(&ioctx, oid));
+
+ parent_image_spec = {1, "", "parent", 2};
+ parent_overlap = (33 << 20) + 1;
+ ASSERT_EQ(0, parent_attach(&ioctx, oid, parent_image_spec, *parent_overlap));
+ ASSERT_EQ(-EEXIST, parent_attach(&ioctx, oid, parent_image_spec,
+ *parent_overlap));
+ --(*parent_overlap);
+
+ cls::rbd::ParentImageSpec on_disk_parent_image_spec;
+ std::optional<uint64_t> on_disk_parent_overlap;
+ ASSERT_EQ(0, parent_get(&ioctx, oid, &on_disk_parent_image_spec));
+ ASSERT_EQ(parent_image_spec, on_disk_parent_image_spec);
+ ASSERT_EQ(0, parent_overlap_get(&ioctx, oid, CEPH_NOSNAP,
+ &on_disk_parent_overlap));
+ ASSERT_EQ(parent_overlap, on_disk_parent_overlap);
+
+ ASSERT_EQ(0, snapshot_add(&ioctx, oid, 10, "snap1"));
+ ASSERT_EQ(0, parent_overlap_get(&ioctx, oid, 10, &on_disk_parent_overlap));
+ std::optional<uint64_t> snap_1_parent_overlap = parent_overlap;
+ ASSERT_EQ(snap_1_parent_overlap, on_disk_parent_overlap);
+
+ parent_overlap = (32 << 20);
+ ASSERT_EQ(0, set_size(&ioctx, oid, *parent_overlap));
+ ASSERT_EQ(0, parent_overlap_get(&ioctx, oid, CEPH_NOSNAP,
+ &on_disk_parent_overlap));
+ ASSERT_EQ(parent_overlap, on_disk_parent_overlap);
+
+ ASSERT_EQ(0, parent_overlap_get(&ioctx, oid, 10, &on_disk_parent_overlap));
+ ASSERT_EQ(snap_1_parent_overlap, on_disk_parent_overlap);
+
+ ASSERT_EQ(0, parent_detach(&ioctx, oid));
+ ASSERT_EQ(-ENOENT, parent_detach(&ioctx, oid));
+
+ ASSERT_EQ(0, parent_get(&ioctx, oid, &on_disk_parent_image_spec));
+ ASSERT_EQ(parent_image_spec, on_disk_parent_image_spec);
+ ASSERT_EQ(0, parent_overlap_get(&ioctx, oid, CEPH_NOSNAP,
+ &on_disk_parent_overlap));
+ ASSERT_EQ(std::nullopt, on_disk_parent_overlap);
+
+ ASSERT_EQ(0, snapshot_remove(&ioctx, oid, 10));
+ ASSERT_EQ(0, parent_get(&ioctx, oid, &on_disk_parent_image_spec));
+ ASSERT_FALSE(on_disk_parent_image_spec.exists());
+
+ // clone across pool namespaces
+ parent_image_spec.pool_namespace = "ns";
+ parent_overlap = 31 << 20;
+ ASSERT_EQ(0, parent_attach(&ioctx, oid, parent_image_spec, *parent_overlap));
+ ASSERT_EQ(-EEXIST, parent_attach(&ioctx, oid, parent_image_spec,
+ *parent_overlap));
+
+ ASSERT_EQ(0, parent_get(&ioctx, oid, &on_disk_parent_image_spec));
+ ASSERT_EQ(parent_image_spec, on_disk_parent_image_spec);
+ ASSERT_EQ(0, parent_overlap_get(&ioctx, oid, CEPH_NOSNAP,
+ &on_disk_parent_overlap));
+ ASSERT_EQ(parent_overlap, on_disk_parent_overlap);
+
+ ASSERT_EQ(0, snapshot_add(&ioctx, oid, 10, "snap1"));
+ ASSERT_EQ(0, parent_overlap_get(&ioctx, oid, 10, &on_disk_parent_overlap));
+ snap_1_parent_overlap = parent_overlap;
+ ASSERT_EQ(snap_1_parent_overlap, on_disk_parent_overlap);
+
+ parent_overlap = (30 << 20);
+ ASSERT_EQ(0, set_size(&ioctx, oid, *parent_overlap));
+ ASSERT_EQ(0, parent_overlap_get(&ioctx, oid, CEPH_NOSNAP,
+ &on_disk_parent_overlap));
+ ASSERT_EQ(parent_overlap, on_disk_parent_overlap);
+
+ ASSERT_EQ(0, parent_overlap_get(&ioctx, oid, 10, &on_disk_parent_overlap));
+ ASSERT_EQ(snap_1_parent_overlap, on_disk_parent_overlap);
+
+ ASSERT_EQ(-EXDEV, remove_parent(&ioctx, oid));
+ ASSERT_EQ(0, parent_detach(&ioctx, oid));
+ ASSERT_EQ(-ENOENT, parent_detach(&ioctx, oid));
+
+ ParentSpec on_disk_parent_spec;
+ uint64_t legacy_parent_overlap;
+ ASSERT_EQ(-EXDEV, get_parent(&ioctx, oid, CEPH_NOSNAP, &on_disk_parent_spec,
+ &legacy_parent_overlap));
+ ASSERT_EQ(-EXDEV, get_parent(&ioctx, oid, 10, &on_disk_parent_spec,
+ &legacy_parent_overlap));
+
+ ASSERT_EQ(0, parent_get(&ioctx, oid, &on_disk_parent_image_spec));
+ ASSERT_EQ(parent_image_spec, on_disk_parent_image_spec);
+ ASSERT_EQ(0, parent_overlap_get(&ioctx, oid, CEPH_NOSNAP,
+ &on_disk_parent_overlap));
+ ASSERT_EQ(std::nullopt, on_disk_parent_overlap);
+
+ ASSERT_EQ(0, snapshot_remove(&ioctx, oid, 10));
+ ASSERT_EQ(0, parent_get(&ioctx, oid, &on_disk_parent_image_spec));
+ ASSERT_FALSE(on_disk_parent_image_spec.exists());
+}
+
TEST_F(TestClsRbd, snapshots)
{
cls::rbd::SnapshotNamespace userSnapNamespace = cls::rbd::UserSnapshotNamespace();