]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
cls/rbd: new cls methods for handling clone v2
authorJason Dillaman <dillaman@redhat.com>
Fri, 19 Jan 2018 01:10:58 +0000 (20:10 -0500)
committerJason Dillaman <dillaman@redhat.com>
Mon, 5 Feb 2018 16:12:00 +0000 (11:12 -0500)
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/cls/rbd/cls_rbd_types.cc
src/cls/rbd/cls_rbd_types.h
src/test/cls_rbd/test_cls_rbd.cc
src/test/encoding/types.h

index 301a8540bde04fce56b12b49d5bf4345c04b12a8..97ce99a9fee533d0b60c7af4bcc998810b53a43d 100644 (file)
@@ -60,12 +60,11 @@ CLS_NAME(rbd)
 
 #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;
@@ -140,6 +139,19 @@ static int read_key(cls_method_context_t hctx, const string &key, T *out)
   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) {
@@ -162,6 +174,14 @@ static bool is_valid_id(const string &id) {
 
 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;
@@ -1229,51 +1249,30 @@ int remove_parent(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
     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;
@@ -1286,6 +1285,15 @@ int remove_parent(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
     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;
 }
 
@@ -1950,12 +1958,33 @@ int snapshot_remove(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
   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;
 }
 
@@ -3008,6 +3037,215 @@ int snapshot_set_limit(cls_method_context_t hctx, bufferlist *in,
 }
 
 
+/**
+ * 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)
@@ -5737,6 +5975,9 @@ CLS_INIT(rbd)
   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;
@@ -5886,6 +6127,15 @@ CLS_INIT(rbd)
   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",
index 9c7f613c60ebae636d0911945ae57f4ec0925550..fa19a5b662639404a24c2a230549bb7d8d8b9564 100644 (file)
@@ -1442,6 +1442,93 @@ namespace librbd {
       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);
index a9b08e923b3f876773f2d1ee09b1ddcca3b20e49..6225f055e61ce82183d1b4c0fb80cc89a2ba283f 100644 (file)
@@ -231,6 +231,24 @@ namespace librbd {
     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);
index c6f5196a06ea18a87c1bb71a207de02edcf3bfab..d709ba80f4ec8c53ff154251e8b8d29409ab0491 100644 (file)
@@ -212,6 +212,30 @@ std::ostream& operator<<(std::ostream& os, const MirrorImageStatus& status) {
   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);
index 2d2a2a138db6de5d4d0005aab3f341563f41bcf4..f34b33773c2c7f6b4e1e6b7bce030b51c2b001ca 100644 (file)
@@ -12,6 +12,7 @@
 #include "include/utime.h"
 #include <iosfwd>
 #include <string>
+#include <set>
 
 #define RBD_GROUP_REF "rbd_group_ref"
 
@@ -162,6 +163,36 @@ std::ostream& operator<<(std::ostream& os, const MirrorImageStatusState& state);
 
 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() {}
 
@@ -182,7 +213,6 @@ struct GroupImageSpec {
   std::string image_key();
 
 };
-
 WRITE_CLASS_ENCODER(GroupImageSpec);
 
 struct GroupImageStatus {
index 1a2e96c37a2b32245795f04242e188e6ef209064..0bd8d754bdf3f6d35f0aa7b3326e6de4a439397b 100644 (file)
@@ -2501,3 +2501,106 @@ TEST_F(TestClsRbd, op_features)
   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);
+}
+
index 9748345ffc6b059df1e7d6bdbbe8f1861950ccc3..ad206456003e6fb38f5916870ec591e38eb9cff5 100644 (file)
@@ -415,6 +415,7 @@ TYPE(cls_rbd_parent)
 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)