]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
rbd: add group snap info command
authorRamana Raja <rraja@redhat.com>
Tue, 18 Jun 2024 21:32:24 +0000 (17:32 -0400)
committerRamana Raja <rraja@redhat.com>
Mon, 22 Jul 2024 16:40:11 +0000 (12:40 -0400)
... to show information about a group snapshot.

And also include group snap ID in `group snap ls` output.

Fixes: https://tracker.ceph.com/issues/66011
Signed-off-by: Ramana Raja <rraja@redhat.com>
15 files changed:
PendingReleaseNotes
doc/man/8/rbd.rst
qa/workunits/rbd/rbd_groups.sh
src/include/rbd/librbd.h
src/include/rbd/librbd.hpp
src/librbd/api/Group.cc
src/librbd/api/Group.h
src/librbd/librbd.cc
src/pybind/rbd/c_rbd.pxd
src/pybind/rbd/mock_rbd.pxi
src/pybind/rbd/rbd.pyx
src/test/cli/rbd/help.t
src/test/librbd/test_Groups.cc
src/test/pybind/test_rbd.py
src/tools/rbd/action/Group.cc

index 25fcbb70db08b306754b5d1900a14f1e3dad1076..88e0c12827d1930bf48fb9bc085085f72e75542e 100644 (file)
@@ -6,6 +6,12 @@
   including `timezone.utc` makes it explicit and avoids the potential of the
   returned timestamp getting misinterpreted -- in Python 3, many `datetime`
   methods treat "naive" `datetime` objects as local times.
+* RBD: `rbd group info` and `rbd group snap info` commands are introduced to
+  show information about a group and a group snapshot respectively.
+* RBD: `rbd group snap ls` output now includes the group snap IDs. The header
+  of the column showing the state of a group snapshot in the unformatted CLI
+  output is changed from 'STATUS' to 'STATE'. The state of a group snapshot
+  that was shown as 'ok' is now shown as 'complete', which is more descriptive.
 
 >=19.0.0
 
index 881e891d76b296173b771b08eeb16d1cb8e4c0c6..df41034e596d0abf160bb64789196308dd874f5c 100644 (file)
@@ -367,6 +367,9 @@ Commands
 :command:`group snap list` *group-spec*
   List snapshots of a group.
 
+:command:`group snap info` *group-snap-spec*
+  Get information about a snapshot of a group.
+
 :command:`group snap rm` *group-snap-spec*
   Remove a snapshot from a group.
 
index a3261848441f046ee53a9ce1dcafe352e9156d6a..450095dabfc1e805ab9cd333996ecf3b26a6344a 100755 (executable)
@@ -25,7 +25,7 @@ list_groups()
 check_group_exists()
 {
     local group_name=$1
-    list_groups | grep $group_name
+    list_groups | grep -w $group_name
 }
 
 remove_group()
@@ -165,7 +165,7 @@ check_snapshot_in_group()
 {
     local group_name=$1
     local snap_name=$2
-    list_snapshots $group_name | grep $snap_name
+    list_snapshots $group_name | grep -w $snap_name
 }
 
 check_snapshots_count_in_group()
