]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: add new namespace compatible parent image cls methods
authorJason Dillaman <dillaman@redhat.com>
Mon, 20 Aug 2018 02:04:29 +0000 (22:04 -0400)
committerJason Dillaman <dillaman@redhat.com>
Wed, 19 Sep 2018 12:04:12 +0000 (08:04 -0400)
Signed-off-by: Jason Dillaman <dillaman@redhat.com>
src/cls/rbd/cls_rbd.cc
src/cls/rbd/cls_rbd_client.cc
src/cls/rbd/cls_rbd_client.h
src/test/cls_rbd/test_cls_rbd.cc

index 12a2ad4f900b0a1fa38e3493fae4ae20027ce44d..38cfb7ca97c3b71b76e8ff62e6c1e04fd2ba92c6 100644 (file)
@@ -621,7 +621,7 @@ int attach(cls_method_context_t hctx, cls_rbd_parent parent) {
   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");
@@ -642,6 +642,8 @@ int detach(cls_method_context_t hctx) {
   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;
   }
@@ -1583,6 +1585,8 @@ int get_parent(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
     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) {
@@ -1666,7 +1670,7 @@ int set_parent(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
  */
 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;
   }
@@ -1674,6 +1678,152 @@ int remove_parent(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
   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
  */
@@ -6897,6 +7047,11 @@ CLS_INIT(rbd)
   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;
@@ -6908,7 +7063,6 @@ CLS_INIT(rbd)
   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;
@@ -7059,6 +7213,8 @@ CLS_INIT(rbd)
   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);
@@ -7068,6 +7224,19 @@ CLS_INIT(rbd)
   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);
index 0df92665591f7746f5e9a7dcf5179250f356ffb8..3c83c13626c6167abfecfbd918401b1f1f87e92f 100644 (file)
@@ -206,6 +206,97 @@ void set_size(librados::ObjectWriteOperation *op, uint64_t size)
   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;
@@ -263,26 +354,38 @@ void set_parent(librados::ObjectWriteOperation *op,
   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);
@@ -291,40 +394,35 @@ int get_flags(librados::IoCtx *ioctx, const std::string &oid,
   }
 
   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);
@@ -333,38 +431,39 @@ int op_features_get(librados::IoCtx *ioctx, const std::string &oid,
   }
 
   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,
index e72d533d3613e1959e2625c77b2ef41a978bc135..7404ff25ab2f4312f29c94f3db0b5a0f58668722 100644 (file)
@@ -61,18 +61,6 @@ int set_size(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,
@@ -89,8 +77,46 @@ void op_features_set(librados::ObjectWriteOperation *op,
                      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,
index 5723988cd2d434e31aad968fb679e031fb7002ae..63b52ff034b17f2ff9c733cea9183e24ec50d0c6 100644 (file)
@@ -646,30 +646,31 @@ TEST_F(TestClsRbd, snapshot_limits)
   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));
 
@@ -820,6 +821,134 @@ TEST_F(TestClsRbd, parents)
   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();