From e5ccce14c4b0094659d9ed6ec7483f5f560c92e8 Mon Sep 17 00:00:00 2001 From: Ramana Raja Date: Tue, 18 Jun 2024 17:32:24 -0400 Subject: [PATCH] rbd: add group snap info command ... 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 --- PendingReleaseNotes | 6 + doc/man/8/rbd.rst | 3 + qa/workunits/rbd/rbd_groups.sh | 77 +++++++--- src/include/rbd/librbd.h | 28 ++++ src/include/rbd/librbd.hpp | 20 +++ src/librbd/api/Group.cc | 136 ++++++++++++++--- src/librbd/api/Group.h | 5 +- src/librbd/librbd.cc | 110 +++++++++++++- src/pybind/rbd/c_rbd.pxd | 29 +++- src/pybind/rbd/mock_rbd.pxi | 30 +++- src/pybind/rbd/rbd.pyx | 138 ++++++++++++++--- src/test/cli/rbd/help.t | 22 +++ src/test/librbd/test_Groups.cc | 260 +++++++++++++++++++++++++++++++++ src/test/pybind/test_rbd.py | 57 +++++++- src/tools/rbd/action/Group.cc | 158 +++++++++++++++++--- 15 files changed, 959 insertions(+), 120 deletions(-) diff --git a/PendingReleaseNotes b/PendingReleaseNotes index 25fcbb70db0..88e0c12827d 100644 --- a/PendingReleaseNotes +++ b/PendingReleaseNotes @@ -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 diff --git a/doc/man/8/rbd.rst b/doc/man/8/rbd.rst index 881e891d76b..df41034e596 100644 --- a/doc/man/8/rbd.rst +++ b/doc/man/8/rbd.rst @@ -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. diff --git a/qa/workunits/rbd/rbd_groups.sh b/qa/workunits/rbd/rbd_groups.sh index a3261848441..450095dabfc 100755 --- a/qa/workunits/rbd/rbd_groups.sh +++ b/qa/workunits/rbd/rbd_groups.sh @@ -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 diff --git a/src/include/rbd/librbd.h b/src/include/rbd/librbd.h index 267ed289bf7..f9af9262b2a 100644 --- a/src/include/rbd/librbd.h +++ b/src/include/rbd/librbd.h @@ -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); diff --git a/src/include/rbd/librbd.hpp b/src/include/rbd/librbd.hpp index c02d179450c..50a6c623d3a 100644 --- a/src/include/rbd/librbd.hpp +++ b/src/include/rbd/librbd.hpp @@ -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 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 *snaps, size_t group_snap_info_size); + int group_snap_list2(IoCtx& group_ioctx, const char *group_name, + std::vector *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, diff --git a/src/librbd/api/Group.cc b/src/librbd/api/Group.cc index 58c7499e5c9..f3891dcf163 100644 --- a/src/librbd/api/Group.cc +++ b/src/librbd/api/Group.cc @@ -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_snaps) { CephContext *cct = (CephContext *)group_ioctx.cct(); - string group_id; - vector 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 snaps_page; @@ -476,6 +466,48 @@ int notify_quiesce(std::vector &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 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(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 @@ -560,7 +592,7 @@ int Group::remove(librados::IoCtx& io_ctx, const char *group_name) string group_header_oid = util::group_header_name(group_id); std::vector 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::snap_remove(librados::IoCtx& group_ioctx, const char *group_name, } std::vector 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::snap_rename(librados::IoCtx& group_ioctx, const char *group_name, } std::vector 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::snap_rename(librados::IoCtx& group_ioctx, const char *group_name, template int Group::snap_list(librados::IoCtx& group_ioctx, const char *group_name, - std::vector *snaps) + std::vector *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_group_snaps; + r = group_snap_list(group_ioctx, group_id, &cls_group_snaps); + if (r < 0) { + return r; + } + + std::vector 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 +int Group::snap_get_info(librados::IoCtx& group_ioctx, + const char *group_name, const char *snap_name, + group_snap_info2_t* group_snap) { - std::vector 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(snap.state)}); + std::vector 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::snap_rollback(librados::IoCtx& group_ioctx, } std::vector snaps; - r = group_snap_list(group_ioctx, group_name, &snaps); + r = group_snap_list(group_ioctx, group_id, &snaps); if (r < 0) { return r; } diff --git a/src/librbd/api/Group.h b/src/librbd/api/Group.h index 98833eb506f..2a7574612ec 100644 --- a/src/librbd/api/Group.h +++ b/src/librbd/api/Group.h @@ -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 *snaps); + std::vector *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); diff --git a/src/librbd/librbd.cc b/src/librbd/librbd.cc index df45e370af4..ed8ec9e9130 100644 --- a/src/librbd/librbd.cc +++ b/src/librbd/librbd.cc @@ -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(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 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 *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 cpp_snaps; + std::vector 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 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) diff --git a/src/pybind/rbd/c_rbd.pxd b/src/pybind/rbd/c_rbd.pxd index c39e945b465..3a960a2a885 100644 --- a/src/pybind/rbd/c_rbd.pxd +++ b/src/pybind/rbd/c_rbd.pxd @@ -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) diff --git a/src/pybind/rbd/mock_rbd.pxi b/src/pybind/rbd/mock_rbd.pxi index bc6132ce6b9..40280dc9c0d 100644 --- a/src/pybind/rbd/mock_rbd.pxi +++ b/src/pybind/rbd/mock_rbd.pxi @@ -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): diff --git a/src/pybind/rbd/rbd.pyx b/src/pybind/rbd/rbd.pyx index d023e231bcf..5290e48832d 100644 --- a/src/pybind/rbd/rbd.pyx +++ b/src/pybind/rbd/rbd.pyx @@ -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 = realloc_chk(self.snaps, - self.num_snaps * - sizeof(rbd_group_snap_info_t)) + self.group_snaps = 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) diff --git a/src/test/cli/rbd/help.t b/src/test/cli/rbd/help.t index c981996ea9a..bee99c51dd1 100644 --- a/src/test/cli/rbd/help.t +++ b/src/test/cli/rbd/help.t @@ -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. @@ -1070,6 +1071,27 @@ --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 ] [--namespace ] + [--group ] [--snap ] + [--format ] [--pretty-format] + + + Show information about a group snapshot. + + Positional arguments + group specification + (example: + [/[/]]@) + + 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 ] [--pretty-format] [--pool ] [--namespace ] diff --git a/src/test/librbd/test_Groups.cc b/src/test/librbd/test_Groups.cc index 16ba5d4f487..a739d6de66c 100644 --- a/src/test/librbd/test_Groups.cc +++ b/src/test/librbd/test_Groups.cc @@ -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(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 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 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())); +} diff --git a/src/test/pybind/test_rbd.py b/src/test/pybind/test_rbd.py index f6a48975e22..9b3a8ccaeb5 100644 --- a/src/test/pybind/test_rbd.py +++ b/src/test/pybind/test_rbd.py @@ -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 diff --git a/src/tools/rbd/action/Group.cc b/src/tools/rbd/action/Group.cc index e8cc66ca679..080d26e1757 100644 --- a/src/tools/rbd/action/Group.cc +++ b/src/tools/rbd/action/Group.cc @@ -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 &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 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 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 &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 = ""; + } 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 &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.", -- 2.39.5