@@ -182,12 +182,43 @@ check_snapshot_not_in_group()
 {
     local group_name=$1
     local snap_name=$2
-    for v in $(list_snapshots $group_name | awk '{print $1}'); do
-        if [ "$v" = "$snap_name" ]; then
-            return 1
-        fi
-    done
-    return 0
+
+    check_group_exists $group_name || return 1
+    ! check_snapshot_in_group $group_name $snap_name
+}
+
+check_snap_id_in_list_snapshots()
+{
+    local group_name=$1
+    local snap_name=$2
+
+    local snap_id_in_info=$(
+        rbd group snap info $group_name@$snap_name --format=json |
+        jq -r '.id')
+    [[ -n "$snap_id_in_info" ]] || return 1
+
+    local snap_id_in_list=$(
+        rbd group snap ls $group_name --format=json |
+        jq --arg snap_name $snap_name -r '
+            .[] | select(.snapshot == $snap_name) | .id')
+    test "$snap_id_in_list" = "$snap_id_in_info"
+}
+
+check_snapshot_info()
+{
+    local group_name=$1
+    local snap_name=$2
+    local image_count=$3
+
+    local snap_info=$(rbd group snap info $group_name@$snap_name --format=json)
+    local actual_snap_name=$(jq -r ".name" <<< "$snap_info")
+    test "$actual_snap_name" = "$snap_name" || return 1
+
+    local snap_state=$(jq -r ".state" <<< "$snap_info")
+    test "$snap_state" = "complete" || return 1
+
+    local actual_image_count=$(jq '.images | length' <<< "$snap_info")
+    test "$actual_image_count" = "$image_count"
 }
 
 echo "TEST: create remove consistency group"
@@ -217,23 +248,24 @@ echo "PASSED"
 echo "TEST: create remove snapshots of consistency group"
 image="test_image"
 group="test_consistency_group"
-snap="group_snap"
-new_snap="new_group_snap"
-sec_snap="group_snap2"
+snaps=("group_snap1" "group_snap2" "group_snap3" "group_snap4")
 create_image $image
 create_group $group
+create_snapshot $group ${snaps[0]}
+check_snapshot_info $group ${snaps[0]} 0
 add_image_to_group $image $group
-create_snapshot $group $snap
-check_snapshot_in_group $group $snap
-rename_snapshot $group $snap $new_snap
-check_snapshot_not_in_group $group $snap
-create_snapshot $group $sec_snap
-check_snapshot_in_group $group $sec_snap
-rollback_snapshot $group $new_snap
-remove_snapshot $group $new_snap
-check_snapshot_not_in_group $group $new_snap
-remove_snapshot $group $sec_snap
-check_snapshot_not_in_group $group $sec_snap
+create_snapshot $group ${snaps[1]}
+check_snapshot_info $group ${snaps[1]} 1
+rename_snapshot $group ${snaps[1]} ${snaps[2]}
+check_snapshot_info $group ${snaps[2]} 1
+check_snapshot_not_in_group $group ${snaps[1]}
+create_snapshot $group ${snaps[3]}
+check_snapshot_in_group $group ${snaps[3]}
+rollback_snapshot $group ${snaps[2]}
+remove_snapshot $group ${snaps[2]}
+check_snapshot_not_in_group $group ${snaps[2]}
+remove_snapshot $group ${snaps[3]}
+check_snapshot_not_in_group $group ${snaps[3]}
 remove_group $group
 remove_image $image
 echo "PASSED"
@@ -247,6 +279,7 @@ create_group $group
 add_image_to_group $image $group
 create_snapshots $group $snap 10
 check_snapshots_count_in_group $group $snap 10
+check_snap_id_in_list_snapshots $group ${snap}1
 remove_snapshots $group $snap 10
 create_snapshots $group $snap 100
 check_snapshots_count_in_group $group $snap 100
index 267ed289bf7b2885e3aa376506ab31e2068d9f0e..f9af9262b2aa3bdcf09e183012de62caf8872939 100644 (file)
@@ -249,11 +249,27 @@ typedef enum {
   RBD_GROUP_SNAP_STATE_COMPLETE
 } rbd_group_snap_state_t;
 
+typedef struct {
+  char *image_name;
+  int64_t pool_id;
+  uint64_t snap_id;
+} rbd_group_image_snap_info_t;
+
 typedef struct {
   char *name;
   rbd_group_snap_state_t state;
 } rbd_group_snap_info_t;
 
+typedef struct {
+  char *id;
+  char *name;
+  char *image_snap_name;
+  rbd_group_snap_state_t state;
+  //rbd_group_snap_namespace_type_t namespace_type;
+  size_t image_snaps_count;
+  rbd_group_image_snap_info_t *image_snaps;
+} rbd_group_snap_info2_t;
+
 typedef struct {
   int64_t group_pool;
   char *group_name;
@@ -1491,6 +1507,18 @@ CEPH_RBD_API int rbd_group_snap_list(rados_ioctx_t group_p,
 CEPH_RBD_API int rbd_group_snap_list_cleanup(rbd_group_snap_info_t *snaps,
                                              size_t group_snap_info_size,
                                              size_t num_entries);
+CEPH_RBD_API int rbd_group_snap_list2(rados_ioctx_t group_p,
+                                      const char *group_name,
+                                      rbd_group_snap_info2_t *snaps,
+                                      size_t *num_entries);
+CEPH_RBD_API void rbd_group_snap_list2_cleanup(rbd_group_snap_info2_t *snaps,
+                                               size_t num_entries);
+CEPH_RBD_API int rbd_group_snap_get_info(rados_ioctx_t group_p,
+                                         const char *group_name,
+                                         const char *snap_name,
+                                         rbd_group_snap_info2_t *group_snap);
+CEPH_RBD_API void rbd_group_snap_get_info_cleanup(
+    rbd_group_snap_info2_t *group_snap);
 CEPH_RBD_API int rbd_group_snap_rollback(rados_ioctx_t group_p,
                                          const char *group_name,
                                          const char *snap_name);
index c02d179450c90a71d30a1b88c55ef2fe5571358e..50a6c623d3a00c7e6e21175e28707e3b20c5561e 100644 (file)
@@ -161,11 +161,26 @@ namespace librbd {
 
   typedef rbd_group_snap_state_t group_snap_state_t;
 
+  typedef struct {
+    std::string image_name;
+    int64_t pool_id;
+    uint64_t snap_id;
+  } group_image_snap_info_t;
+
   typedef struct {
     std::string name;
     group_snap_state_t state;
   } group_snap_info_t;
 
+  typedef struct {
+    std::string id;
+    std::string name;
+    std::string image_snap_name;
+    group_snap_state_t state;
+    //group_snap_namespace_type_t namespace_type;
+    std::vector<group_image_snap_info_t> image_snaps;
+  } group_snap_info2_t;
+
   typedef rbd_image_info_t image_info_t;
 
   class CEPH_RBD_API ProgressContext
@@ -443,6 +458,11 @@ public:
   int group_snap_list(IoCtx& group_ioctx, const char *group_name,
                       std::vector<group_snap_info_t> *snaps,
                       size_t group_snap_info_size);
+  int group_snap_list2(IoCtx& group_ioctx, const char *group_name,
+                       std::vector<group_snap_info2_t> *snaps);
+  int group_snap_get_info(IoCtx& group_ioctx, const char *group_name,
+                          const char *snap_name,
+                          group_snap_info2_t *group_snap);
   int group_snap_rollback(IoCtx& io_ctx, const char *group_name,
                           const char *snap_name);
   int group_snap_rollback_with_progress(IoCtx& io_ctx, const char *group_name,
index 58c7499e5c9755f5b2f56de226408ee3e35eea25..f3891dcf163d34587c94b279d9eb6b21d0fb0a4f 100644 (file)
@@ -53,26 +53,16 @@ snap_t get_group_snap_id(I* ictx,
   return CEPH_NOSNAP;
 }
 
-int group_snap_list(librados::IoCtx& group_ioctx, const char *group_name,
+int group_snap_list(librados::IoCtx& group_ioctx, const std::string& group_id,
                    std::vector<cls::rbd::GroupSnapshot> *cls_snaps)
 {
   CephContext *cct = (CephContext *)group_ioctx.cct();
 
-  string group_id;
-  vector<string> ind_snap_names;
-
-  int r = cls_client::dir_get_id(&group_ioctx, RBD_GROUP_DIRECTORY,
-                                group_name, &group_id);
-  if (r < 0) {
-    lderr(cct) << "error reading group id object: "
-              << cpp_strerror(r)
-              << dendl;
-    return r;
-  }
   string group_header_oid = util::group_header_name(group_id);
 
   const int max_read = 1024;
   cls::rbd::GroupSnapshot snap_last;
+  int r;
 
   for (;;) {
     vector<cls::rbd::GroupSnapshot> snaps_page;
@@ -476,6 +466,48 @@ int notify_quiesce(std::vector<I*> &ictxs, ProgressContext &prog_ctx,
   return ret_code;
 }
 
+int GroupSnapshot_to_group_snap_info2(
+    librados::IoCtx& group_ioctx, const std::string& group_id,
+    const cls::rbd::GroupSnapshot& cls_group_snap,
+    group_snap_info2_t* group_snap)
+{
+  std::vector<group_image_snap_info_t> image_snaps;
+  image_snaps.reserve(cls_group_snap.snaps.size());
+
+  for (const auto& snap : cls_group_snap.snaps) {
+    librbd::IoCtx image_ioctx;
+    int r = util::create_ioctx(group_ioctx, "image", snap.pool, {},
+                               &image_ioctx);
+    if (r < 0) {
+      return r;
+    }
+
+    std::string image_name;
+    r = cls_client::dir_get_name(&image_ioctx, RBD_DIRECTORY, snap.image_id,
+                                 &image_name);
+    if (r < 0) {
+      return r;
+    }
+
+    image_snaps.push_back(
+      group_image_snap_info_t {
+        std::move(image_name),
+        snap.pool,
+        snap.snap_id
+      });
+  }
+
+  group_snap->id = cls_group_snap.id;
+  group_snap->name = cls_group_snap.name;
+  group_snap->state = static_cast<group_snap_state_t>(cls_group_snap.state);
+  group_snap->image_snap_name = calc_ind_image_snap_name(group_ioctx.get_id(),
+                                                         group_id,
+                                                         cls_group_snap.id);
+  group_snap->image_snaps = std::move(image_snaps);
+
+  return 0;
+}
+
 } // anonymous namespace
 
 template <typename I>
@@ -560,7 +592,7 @@ int Group<I>::remove(librados::IoCtx& io_ctx, const char *group_name)
   string group_header_oid = util::group_header_name(group_id);
 
   std::vector<cls::rbd::GroupSnapshot> snaps;
-  r = group_snap_list(io_ctx, group_name, &snaps);
+  r = group_snap_list(io_ctx, group_id, &snaps);
   if (r < 0 && r != -ENOENT) {
     lderr(cct) << "error listing group snapshots" << dendl;
     return r;
@@ -1158,7 +1190,7 @@ int Group<I>::snap_remove(librados::IoCtx& group_ioctx, const char *group_name,
   }
 
   std::vector<cls::rbd::GroupSnapshot> snaps;
-  r = group_snap_list(group_ioctx, group_name, &snaps);
+  r = group_snap_list(group_ioctx, group_id, &snaps);
   if (r < 0) {
     return r;
   }
@@ -1199,7 +1231,7 @@ int Group<I>::snap_rename(librados::IoCtx& group_ioctx, const char *group_name,
   }
 
   std::vector<cls::rbd::GroupSnapshot> group_snaps;
-  r = group_snap_list(group_ioctx, group_name, &group_snaps);
+  r = group_snap_list(group_ioctx, group_id, &group_snaps);
   if (r < 0) {
     return r;
   }
@@ -1228,22 +1260,78 @@ int Group<I>::snap_rename(librados::IoCtx& group_ioctx, const char *group_name,
 
 template <typename I>
 int Group<I>::snap_list(librados::IoCtx& group_ioctx, const char *group_name,
-                       std::vector<group_snap_info_t> *snaps)
+                       std::vector<group_snap_info2_t> *group_snaps)
+{
+  CephContext *cct = (CephContext *)group_ioctx.cct();
+
+  std::string group_id;
+  int r = cls_client::dir_get_id(&group_ioctx, RBD_GROUP_DIRECTORY,
+                                 group_name, &group_id);
+  if (r < 0) {
+    lderr(cct) << "error reading group id object: " << cpp_strerror(r)
+               << dendl;
+    return r;
+  }
+
+  std::vector<cls::rbd::GroupSnapshot> cls_group_snaps;
+  r = group_snap_list(group_ioctx, group_id, &cls_group_snaps);
+  if (r < 0) {
+    return r;
+  }
+
+  std::vector<group_snap_info2_t> group_snaps_tmp(cls_group_snaps.size());
+  for (size_t i = 0; i < cls_group_snaps.size(); i++) {
+    r = GroupSnapshot_to_group_snap_info2(group_ioctx, group_id,
+                                          cls_group_snaps[i],
+                                          &group_snaps_tmp[i]);
+    if (r < 0) {
+      return r;
+    }
+  }
+
+  *group_snaps = std::move(group_snaps_tmp);
+  return 0;
+}
+
+template <typename I>
+int Group<I>::snap_get_info(librados::IoCtx& group_ioctx,
+                            const char *group_name, const char *snap_name,
+                            group_snap_info2_t* group_snap)
 {
-  std::vector<cls::rbd::GroupSnapshot> cls_snaps;
+  CephContext *cct = (CephContext *)group_ioctx.cct();
 
-  int r = group_snap_list(group_ioctx, group_name, &cls_snaps);
+  std::string group_id;
+  int r = cls_client::dir_get_id(&group_ioctx, RBD_GROUP_DIRECTORY,
+                                 group_name, &group_id);
   if (r < 0) {
+    lderr(cct) << "error reading group id object: " << cpp_strerror(r)
+               << dendl;
     return r;
   }
 
-  for (auto snap : cls_snaps) {
-    snaps->push_back(
-       group_snap_info_t {
-          snap.name,
-          static_cast<group_snap_state_t>(snap.state)});
+  std::vector<cls::rbd::GroupSnapshot> cls_group_snaps;
+  r = group_snap_list(group_ioctx, group_id, &cls_group_snaps);
+  if (r < 0) {
+    return r;
+  }
 
+  const cls::rbd::GroupSnapshot *cls_group_snap_ptr = nullptr;
+  for (const auto& cls_group_snap : cls_group_snaps) {
+    if (cls_group_snap.name == snap_name) {
+      cls_group_snap_ptr = &cls_group_snap;
+      break;
+    }
+  }
+  if (cls_group_snap_ptr == nullptr) {
+    return -ENOENT;
+  }
+
+  r = GroupSnapshot_to_group_snap_info2(group_ioctx, group_id,
+                                        *cls_group_snap_ptr, group_snap);
+  if (r < 0) {
+    return r;
   }
+
   return 0;
 }
 
@@ -1264,7 +1352,7 @@ int Group<I>::snap_rollback(librados::IoCtx& group_ioctx,
   }
 
   std::vector<cls::rbd::GroupSnapshot> snaps;
-  r = group_snap_list(group_ioctx, group_name, &snaps);
+  r = group_snap_list(group_ioctx, group_id, &snaps);
   if (r < 0) {
     return r;
   }
index 98833eb506f541fb3d6744bc9007dede5faf6b84..2a7574612ec1a1f4e0154dc29b9dca2782994e86 100644 (file)
@@ -47,7 +47,10 @@ struct Group {
   static int snap_rename(librados::IoCtx& group_ioctx, const char *group_name,
                          const char *old_snap_name, const char *new_snap_name);
   static int snap_list(librados::IoCtx& group_ioctx, const char *group_name,
-                       std::vector<group_snap_info_t> *snaps);
+                       std::vector<group_snap_info2_t> *snaps);
+  static int snap_get_info(librados::IoCtx& group_ioctx,
+                           const char *group_name, const char *snap_name,
+                           group_snap_info2_t* group_snap);
   static int snap_rollback(librados::IoCtx& group_ioctx,
                            const char *group_name, const char *snap_name,
                            ProgressContext& pctx);
index df45e370af4516ee0c5f89c9bbaac51b7a34f9d1..ed8ec9e913057703a769cf88406b863b9daf60d9 100644 (file)
@@ -263,12 +263,31 @@ void group_info_cpp_to_c(const librbd::group_info_t &cpp_info,
   c_info->pool = cpp_info.pool;
 }
 
-void group_snap_info_cpp_to_c(const librbd::group_snap_info_t &cpp_info,
+void group_snap_info_cpp_to_c(const librbd::group_snap_info2_t &cpp_info,
                              rbd_group_snap_info_t *c_info) {
   c_info->name = strdup(cpp_info.name.c_str());
   c_info->state = cpp_info.state;
 }
 
+void group_snap_info2_cpp_to_c(const librbd::group_snap_info2_t &cpp_info,
+                               rbd_group_snap_info2_t *c_info) {
+  c_info->id = strdup(cpp_info.id.c_str());
+  c_info->name = strdup(cpp_info.name.c_str());
+  c_info->image_snap_name = strdup(cpp_info.image_snap_name.c_str());
+  c_info->state = cpp_info.state;
+  c_info->image_snaps_count = cpp_info.image_snaps.size();
+  c_info->image_snaps = static_cast<rbd_group_image_snap_info_t*>(calloc(
+    cpp_info.image_snaps.size(), sizeof(rbd_group_image_snap_info_t)));
+  size_t i = 0;
+  for (const auto& cpp_image_snap : cpp_info.image_snaps) {
+    c_info->image_snaps[i].image_name = strdup(
+      cpp_image_snap.image_name.c_str());
+    c_info->image_snaps[i].pool_id = cpp_image_snap.pool_id;
+    c_info->image_snaps[i].snap_id = cpp_image_snap.snap_id;
+    i++;
+  }
+}
+
 void mirror_image_info_cpp_to_c(const librbd::mirror_image_info_t &cpp_info,
                                rbd_mirror_image_info_t *c_info) {
   c_info->global_id = strdup(cpp_info.global_id.c_str());
@@ -1436,11 +1455,34 @@ namespace librbd {
       return -ERANGE;
     }
 
-    int r = librbd::api::Group<>::snap_list(group_ioctx, group_name, snaps);
+    std::vector<group_snap_info2_t> snaps2;
+    int r = librbd::api::Group<>::snap_list(group_ioctx, group_name, &snaps2);
+
+    for (const auto& snap : snaps2) {
+      snaps->push_back(
+        group_snap_info_t {
+          snap.name,
+          snap.state
+        });
+    }
+
     tracepoint(librbd, group_snap_list_exit, r);
     return r;
   }
 
+  int RBD::group_snap_list2(IoCtx& group_ioctx, const char *group_name,
+                            std::vector<group_snap_info2_t> *snaps)
+  {
+    return librbd::api::Group<>::snap_list(group_ioctx, group_name, snaps);
+  }
+
+  int RBD::group_snap_get_info(IoCtx& group_ioctx, const char *group_name,
+                               const char *snap_name,
+                               group_snap_info2_t *group_snap) {
+    return librbd::api::Group<>::snap_get_info(group_ioctx, group_name,
+                                               snap_name, group_snap);
+  }
+
   int RBD::group_snap_rename(IoCtx& group_ioctx, const char *group_name,
                              const char *old_snap_name,
                              const char *new_snap_name)
@@ -7230,6 +7272,34 @@ extern "C" int rbd_group_snap_rename(rados_ioctx_t group_p,
   return r;
 }
 
+extern "C" int rbd_group_snap_get_info(
+    rados_ioctx_t group_p, const char *group_name, const char *snap_name,
+    rbd_group_snap_info2_t *group_snap)
+{
+  librados::IoCtx group_ioctx;
+  librados::IoCtx::from_rados_ioctx_t(group_p, group_ioctx);
+
+  librbd::group_snap_info2_t cpp_group_snap;
+  int r = librbd::api::Group<>::snap_get_info(group_ioctx, group_name,
+                                              snap_name, &cpp_group_snap);
+  if (r < 0) {
+    return r;
+  }
+  group_snap_info2_cpp_to_c(cpp_group_snap, group_snap);
+  return 0;
+}
+
+extern "C" void rbd_group_snap_get_info_cleanup(
+    rbd_group_snap_info2_t *group_snap) {
+  free(group_snap->id);
+  free(group_snap->name);
+  free(group_snap->image_snap_name);
+  for (size_t i = 0; i < group_snap->image_snaps_count; ++i) {
+    free(group_snap->image_snaps[i].image_name);
+  }
+  free(group_snap->image_snaps);
+}
+
 extern "C" int rbd_group_snap_list(rados_ioctx_t group_p,
                                    const char *group_name,
                                    rbd_group_snap_info_t *snaps,
@@ -7251,7 +7321,7 @@ extern "C" int rbd_group_snap_list(rados_ioctx_t group_p,
     return -ERANGE;
   }
 
-  std::vector<librbd::group_snap_info_t> cpp_snaps;
+  std::vector<librbd::group_snap_info2_t> cpp_snaps;
   int r = librbd::api::Group<>::snap_list(group_ioctx, group_name, &cpp_snaps);
 
   if (r == -ENOENT) {
@@ -7293,6 +7363,40 @@ extern "C" int rbd_group_snap_list_cleanup(rbd_group_snap_info_t *snaps,
   return 0;
 }
 
+extern "C" int rbd_group_snap_list2(rados_ioctx_t group_p,
+                                    const char *group_name,
+                                    rbd_group_snap_info2_t *snaps,
+                                    size_t *snaps_size)
+{
+  librados::IoCtx group_ioctx;
+  librados::IoCtx::from_rados_ioctx_t(group_p, group_ioctx);
+
+  std::vector<librbd::group_snap_info2_t> cpp_snaps;
+  int r = librbd::api::Group<>::snap_list(group_ioctx, group_name, &cpp_snaps);
+  if (r < 0) {
+    return r;
+  }
+
+  if (*snaps_size < cpp_snaps.size()) {
+    *snaps_size = cpp_snaps.size();
+    return -ERANGE;
+  }
+
+  for (size_t i = 0; i < cpp_snaps.size(); ++i) {
+    group_snap_info2_cpp_to_c(cpp_snaps[i], &snaps[i]);
+  }
+
+  *snaps_size = cpp_snaps.size();
+  return 0;
+}
+
+extern "C" void rbd_group_snap_list2_cleanup(rbd_group_snap_info2_t *snaps,
+                                             size_t len) {
+  for (size_t i = 0; i < len; ++i) {
+    rbd_group_snap_get_info_cleanup(&snaps[i]);
+  }
+}
+
 extern "C" int rbd_group_snap_rollback(rados_ioctx_t group_p,
                                        const char *group_name,
                                        const char *snap_name)
index c39e945b465dc30c8b0fcbf2372697a4e6c76ce3..3a960a2a885eb2971c521ea14f0d7c155cb631a6 100644 (file)
@@ -224,9 +224,18 @@ cdef extern from "rbd/librbd.h" nogil:
         _RBD_GROUP_SNAP_STATE_INCOMPLETE "RBD_GROUP_SNAP_STATE_INCOMPLETE"
         _RBD_GROUP_SNAP_STATE_COMPLETE "RBD_GROUP_SNAP_STATE_COMPLETE"
 
-    ctypedef struct rbd_group_snap_info_t:
+    ctypedef struct rbd_group_image_snap_info_t:
+        char *image_name
+        int64_t pool_id
+        uint64_t snap_id
+
+    ctypedef struct rbd_group_snap_info2_t:
+        char *id
         char *name
+        char *image_snap_name
         rbd_group_snap_state_t state
+        size_t image_snaps_count
+        rbd_group_image_snap_info_t *image_snaps
 
     ctypedef enum rbd_image_migration_state_t:
         _RBD_IMAGE_MIGRATION_STATE_UNKNOWN "RBD_IMAGE_MIGRATION_STATE_UNKNOWN"
@@ -700,14 +709,18 @@ cdef extern from "rbd/librbd.h" nogil:
                               const char *old_snap_name,
                               const char *new_snap_name)
 
-    int rbd_group_snap_list(rados_ioctx_t group_p,
-                            const char *group_name,
-                            rbd_group_snap_info_t *snaps,
-                            size_t group_snap_info_size,
-                            size_t *snaps_size)
+    int rbd_group_snap_get_info(rados_ioctx_t group_p, const char *group_name,
+                                const char *snap_name,
+                                rbd_group_snap_info2_t *group_snap)
+    void rbd_group_snap_get_info_cleanup(rbd_group_snap_info2_t *group_snap)
+
+    int rbd_group_snap_list2(rados_ioctx_t group_p,
+                             const char *group_name,
+                             rbd_group_snap_info2_t *snaps,
+                             size_t *snaps_size)
+    void rbd_group_snap_list2_cleanup(rbd_group_snap_info2_t *snaps,
+                                      size_t len)
 
-    void rbd_group_snap_list_cleanup(rbd_group_snap_info_t *snaps,
-                                     size_t group_snap_info_size, size_t len)
     int rbd_group_snap_rollback(rados_ioctx_t group_p, const char *group_name,
                                 const char *snap_name)
 
index bc6132ce6b92f9374d4be53aa3865ffe241a1815..40280dc9c0d9c3c921c97b9ff76a020e91320e8e 100644 (file)
@@ -228,9 +228,18 @@ cdef nogil:
         _RBD_GROUP_SNAP_STATE_INCOMPLETE "RBD_GROUP_SNAP_STATE_INCOMPLETE"
         _RBD_GROUP_SNAP_STATE_COMPLETE "RBD_GROUP_SNAP_STATE_COMPLETE"
 
-    ctypedef struct rbd_group_snap_info_t:
+    ctypedef struct rbd_group_image_snap_info_t:
+        char *image_name
+        int64_t pool_id
+        uint64_t snap_id
+
+    ctypedef struct rbd_group_snap_info2_t:
+        char *id
         char *name
+        char *image_snap_name
         rbd_group_snap_state_t state
+        size_t image_snaps_count
+        rbd_group_image_snap_info_t *image_snaps
 
     ctypedef enum rbd_image_migration_state_t:
         _RBD_IMAGE_MIGRATION_STATE_UNKNOWN "RBD_IMAGE_MIGRATION_STATE_UNKNOWN"
@@ -886,14 +895,19 @@ cdef nogil:
                               const char *old_snap_name,
                               const char *new_snap_name):
         pass
-    int rbd_group_snap_list(rados_ioctx_t group_p,
-                            const char *group_name,
-                            rbd_group_snap_info_t *snaps,
-                            size_t group_snap_info_size,
-                            size_t *snaps_size):
+    int rbd_group_snap_list2(rados_ioctx_t group_p,
+                             const char *group_name,
+                             rbd_group_snap_info2_t *snaps,
+                             size_t *snaps_size):
+        pass
+    void rbd_group_snap_list2_cleanup(rbd_group_snap_info2_t *snaps,
+                                      size_t len):
+        pass
+    int rbd_group_snap_get_info(rados_ioctx_t group_p, const char *group_name,
+                                const char *snap_name,
+                                rbd_group_snap_info2_t *group_snap):
         pass
-    void rbd_group_snap_list_cleanup(rbd_group_snap_info_t *snaps,
-                                     size_t group_snap_info_size, size_t len):
+    void rbd_group_snap_get_info_cleanup(rbd_group_snap_info2_t *group_snap):
         pass
     int rbd_group_snap_rollback(rados_ioctx_t group_p, const char *group_name,
                                 const char *snap_name):
index d023e231bcfd929014a4787480d3309b7d3a6a17..5290e48832d14d8177e49bf9ba28ceb9577521bb 100644 (file)
@@ -2778,6 +2778,71 @@ cdef class Group(object):
         if ret != 0:
             raise make_ex(ret, 'error removing group snapshot', group_errno_to_exception)
 
+    def get_snap_info(self, snap_name):
+        """
+        Get information about a group snapshot.
+
+        :param snap_name: the name of the snapshot to get
+        :type name: str
+
+        :raises: :class:`ObjectNotFound`
+        :raises: :class:`InvalidArgument`
+        :raises: :class:`FunctionNotSupported`
+
+        :returns: dict - contains the following keys:
+
+            * ``id`` (str) - ID of the group snapshot
+
+            * ``name`` (str) - name of the group snapshot
+
+            * ``state`` (int) - state of the group snapshot
+
+            * ``image_snap_name`` (str) - name of the image snapshots
+
+            * ``image_snaps`` (list) - image snapshots that constitute the group snapshot.
+
+              Each image snapshot is itself a dictionary with keys:
+
+              * ``pool_id`` (int) - ID of the image's pool
+
+              * ``snap_id`` (int) - ID of the image snapshot
+
+              * ``image_name`` (str) - name of the image
+
+        """
+        snap_name = cstr(snap_name, 'snap_name')
+        cdef:
+            char *_snap_name = snap_name
+            rbd_group_snap_info2_t group_snap
+
+        with nogil:
+            ret = rbd_group_snap_get_info(self._ioctx, self._name,
+                                          _snap_name, &group_snap)
+        if ret != 0:
+            raise make_ex(ret, 'error showing a group snapshot',
+                          group_errno_to_exception)
+        image_snaps = []
+        for i in range(group_snap.image_snaps_count):
+            image_snap = &group_snap.image_snaps[i]
+            image_snaps.append(
+                {
+                    'pool_id': image_snap.pool_id,
+                    'snap_id': image_snap.snap_id,
+                    'image_name': decode_cstr(image_snap.image_name),
+                }
+            )
+        snap_info = {
+            'id': decode_cstr(group_snap.id),
+            'name': decode_cstr(group_snap.name),
+            'state': group_snap.state,
+            'image_snap_name': decode_cstr(group_snap.image_snap_name),
+            'image_snaps': image_snaps
+        }
+
+        rbd_group_snap_get_info_cleanup(&group_snap)
+
+        return snap_info
+
     def rename_snap(self, old_snap_name, new_snap_name):
         """
         Rename group's snapshot.
@@ -2802,7 +2867,7 @@ cdef class Group(object):
 
     def list_snaps(self):
         """
-        Iterate over the images of a group.
+        Iterate over the snapshots of a group.
 
         :returns: :class:`GroupSnapIterator`
         """
@@ -5934,47 +5999,72 @@ cdef class GroupSnapIterator(object):
     """
     Iterator over snaps specs for a group.
 
-    Yields a dictionary containing information about a snapshot.
+    Yields a dictionary containing information about a group snapshot.
 
     Keys are:
 
-    * ``name`` (str) - name of the snapshot
+    * ``id`` (str) - ID of the group snapshot
+
+    * ``name`` (str) - name of the group snapshot
+
+    * ``state`` (int) - state of the group snapshot
+
+    * ``image_snap_name`` (str) - name of the image snapshots
+
+    * ``image_snaps`` (list) - image snapshots that constitute the group snapshot.
+
+      Each image snapshot is itself a dictionary with keys:
 
-    * ``state`` (int) - state of the snapshot
+      * ``pool_id`` (int) - ID of the image's pool
+
+      * ``snap_id`` (int) - ID of the image snapshot
+
+      * ``image_name`` (str) - name of the image
     """
 
-    cdef rbd_group_snap_info_t *snaps
-    cdef size_t num_snaps
+    cdef rbd_group_snap_info2_t *group_snaps
+    cdef size_t num_group_snaps
     cdef object group
 
     def __init__(self, Group group):
         self.group = group
-        self.snaps = NULL
-        self.num_snaps = 10
+        self.group_snaps = NULL
+        self.num_group_snaps = 10
         while True:
-            self.snaps = <rbd_group_snap_info_t*>realloc_chk(self.snaps,
-                                                             self.num_snaps *
-                                                             sizeof(rbd_group_snap_info_t))
+            self.group_snaps = <rbd_group_snap_info2_t*>realloc_chk(
+                self.group_snaps, self.num_group_snaps * sizeof(rbd_group_snap_info2_t))
             with nogil:
-                ret = rbd_group_snap_list(group._ioctx, group._name, self.snaps,
-                                          sizeof(rbd_group_snap_info_t),
-                                          &self.num_snaps)
-
+                ret = rbd_group_snap_list2(group._ioctx, group._name, self.group_snaps,
+                                           &self.num_group_snaps)
             if ret >= 0:
                 break
             elif ret != -errno.ERANGE:
                 raise make_ex(ret, 'error listing snapshots for group %s' % group.name, group_errno_to_exception)
 
     def __iter__(self):
-        for i in range(self.num_snaps):
+        for i in range(self.num_group_snaps):
+            group_snap = &self.group_snaps[i]
+            image_snaps = []
+            for j in range(group_snap.image_snaps_count):
+                image_snap = &group_snap.image_snaps[j]
+                image_snaps.append(
+                    {
+                        'pool_id': image_snap.pool_id,
+                        'snap_id': image_snap.snap_id,
+                        'image_name': decode_cstr(image_snap.image_name),
+                    }
+                )
+
             yield {
-                'name'  : decode_cstr(self.snaps[i].name),
-                'state' : self.snaps[i].state,
-                }
+                'id': decode_cstr(group_snap.id),
+                'name': decode_cstr(group_snap.name),
+                'state': group_snap.state,
+                'image_snap_name': decode_cstr(group_snap.image_snap_name),
+                'image_snaps': image_snaps,
+            }
 
     def __dealloc__(self):
-        if self.snaps:
-            rbd_group_snap_list_cleanup(self.snaps,
-                                        sizeof(rbd_group_snap_info_t),
-                                        self.num_snaps)
-            free(self.snaps)
+        if self.group_snaps:
+            rbd_group_snap_list2_cleanup(self.group_snaps,
+                                         self.num_group_snaps)
+            free(self.group_snaps)
index c981996ea9a5fc748e1bdef1f8816e3684be0a7c..bee99c51dd10905a4aa620023870ab98f11fa18d 100644 (file)
@@ -53,6 +53,7 @@
       group remove (group rm)           Delete a group.
       group rename                      Rename a group within pool.
       group snap create                 Make a snapshot of a group.
+      group snap info                   Show information about a group snapshot.
       group snap list (... ls)          List snapshots of a group.
       group snap remove (... rm)        Remove a snapshot from a group.
       group snap rename                 Rename group's snapshot.
     --skip-quiesce          do not run quiesce hooks
     --ignore-quiesce-error  ignore quiesce hook error
   
+  rbd help group snap info
+  usage: rbd group snap info [--pool <pool>] [--namespace <namespace>] 
+                             [--group <group>] [--snap <snap>] 
+                             [--format <format>] [--pretty-format] 
+                             <group-snap-spec> 
+  
+  Show information about a group snapshot.
+  
+  Positional arguments
+    <group-snap-spec>    group specification
+                         (example:
+                         [<pool-name>/[<namespace>/]]<group-name>@<snap-name>)
+  
+  Optional arguments
+    -p [ --pool ] arg    pool name
+    --namespace arg      namespace name
+    --group arg          group name
+    --snap arg           snapshot name
+    --format arg         output format (plain, json, or xml) [default: plain]
+    --pretty-format      pretty formatting (json and xml)
+  
   rbd help group snap list
   usage: rbd group snap list [--format <format>] [--pretty-format] 
                              [--pool <pool>] [--namespace <namespace>] 
index 16ba5d4f487eaa03b74b89882ee8abf7f5c6216c..a739d6de66c9b68b44e2ea5c0ac2b56741d45007 100644 (file)
@@ -489,3 +489,263 @@ TEST_F(TestGroup, add_snapshotPP)
   ASSERT_EQ(0, rbd.group_snap_remove(ioctx, group_name, snap_name));
   ASSERT_EQ(0, rbd.group_remove(ioctx, group_name));
 }
+
+TEST_F(TestGroup, snap_get_info)
+{
+  REQUIRE_FORMAT_V2();
+
+  std::string pool_name2 = get_temp_pool_name("test-librbd-");
+  ASSERT_EQ(0, rados_pool_create(_cluster, pool_name2.c_str()));
+
+  rados_ioctx_t ioctx;
+  ASSERT_EQ(0, rados_ioctx_create(_cluster, _pool_name.c_str(), &ioctx));
+
+  rados_ioctx_t ioctx2;
+  ASSERT_EQ(0, rados_ioctx_create(_cluster, pool_name2.c_str(), &ioctx2));
+
+  const char *gp_name = "gp_snapgetinfo";
+  ASSERT_EQ(0, rbd_group_create(ioctx2, gp_name));
+  ASSERT_EQ(0, rbd_group_image_add(ioctx2, gp_name, ioctx,
+                                   m_image_name.c_str()));
+
+  const char *gp_snap_name = "snap_snapshot";
+  ASSERT_EQ(0, rbd_group_snap_create(ioctx2, gp_name, gp_snap_name));
+
+  rbd_group_snap_info2_t gp_snap_info;
+  ASSERT_EQ(-ENOENT, rbd_group_snap_get_info(ioctx2, "absent", gp_snap_name,
+                                             &gp_snap_info));
+  ASSERT_EQ(-ENOENT, rbd_group_snap_get_info(ioctx2, gp_name, "absent",
+                                             &gp_snap_info));
+
+  ASSERT_EQ(0, rbd_group_snap_get_info(ioctx2, gp_name, gp_snap_name,
+                                       &gp_snap_info));
+  ASSERT_STREQ(gp_snap_name, gp_snap_info.name);
+  ASSERT_EQ(RBD_GROUP_SNAP_STATE_COMPLETE, gp_snap_info.state);
+  ASSERT_EQ(1U, gp_snap_info.image_snaps_count);
+  ASSERT_EQ(m_image_name, gp_snap_info.image_snaps[0].image_name);
+  ASSERT_EQ(rados_ioctx_get_id(ioctx), gp_snap_info.image_snaps[0].pool_id);
+
+  rbd_group_snap_get_info_cleanup(&gp_snap_info);
+  ASSERT_EQ(0, rbd_group_snap_remove(ioctx2, gp_name, gp_snap_name));
+  ASSERT_EQ(0, rbd_group_remove(ioctx2, gp_name));
+  rados_ioctx_destroy(ioctx2);
+  rados_ioctx_destroy(ioctx);
+  ASSERT_EQ(0, rados_pool_delete(_cluster, pool_name2.c_str()));
+}
+
+TEST_F(TestGroup, snap_get_infoPP)
+{
+  REQUIRE_FORMAT_V2();
+
+  std::string pool_name2 = get_temp_pool_name("test-librbd-");
+  ASSERT_EQ(0, _rados.pool_create(pool_name2.c_str()));
+
+  librados::IoCtx ioctx2;
+  ASSERT_EQ(0, _rados.ioctx_create(pool_name2.c_str(), ioctx2));
+
+  const char *gp_name = "gp_snapgetinfoPP";
+  ASSERT_EQ(0, m_rbd.group_create(ioctx2, gp_name));
+  ASSERT_EQ(0, m_rbd.group_image_add(ioctx2, gp_name, m_ioctx,
+                                     m_image_name.c_str()));
+
+  const char *gp_snap_name = "snap_snapshot";
+  ASSERT_EQ(0, m_rbd.group_snap_create(ioctx2, gp_name, gp_snap_name));
+
+  librbd::group_snap_info2_t gp_snap_info;
+  ASSERT_EQ(-ENOENT, m_rbd.group_snap_get_info(ioctx2, "absent", gp_snap_name,
+                                               &gp_snap_info));
+  ASSERT_EQ(-ENOENT, m_rbd.group_snap_get_info(ioctx2, gp_name, "absent",
+                                               &gp_snap_info));
+
+  ASSERT_EQ(0, m_rbd.group_snap_get_info(ioctx2, gp_name, gp_snap_name,
+                                         &gp_snap_info));
+  ASSERT_EQ(gp_snap_name, gp_snap_info.name);
+  ASSERT_EQ(RBD_GROUP_SNAP_STATE_COMPLETE, gp_snap_info.state);
+  ASSERT_EQ(1U, gp_snap_info.image_snaps.size());
+  ASSERT_EQ(m_image_name, gp_snap_info.image_snaps[0].image_name);
+  ASSERT_EQ(m_ioctx.get_id(), gp_snap_info.image_snaps[0].pool_id);
+
+  ASSERT_EQ(0, m_rbd.group_snap_remove(ioctx2, gp_name, gp_snap_name));
+  ASSERT_EQ(0, m_rbd.group_remove(ioctx2, gp_name));
+  ASSERT_EQ(0, _rados.pool_delete(pool_name2.c_str()));
+}
+
+TEST_F(TestGroup, snap_list2)
+{
+  REQUIRE_FORMAT_V2();
+
+  std::string pool_name2 = get_temp_pool_name("test-librbd-");
+  ASSERT_EQ(0, rados_pool_create(_cluster, pool_name2.c_str()));
+
+  rados_ioctx_t ioctx;
+  ASSERT_EQ(0, rados_ioctx_create(_cluster, _pool_name.c_str(), &ioctx));
+
+  rados_ioctx_t ioctx2;
+  ASSERT_EQ(0, rados_ioctx_create(_cluster, pool_name2.c_str(), &ioctx2));
+
+  std::string image_name2 = get_temp_image_name();
+  uint64_t features;
+  int order = 0;
+  ASSERT_TRUE(get_features(&features));
+  ASSERT_EQ(0, rbd_create2(ioctx2, image_name2.c_str(), m_image_size, features,
+                           &order));
+
+  const char *gp_name = "gp_snaplist2";
+  ASSERT_EQ(0, rbd_group_create(ioctx, gp_name));
+
+  size_t num_snaps = 10U;
+  auto gp_snaps = static_cast<rbd_group_snap_info2_t*>(calloc(
+    num_snaps, sizeof(rbd_group_snap_info2_t)));
+  ASSERT_EQ(-ENOENT, rbd_group_snap_list2(ioctx, "absent", gp_snaps,
+                                          &num_snaps));
+  ASSERT_EQ(0, rbd_group_snap_list2(ioctx, gp_name, gp_snaps, &num_snaps));
+  ASSERT_EQ(0U, num_snaps);
+
+  const char* const gp_snap_names[] = {
+    "snap_snapshot0", "snap_snapshot1", "snap_snapshot2", "snap_snapshot3"};
+  ASSERT_EQ(0, rbd_group_snap_create(ioctx, gp_name, gp_snap_names[0]));
+
+  ASSERT_EQ(0, rbd_group_image_add(ioctx, gp_name, ioctx,
+                                   m_image_name.c_str()));
+  ASSERT_EQ(0, rbd_group_snap_create(ioctx, gp_name, gp_snap_names[1]));
+
+  ASSERT_EQ(0, rbd_group_image_add(ioctx, gp_name, ioctx2,
+                                   image_name2.c_str()));
+  ASSERT_EQ(0, rbd_group_snap_create(ioctx, gp_name, gp_snap_names[2]));
+
+  ASSERT_EQ(0, rbd_group_image_remove(ioctx, gp_name, ioctx,
+                                      m_image_name.c_str()));
+  ASSERT_EQ(0, rbd_group_snap_create(ioctx, gp_name, gp_snap_names[3]));
+
+  num_snaps = 3U;
+  ASSERT_EQ(-ERANGE, rbd_group_snap_list2(ioctx, gp_name, gp_snaps,
+                                          &num_snaps));
+  ASSERT_EQ(4U, num_snaps);
+  ASSERT_EQ(0, rbd_group_snap_list2(ioctx, gp_name, gp_snaps, &num_snaps));
+  ASSERT_EQ(4U, num_snaps);
+
+  for (int i = 0; i < 4; i++) {
+    ASSERT_EQ(RBD_GROUP_SNAP_STATE_COMPLETE, gp_snaps[i].state);
+    if (!strcmp(gp_snaps[i].name, gp_snap_names[0])) {
+      ASSERT_EQ(0U, gp_snaps[i].image_snaps_count);
+    } else if (!strcmp(gp_snaps[i].name, gp_snap_names[1])) {
+      ASSERT_EQ(1U, gp_snaps[i].image_snaps_count);
+      ASSERT_EQ(m_image_name, gp_snaps[i].image_snaps[0].image_name);
+      ASSERT_EQ(rados_ioctx_get_id(ioctx), gp_snaps[i].image_snaps[0].pool_id);
+    } else if (!strcmp(gp_snaps[i].name, gp_snap_names[2])) {
+      ASSERT_EQ(2U, gp_snaps[i].image_snaps_count);
+      for (int j = 0; j < 2; j++) {
+       if (m_image_name == gp_snaps[i].image_snaps[j].image_name) {
+         ASSERT_EQ(rados_ioctx_get_id(ioctx),
+                    gp_snaps[i].image_snaps[j].pool_id);
+       } else if (image_name2 == gp_snaps[i].image_snaps[j].image_name) {
+         ASSERT_EQ(rados_ioctx_get_id(ioctx2),
+                    gp_snaps[i].image_snaps[j].pool_id);
+       } else {
+          FAIL() << "Unexpected image in group snap: "
+                 << gp_snaps[i].image_snaps[j].image_name;
+       }
+      }
+    } else if (!strcmp(gp_snaps[i].name, gp_snap_names[3])) {
+      ASSERT_EQ(1U, gp_snaps[i].image_snaps_count);
+      ASSERT_EQ(image_name2, gp_snaps[i].image_snaps[0].image_name);
+      ASSERT_EQ(rados_ioctx_get_id(ioctx2),
+                gp_snaps[i].image_snaps[0].pool_id);
+    } else {
+      FAIL() << "Unexpected group snap: " << gp_snaps[i].name;
+    }
+  }
+
+  for (const auto& gp_snap_name : gp_snap_names) {
+    ASSERT_EQ(0, rbd_group_snap_remove(ioctx, gp_name, gp_snap_name));
+  }
+  rbd_group_snap_list2_cleanup(gp_snaps, num_snaps);
+  free(gp_snaps);
+  ASSERT_EQ(0, rbd_group_snap_list2(ioctx, gp_name, NULL, &num_snaps));
+  ASSERT_EQ(0U, num_snaps);
+  ASSERT_EQ(0, rbd_group_remove(ioctx, gp_name));
+  rados_ioctx_destroy(ioctx2);
+  rados_ioctx_destroy(ioctx);
+  ASSERT_EQ(0, rados_pool_delete(_cluster, pool_name2.c_str()));
+}
+
+TEST_F(TestGroup, snap_list2PP)
+{
+  REQUIRE_FORMAT_V2();
+
+  std::string pool_name2 = get_temp_pool_name("test-librbd-");
+  ASSERT_EQ(0, _rados.pool_create(pool_name2.c_str()));
+
+  librados::IoCtx ioctx2;
+  ASSERT_EQ(0, _rados.ioctx_create(pool_name2.c_str(), ioctx2));
+
+  std::string image_name2 = get_temp_image_name();
+  ASSERT_EQ(0, create_image_pp(m_rbd, ioctx2, image_name2.c_str(),
+                               m_image_size));
+
+  const char *gp_name = "gp_snaplist2PP";
+  ASSERT_EQ(0, m_rbd.group_create(m_ioctx, gp_name));
+
+  std::vector<librbd::group_snap_info2_t> gp_snaps;
+  ASSERT_EQ(-ENOENT, m_rbd.group_snap_list2(m_ioctx, "absent", &gp_snaps));
+  ASSERT_EQ(0, m_rbd.group_snap_list2(m_ioctx, gp_name, &gp_snaps));
+  ASSERT_EQ(0U, gp_snaps.size());
+
+  const char* const gp_snap_names[] = {
+    "snap_snapshot0", "snap_snapshot1", "snap_snapshot2", "snap_snapshot3"};
+
+  ASSERT_EQ(0, m_rbd.group_snap_create(m_ioctx, gp_name, gp_snap_names[0]));
+
+  ASSERT_EQ(0, m_rbd.group_image_add(m_ioctx, gp_name, m_ioctx,
+                                     m_image_name.c_str()));
+  ASSERT_EQ(0, m_rbd.group_snap_create(m_ioctx, gp_name, gp_snap_names[1]));
+
+  ASSERT_EQ(0, m_rbd.group_image_add(m_ioctx, gp_name, ioctx2,
+                                     image_name2.c_str()));
+  ASSERT_EQ(0, m_rbd.group_snap_create(m_ioctx, gp_name, gp_snap_names[2]));
+
+  ASSERT_EQ(0, m_rbd.group_image_remove(m_ioctx, gp_name,
+                                        m_ioctx, m_image_name.c_str()));
+  ASSERT_EQ(0, m_rbd.group_snap_create(m_ioctx, gp_name, gp_snap_names[3]));
+
+  ASSERT_EQ(0, m_rbd.group_snap_list2(m_ioctx, gp_name, &gp_snaps));
+  ASSERT_EQ(4U, gp_snaps.size());
+
+  for (const auto& gp_snap : gp_snaps) {
+    ASSERT_EQ(RBD_GROUP_SNAP_STATE_COMPLETE, gp_snap.state);
+    if (gp_snap.name == gp_snap_names[0]) {
+      ASSERT_EQ(0U, gp_snap.image_snaps.size());
+    } else if (gp_snap.name == gp_snap_names[1]) {
+      ASSERT_EQ(1U, gp_snap.image_snaps.size());
+      ASSERT_EQ(m_image_name, gp_snap.image_snaps[0].image_name);
+      ASSERT_EQ(m_ioctx.get_id(), gp_snap.image_snaps[0].pool_id);
+    } else if (gp_snap.name == gp_snap_names[2]) {
+      ASSERT_EQ(2U, gp_snap.image_snaps.size());
+      for (const auto& image_snap : gp_snap.image_snaps) {
+       if (image_snap.image_name == m_image_name) {
+         ASSERT_EQ(m_ioctx.get_id(), image_snap.pool_id);
+       } else if (image_snap.image_name == image_name2) {
+         ASSERT_EQ(ioctx2.get_id(), image_snap.pool_id);
+       } else {
+          FAIL() << "Unexpected image in group snap: "
+                 << image_snap.image_name;
+       }
+      }
+    } else if (gp_snap.name == gp_snap_names[3]) {
+      ASSERT_EQ(1U, gp_snap.image_snaps.size());
+      ASSERT_EQ(image_name2, gp_snap.image_snaps[0].image_name);
+      ASSERT_EQ(ioctx2.get_id(), gp_snap.image_snaps[0].pool_id);
+    } else {
+      FAIL() << "Unexpected group snap: " << gp_snap.name;
+    }
+  }
+
+  for (const auto& gp_snap_name : gp_snap_names) {
+    ASSERT_EQ(0, m_rbd.group_snap_remove(m_ioctx, gp_name, gp_snap_name));
+  }
+  std::vector<librbd::group_snap_info2_t> gp_snaps2;
+  ASSERT_EQ(0, m_rbd.group_snap_list2(m_ioctx, gp_name, &gp_snaps2));
+  ASSERT_EQ(0U, gp_snaps2.size());
+  ASSERT_EQ(0, m_rbd.group_remove(m_ioctx, gp_name));
+  ASSERT_EQ(0, _rados.pool_delete(pool_name2.c_str()));
+}
index f6a48975e2226b9bee8381545fe5c071f2ae4a4d..9b3a8ccaeb5924b09a60b2b175d7aae817448bea 100644 (file)
@@ -20,7 +20,7 @@ from rados import (Rados,
                    LIBRADOS_OP_FLAG_FADVISE_NOCACHE,
                    LIBRADOS_OP_FLAG_FADVISE_RANDOM)
 from rbd import (RBD, Group, Image, ImageNotFound, InvalidArgument, ImageExists,
-                 ImageBusy, ImageHasSnapshots, ReadOnlyImage,
+                 ImageBusy, ImageHasSnapshots, ReadOnlyImage, ObjectNotFound,
                  FunctionNotSupported, ArgumentOutOfRange,
                  ECANCELED, OperationCanceled,
                  DiskQuotaExceeded, ConnectionShutdown, PermissionError,
@@ -49,7 +49,7 @@ from rbd import (RBD, Group, Image, ImageNotFound, InvalidArgument, ImageExists,
                  RBD_SNAP_CREATE_IGNORE_QUIESCE_ERROR,
                  RBD_WRITE_ZEROES_FLAG_THICK_PROVISION,
                  RBD_ENCRYPTION_FORMAT_LUKS1, RBD_ENCRYPTION_FORMAT_LUKS2,
-                 RBD_ENCRYPTION_FORMAT_LUKS)
+                 RBD_ENCRYPTION_FORMAT_LUKS, RBD_GROUP_SNAP_STATE_COMPLETE)
 
 rados = None
 ioctx = None
@@ -2809,6 +2809,8 @@ def test_list_groups_after_removed():
     eq([], RBD().group_list(ioctx))
 
 class TestGroups(object):
+    img_snap_keys = ['image_name', 'pool_id', 'snap_id']
+    gp_snap_keys = ['id', 'image_snap_name', 'image_snaps', 'name', 'state']
 
     def setup_method(self, method):
         global snap_name
@@ -2882,6 +2884,35 @@ class TestGroups(object):
         with Image(ioctx, image_name) as image:
             eq(0, image.op_features() & RBD_OPERATION_FEATURE_GROUP)
 
+    def test_group_snap_get_info(self):
+        self.image_names.append(create_image())
+        self.image_names.sort()
+        self.group.add_image(ioctx, self.image_names[0])
+        self.group.add_image(ioctx, self.image_names[1])
+        pool_id = ioctx.get_pool_id()
+
+        assert_raises(ObjectNotFound, self.group.get_snap_info, "")
+
+        self.group.create_snap(snap_name)
+        snap_info_dict = self.group.get_snap_info(snap_name)
+        image_names = []
+        assert sorted(snap_info_dict.keys()) == self.gp_snap_keys
+        assert snap_info_dict['name'] == snap_name
+        assert snap_info_dict['state'] == RBD_GROUP_SNAP_STATE_COMPLETE
+        for image_snap in snap_info_dict['image_snaps']:
+            assert sorted(image_snap.keys()) == self.img_snap_keys
+            assert image_snap['pool_id'] == pool_id
+            image_names.append(image_snap['image_name'])
+            with Image(ioctx, image_snap['image_name']) as member_image:
+                snaps = [snap for snap in member_image.list_snaps()]
+            assert len(snaps) == 1
+            assert snaps[0]['name'] == snap_info_dict['image_snap_name']
+            assert snaps[0]['id'] == image_snap['snap_id']
+        assert sorted(image_names) == self.image_names
+
+        self.group.remove_snap(snap_name)
+        assert_raises(ObjectNotFound, self.group.get_snap_info, snap_name)
+
     def test_group_snap(self):
         global snap_name
         eq([], list(self.group.list_snaps()))
@@ -2919,18 +2950,30 @@ class TestGroups(object):
         eq([], list(self.group.list_snaps()))
 
     def test_group_snap_list_many(self):
+        self.image_names.append(create_image())
+        self.image_names.sort()
+        self.group.add_image(ioctx, self.image_names[0])
+        self.group.add_image(ioctx, self.image_names[1])
+
         global snap_name
-        eq([], list(self.group.list_snaps()))
+        assert list(self.group.list_snaps()) == []
         snap_names = []
         for x in range(0, 20):
             snap_names.append(snap_name)
             self.group.create_snap(snap_name)
             snap_name = get_temp_snap_name()
 
-        snap_names.sort()
-        answer = [snap['name'] for snap in self.group.list_snaps()]
-        answer.sort()
-        eq(snap_names, answer)
+        gp_snaps_list = self.group.list_snaps()
+        gp_snap_names = []
+        for gp_snap in gp_snaps_list:
+            assert sorted(gp_snap.keys()) == self.gp_snap_keys
+            gp_snap_names.append(gp_snap['name'])
+            image_names = []
+            for img_snap in gp_snap['image_snaps']:
+                assert sorted(img_snap.keys()) == self.img_snap_keys
+                image_names.append(img_snap['image_name'])
+            assert sorted(image_names) == self.image_names
+        assert sorted(gp_snap_names) == sorted(snap_names)
 
     def test_group_snap_namespace(self):
         global snap_name
index e8cc66ca6797e8d92c758c06425688b406390819..080d26e1757d09d50456d317e1983366786d5993 100644 (file)
@@ -85,6 +85,18 @@ void add_group_spec_options(po::options_description *pos,
   }
 }
 
+std::string get_group_snap_state_name(rbd_group_snap_state_t state)
+{
+  switch (state) {
+  case RBD_GROUP_SNAP_STATE_INCOMPLETE:
+    return "incomplete";
+  case RBD_GROUP_SNAP_STATE_COMPLETE:
+    return "complete";
+  default:
+    return "unknown (" + stringify(state) + ")";
+  }
+}
+
 int execute_create(const po::variables_map &vm,
                    const std::vector<std::string> &ceph_global_init_args) {
   size_t arg_index = 0;
@@ -705,14 +717,8 @@ int execute_group_snap_list(const po::variables_map &vm,
   }
 
   librbd::RBD rbd;
-  std::vector<librbd::group_snap_info_t> snaps;
-
-  r = rbd.group_snap_list(io_ctx, group_name.c_str(), &snaps,
-                          sizeof(librbd::group_snap_info_t));
-
-  if (r == -ENOENT) {
-    r = 0;
-  }
+  std::vector<librbd::group_snap_info2_t> snaps;
+  r = rbd.group_snap_list2(io_ctx, group_name.c_str(), &snaps);
   if (r < 0) {
     return r;
   }
@@ -721,29 +727,21 @@ int execute_group_snap_list(const po::variables_map &vm,
   if (f) {
     f->open_array_section("group_snaps");
   } else {
+    t.define_column("ID", TextTable::LEFT, TextTable::LEFT);
     t.define_column("NAME", TextTable::LEFT, TextTable::LEFT);
-    t.define_column("STATUS", TextTable::LEFT, TextTable::RIGHT);
+    t.define_column("STATE", TextTable::LEFT, TextTable::RIGHT);
   }
 
-  for (auto i : snaps) {
-    std::string snap_name = i.name;
-    int state = i.state;
-    std::string state_string;
-    if (RBD_GROUP_SNAP_STATE_INCOMPLETE == state) {
-      state_string = "incomplete";
-    } else {
-      state_string = "ok";
-    }
-    if (r < 0) {
-      return r;
-    }
+  for (const auto& snap : snaps) {
+    auto state_string = get_group_snap_state_name(snap.state);
     if (f) {
       f->open_object_section("group_snap");
-      f->dump_string("snapshot", snap_name);
+      f->dump_string("id", snap.id);
+      f->dump_string("snapshot", snap.name);
       f->dump_string("state", state_string);
       f->close_section();
     } else {
-      t << snap_name << state_string << TextTable::endrow;
+      t << snap.id << snap.name << state_string << TextTable::endrow;
     }
   }
 
@@ -756,6 +754,109 @@ int execute_group_snap_list(const po::variables_map &vm,
   return 0;
 }
 
+int execute_group_snap_info(const po::variables_map &vm,
+                            const std::vector<std::string> &ceph_global_args) {
+  size_t arg_index = 0;
+  std::string pool_name;
+  std::string namespace_name;
+  std::string group_name;
+  std::string group_snap_name;
+
+  int r = utils::get_pool_generic_snapshot_names(
+    vm, at::ARGUMENT_MODIFIER_NONE, &arg_index, at::POOL_NAME, &pool_name,
+    &namespace_name, GROUP_NAME, "group", &group_name, &group_snap_name, true,
+    utils::SNAPSHOT_PRESENCE_REQUIRED, utils::SPEC_VALIDATION_FULL);
+  if (r < 0) {
+    return r;
+  }
+
+  at::Format::Formatter formatter;
+  r = utils::get_formatter(vm, &formatter);
+  if (r < 0) {
+    return r;
+  }
+  Formatter *f = formatter.get();
+
+  librados::Rados rados;
+  librados::IoCtx io_ctx;
+  r = utils::init(pool_name, namespace_name, &rados, &io_ctx);
+  if (r < 0) {
+    return r;
+  }
+
+  librbd::RBD rbd;
+  librbd::group_snap_info2_t group_snap;
+  r = rbd.group_snap_get_info(io_ctx, group_name.c_str(),
+                              group_snap_name.c_str(), &group_snap);
+  if (r < 0) {
+    std::cerr << "rbd: failed to show group snapshot: "
+              << cpp_strerror(r) << std::endl;
+    return r;
+  }
+
+  auto state_string = get_group_snap_state_name(group_snap.state);
+  if (f) {
+    f->open_object_section("group_snapshot");
+    f->dump_string("id", group_snap.id);
+    f->dump_string("name", group_snap.name);
+    f->dump_string("state", state_string);
+    f->dump_string("image_snap_name", group_snap.image_snap_name);
+    f->open_array_section("images");
+  } else {
+    std::cout << "rbd group snapshot '" << group_snap.name << "':\n"
+              << "\tid: " << group_snap.id << std::endl
+              << "\tstate: " << state_string << std::endl
+              << "\timage snap: " << group_snap.image_snap_name << std::endl
+              << "\timages:" << std::endl;
+  }
+
+  std::sort(group_snap.image_snaps.begin(), group_snap.image_snaps.end(),
+    [](const librbd::group_image_snap_info_t& lhs,
+       const librbd::group_image_snap_info_t& rhs) {
+      if (lhs.pool_id != rhs.pool_id) {
+        return lhs.pool_id < rhs.pool_id;
+      }
+      return lhs.image_name < rhs.image_name;
+    }
+  );
+
+  for (const auto& image_snap : group_snap.image_snaps) {
+    std::string pool_name;
+    r = rados.pool_reverse_lookup(image_snap.pool_id, &pool_name);
+    if (r == -ENOENT) {
+      pool_name = "<missing image pool " + stringify(image_snap.pool_id) + ">";
+    } else if (r < 0) {
+      std::cerr << "rbd: error looking up pool name for pool_id="
+                << image_snap.pool_id << ": " << cpp_strerror(r) << std::endl;
+      return r;
+    }
+
+    if (f) {
+      f->open_object_section("image");
+      f->dump_string("pool_name", pool_name);
+      f->dump_string("namespace", io_ctx.get_namespace());
+      f->dump_string("image_name", image_snap.image_name);
+      f->dump_int("snap_id", image_snap.snap_id);
+      f->close_section();
+    } else {
+      std::cout << "\t\t" << pool_name << "/";
+      if (!io_ctx.get_namespace().empty()) {
+        std::cout << io_ctx.get_namespace() << "/";
+      }
+      std::cout << image_snap.image_name << " (snap id: " << image_snap.snap_id
+                << ")" << std::endl;
+    }
+  }
+
+  if (f) {
+    f->close_section();
+    f->close_section();
+    f->flush(std::cout);
+  }
+
+  return 0;
+}
+
 int execute_group_snap_rollback(const po::variables_map &vm,
                                 const std::vector<std::string> &global_args) {
   size_t arg_index = 0;
@@ -917,6 +1018,13 @@ void get_group_snap_list_arguments(po::options_description *positional,
                          false);
 }
 
+void get_group_snap_info_arguments(po::options_description *positional,
+                                   po::options_description *options) {
+  add_group_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE,
+                         true);
+  at::add_format_options(options);
+}
+
 void get_group_snap_rollback_arguments(po::options_description *positional,
                                        po::options_description *options) {
   at::add_no_progress_option(options);
@@ -964,6 +1072,10 @@ Shell::Action action_group_snap_list(
   {"group", "snap", "list"}, {"group", "snap", "ls"},
   "List snapshots of a group.",
   "", &get_group_snap_list_arguments, &execute_group_snap_list);
+Shell::Action action_group_snap_info(
+  {"group", "snap", "info"}, {},
+  "Show information about a group snapshot.",
+  "", &get_group_snap_info_arguments, &execute_group_snap_info);
 Shell::Action action_group_snap_rollback(
   {"group", "snap", "rollback"}, {},
   "Rollback group to snapshot.",