From f97114ad98cf5253c7aaa4281e6a453c84bc1272 Mon Sep 17 00:00:00 2001 From: Ramana Raja Date: Wed, 23 Oct 2024 13:20:37 -0400 Subject: [PATCH] tools/rbd: add CLI to call into mirror group snapshot scheduler Signed-off-by: Ramana Raja --- src/test/cli/rbd/help.t | 78 +++++ src/tools/rbd/ArgumentTypes.cc | 18 + src/tools/rbd/ArgumentTypes.h | 5 + src/tools/rbd/CMakeLists.txt | 1 + src/tools/rbd/Schedule.cc | 66 +++- src/tools/rbd/Schedule.h | 8 +- src/tools/rbd/action/Group.cc | 24 +- .../rbd/action/MirrorGroupSnapshotSchedule.cc | 325 ++++++++++++++++++ 8 files changed, 484 insertions(+), 41 deletions(-) create mode 100644 src/tools/rbd/action/MirrorGroupSnapshotSchedule.cc diff --git a/src/test/cli/rbd/help.t b/src/test/cli/rbd/help.t index b6168c553a31f..4ab1c4abb718e 100644 --- a/src/test/cli/rbd/help.t +++ b/src/test/cli/rbd/help.t @@ -96,6 +96,15 @@ mirror group resync Force resync to primary group for RBD mirroring. mirror group snapshot Create RBD mirroring group snapshot. + mirror group snapshot schedule add + Add mirror group snapshot schedule. + mirror group snapshot schedule list (... ls) + List mirror group snapshot schedule. + mirror group snapshot schedule remove (... rm) + Remove mirror group snapshot schedule. + mirror group snapshot schedule status + Show mirror group snapshot schedule + status. mirror group status Show RBD mirroring status for an group. mirror image demote Demote an image to non-primary for RBD mirroring. @@ -1792,6 +1801,75 @@ --skip-quiesce do not run quiesce hooks --ignore-quiesce-error ignore quiesce hook error + rbd help mirror group snapshot schedule add + usage: rbd mirror group snapshot schedule add + [--pool ] + [--namespace ] + [--group ] + [] + + Add mirror group snapshot schedule. + + Positional arguments + schedule interval + schedule start time + + Optional arguments + -p [ --pool ] arg pool name + --namespace arg namespace name + --group arg group name + + rbd help mirror group snapshot schedule list + usage: rbd mirror group snapshot schedule list + [--pool ] + [--namespace ] + [--group ] [--recursive] + [--format ] [--pretty-format] + + List mirror group snapshot schedule. + + Optional arguments + -p [ --pool ] arg pool name + --namespace arg namespace name + --group arg group name + -R [ --recursive ] list all schedules + --format arg output format (plain, json, or xml) [default: plain] + --pretty-format pretty formatting (json and xml) + + rbd help mirror group snapshot schedule remove + usage: rbd mirror group snapshot schedule remove + [--pool ] + [--namespace ] + [--group ] + [] [] + + Remove mirror group snapshot schedule. + + Positional arguments + schedule interval + schedule start time + + Optional arguments + -p [ --pool ] arg pool name + --namespace arg namespace name + --group arg group name + + rbd help mirror group snapshot schedule status + usage: rbd mirror group snapshot schedule status + [--pool ] + [--namespace ] + [--group ] [--format ] + [--pretty-format] + + Show mirror group snapshot schedule status. + + Optional arguments + -p [ --pool ] arg pool name + --namespace arg namespace name + --group arg group name + --format arg output format (plain, json, or xml) [default: plain] + --pretty-format pretty formatting (json and xml) + rbd help mirror group status usage: rbd mirror group status [--pool ] [--namespace ] [--group ] [--format ] diff --git a/src/tools/rbd/ArgumentTypes.cc b/src/tools/rbd/ArgumentTypes.cc index b479f96158845..8688e426b58c8 100644 --- a/src/tools/rbd/ArgumentTypes.cc +++ b/src/tools/rbd/ArgumentTypes.cc @@ -130,6 +130,24 @@ void add_image_option(po::options_description *opt, (name.c_str(), po::value(), description.c_str()); } +void add_group_option(po::options_description *opt, + ArgumentModifier modifier) { + std::string name = GROUP_NAME; + std::string description = get_description_prefix(modifier) + "group name"; + switch (modifier) { + case ARGUMENT_MODIFIER_NONE: + case ARGUMENT_MODIFIER_SOURCE: + break; + case ARGUMENT_MODIFIER_DEST: + name = DEST_GROUP_NAME; + break; + } + + // TODO add validator + opt->add_options() + (name.c_str(), po::value(), description.c_str()); +} + void add_image_id_option(po::options_description *opt, const std::string &desc_suffix) { std::string name = IMAGE_ID; diff --git a/src/tools/rbd/ArgumentTypes.h b/src/tools/rbd/ArgumentTypes.h index cc7c481363693..21869304a4ee7 100644 --- a/src/tools/rbd/ArgumentTypes.h +++ b/src/tools/rbd/ArgumentTypes.h @@ -55,6 +55,8 @@ static const std::string DEST_SNAPSHOT_NAME("dest-snap"); static const std::string PATH("path"); static const std::string FROM_SNAPSHOT_NAME("from-snap"); static const std::string WHOLE_OBJECT("whole-object"); +static const std::string GROUP_NAME("group"); +static const std::string DEST_GROUP_NAME("dest-group"); // encryption arguments static const std::string ENCRYPTION_FORMAT("encryption-format"); @@ -149,6 +151,9 @@ void add_pool_option(boost::program_options::options_description *opt, void add_namespace_option(boost::program_options::options_description *opt, ArgumentModifier modifier); +void add_group_option(boost::program_options::options_description *opt, + ArgumentModifier modifier); + void add_image_option(boost::program_options::options_description *opt, ArgumentModifier modifier, const std::string &desc_suffix = ""); diff --git a/src/tools/rbd/CMakeLists.txt b/src/tools/rbd/CMakeLists.txt index 6e4d31bef0d70..43f010e591a46 100644 --- a/src/tools/rbd/CMakeLists.txt +++ b/src/tools/rbd/CMakeLists.txt @@ -39,6 +39,7 @@ set(rbd_srcs action/MirrorImage.cc action/MirrorPool.cc action/MirrorSnapshotSchedule.cc + action/MirrorGroupSnapshotSchedule.cc action/Namespace.cc action/Nbd.cc action/ObjectMap.cc diff --git a/src/tools/rbd/Schedule.cc b/src/tools/rbd/Schedule.cc index 15dda3aee7ef9..3fb8321f54dcf 100644 --- a/src/tools/rbd/Schedule.cc +++ b/src/tools/rbd/Schedule.cc @@ -19,8 +19,9 @@ namespace po = boost::program_options; namespace { int parse_schedule_name(const std::string &name, bool allow_images, - std::string *pool_name, std::string *namespace_name, - std::string *image_name) { + bool allow_groups, std::string *pool_name, + std::string *namespace_name, std::string *image_name, + std::string *group_name) { // parse names like: // '', 'rbd/', 'rbd/ns/', 'rbd/image', 'rbd/ns/image' std::regex pattern("^(?:([^/]+)/(?:(?:([^/]+)/|)(?:([^/@]+))?)?)?$"); @@ -44,12 +45,18 @@ int parse_schedule_name(const std::string &name, bool allow_images, } if (match[3].matched) { - if (!allow_images) { + if (!allow_images and !allow_groups) { return -EINVAL; } - *image_name = match[3]; + if (allow_images) { + *image_name = match[3]; + } else if (allow_groups) { + *group_name = match[3]; + } + } else { *image_name = "-"; + *group_name = "-"; } return 0; @@ -58,26 +65,40 @@ int parse_schedule_name(const std::string &name, bool allow_images, } // anonymous namespace void add_level_spec_options(po::options_description *options, - bool allow_image) { + bool allow_image, bool allow_group) { at::add_pool_option(options, at::ARGUMENT_MODIFIER_NONE); at::add_namespace_option(options, at::ARGUMENT_MODIFIER_NONE); if (allow_image) { at::add_image_option(options, at::ARGUMENT_MODIFIER_NONE); } + if (allow_group) { + at::add_group_option(options, at::ARGUMENT_MODIFIER_NONE); + } } int get_level_spec_args(const po::variables_map &vm, std::map *args) { - if (vm.count(at::IMAGE_NAME)) { + if (vm.count(at::IMAGE_NAME) or vm.count(at::GROUP_NAME)) { std::string pool_name; std::string namespace_name; - std::string image_name; + std::string image_or_group_name; - int r = utils::extract_spec(vm[at::IMAGE_NAME].as(), - &pool_name, &namespace_name, &image_name, - nullptr, utils::SPEC_VALIDATION_FULL); - if (r < 0) { - return r; + if (vm.count(at::IMAGE_NAME)) { + int r = utils::extract_spec(vm[at::IMAGE_NAME].as(), + &pool_name, &namespace_name, &image_or_group_name, + nullptr, utils::SPEC_VALIDATION_FULL); + if (r < 0) { + return r; + } + } + + if (vm.count(at::GROUP_NAME)) { + int r = utils::extract_spec(vm[at::GROUP_NAME].as(), + &pool_name, &namespace_name, &image_or_group_name, + nullptr, utils::SPEC_VALIDATION_FULL); + if (r < 0) { + return r; + } } if (!pool_name.empty()) { @@ -102,10 +123,10 @@ int get_level_spec_args(const po::variables_map &vm, } if (namespace_name.empty()) { - (*args)["level_spec"] = pool_name + "/" + image_name; + (*args)["level_spec"] = pool_name + "/" + image_or_group_name; } else { (*args)["level_spec"] = pool_name + "/" + namespace_name + "/" + - image_name; + image_or_group_name; } return 0; } @@ -310,9 +331,10 @@ void ScheduleList::dump(ceph::Formatter *f) { std::string pool_name; std::string namespace_name; std::string image_name; + std::string group_name; - int r = parse_schedule_name(name, allow_images, &pool_name, &namespace_name, - &image_name); + int r = parse_schedule_name(name, allow_images, allow_groups, &pool_name, + &namespace_name, &image_name, &group_name); if (r < 0) { continue; } @@ -322,6 +344,8 @@ void ScheduleList::dump(ceph::Formatter *f) { f->dump_string("namespace", namespace_name); if (allow_images) { f->dump_string("image", image_name); + } else if (allow_groups) { + f->dump_string("group", group_name); } s.dump(f); f->close_section(); @@ -335,6 +359,8 @@ std::ostream& operator<<(std::ostream& os, ScheduleList &l) { tbl.define_column("NAMESPACE", TextTable::LEFT, TextTable::LEFT); if (l.allow_images) { tbl.define_column("IMAGE", TextTable::LEFT, TextTable::LEFT); + } else if (l.allow_groups) { + tbl.define_column("GROUP", TextTable::LEFT, TextTable::LEFT); } tbl.define_column("SCHEDULE", TextTable::LEFT, TextTable::LEFT); @@ -342,9 +368,11 @@ std::ostream& operator<<(std::ostream& os, ScheduleList &l) { std::string pool_name; std::string namespace_name; std::string image_name; + std::string group_name; - int r = parse_schedule_name(name, l.allow_images, &pool_name, - &namespace_name, &image_name); + int r = parse_schedule_name(name, l.allow_images, l.allow_groups, + &pool_name, &namespace_name, &image_name, + &group_name); if (r < 0) { continue; } @@ -355,6 +383,8 @@ std::ostream& operator<<(std::ostream& os, ScheduleList &l) { tbl << pool_name << namespace_name; if (l.allow_images) { tbl << image_name; + } else if (l.allow_groups) { + tbl << group_name; } tbl << ss.str() << TextTable::endrow; } diff --git a/src/tools/rbd/Schedule.h b/src/tools/rbd/Schedule.h index bf0964bb1e51b..0b71f551e9705 100644 --- a/src/tools/rbd/Schedule.h +++ b/src/tools/rbd/Schedule.h @@ -17,7 +17,8 @@ namespace ceph { class Formatter; } namespace rbd { void add_level_spec_options( - boost::program_options::options_description *options, bool allow_image=true); + boost::program_options::options_description *options, bool allow_image=true, + bool allow_group=false); int get_level_spec_args(const boost::program_options::variables_map &vm, std::map *args); void normalize_level_spec_args(std::map *args); @@ -46,7 +47,9 @@ std::ostream& operator<<(std::ostream& os, Schedule &s); class ScheduleList { public: - ScheduleList(bool allow_images=true) : allow_images(allow_images) { + ScheduleList(bool allow_images=true, bool allow_groups=false) : + allow_images(allow_images), allow_groups(allow_groups) { + //TODO: throw exception when both allow_images and allow_groups are True } int parse(const std::string &list); @@ -57,6 +60,7 @@ public: private: bool allow_images; + bool allow_groups; std::map schedules; }; diff --git a/src/tools/rbd/action/Group.cc b/src/tools/rbd/action/Group.cc index de38d3faf954f..057c5492ce7ed 100644 --- a/src/tools/rbd/action/Group.cc +++ b/src/tools/rbd/action/Group.cc @@ -31,24 +31,6 @@ static const std::string IMAGE_POOL_NAME("image-" + at::POOL_NAME); static const std::string GROUP_NAMESPACE_NAME("group-" + at::NAMESPACE_NAME); static const std::string IMAGE_NAMESPACE_NAME("image-" + at::NAMESPACE_NAME); -void add_group_option(po::options_description *opt, - at::ArgumentModifier modifier) { - std::string name = GROUP_NAME; - std::string description = at::get_description_prefix(modifier) + "group name"; - switch (modifier) { - case at::ARGUMENT_MODIFIER_NONE: - case at::ARGUMENT_MODIFIER_SOURCE: - break; - case at::ARGUMENT_MODIFIER_DEST: - name = DEST_GROUP_NAME; - break; - } - - // TODO add validator - opt->add_options() - (name.c_str(), po::value(), description.c_str()); -} - void add_prefixed_pool_option(po::options_description *opt, const std::string &prefix) { std::string name = prefix + "-" + at::POOL_NAME; @@ -73,7 +55,7 @@ void add_group_spec_options(po::options_description *pos, bool snap) { at::add_pool_option(opt, modifier); at::add_namespace_option(opt, modifier); - add_group_option(opt, modifier); + at::add_group_option(opt, modifier); if (!snap) { pos->add_options() ((get_name_prefix(modifier) + GROUP_SPEC).c_str(), @@ -966,7 +948,7 @@ void get_add_arguments(po::options_description *positional, add_prefixed_pool_option(options, "group"); add_prefixed_namespace_option(options, "group"); - add_group_option(options, at::ARGUMENT_MODIFIER_NONE); + at::add_group_option(options, at::ARGUMENT_MODIFIER_NONE); positional->add_options() (at::IMAGE_SPEC.c_str(), @@ -988,7 +970,7 @@ void get_remove_image_arguments(po::options_description *positional, add_prefixed_pool_option(options, "group"); add_prefixed_namespace_option(options, "group"); - add_group_option(options, at::ARGUMENT_MODIFIER_NONE); + at::add_group_option(options, at::ARGUMENT_MODIFIER_NONE); positional->add_options() (at::IMAGE_SPEC.c_str(), diff --git a/src/tools/rbd/action/MirrorGroupSnapshotSchedule.cc b/src/tools/rbd/action/MirrorGroupSnapshotSchedule.cc new file mode 100644 index 0000000000000..4016112b260b0 --- /dev/null +++ b/src/tools/rbd/action/MirrorGroupSnapshotSchedule.cc @@ -0,0 +1,325 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "tools/rbd/ArgumentTypes.h" +#include "tools/rbd/Schedule.h" +#include "tools/rbd/Shell.h" +#include "tools/rbd/Utils.h" +#include "common/ceph_context.h" +#include "common/ceph_json.h" +#include "common/errno.h" +#include "common/Formatter.h" +#include "common/TextTable.h" +#include "global/global_context.h" +#include "include/stringify.h" + +#include +#include +#include +#include +#include + +#include "json_spirit/json_spirit.h" + +namespace rbd { +namespace action { +namespace mirror_group_snapshot_schedule { + +namespace at = argument_types; +namespace po = boost::program_options; + +namespace { + +class ScheduleStatus { +public: + ScheduleStatus() { + } + + int parse(const std::string &status) { + json_spirit::mValue json_root; + if(!json_spirit::read(status, json_root)) { + std::cerr << "rbd: invalid schedule status JSON received" << std::endl; + return -EBADMSG; + } + + try { + auto &s = json_root.get_obj(); + + if (s["scheduled_groups"].type() != json_spirit::array_type) { + std::cerr << "rbd: unexpected schedule JSON received: " + << "scheduled_groups is not array" << std::endl; + return -EBADMSG; + } + + for (auto &item_val : s["scheduled_groups"].get_array()) { + if (item_val.type() != json_spirit::obj_type) { + std::cerr << "rbd: unexpected schedule status JSON received: " + << "schedule item is not object" << std::endl; + return -EBADMSG; + } + + auto &item = item_val.get_obj(); + + if (item["schedule_time"].type() != json_spirit::str_type) { + std::cerr << "rbd: unexpected schedule JSON received: " + << "schedule_time is not string" << std::endl; + return -EBADMSG; + } + auto schedule_time = item["schedule_time"].get_str(); + + if (item["group"].type() != json_spirit::str_type) { + std::cerr << "rbd: unexpected schedule JSON received: " + << "group is not string" << std::endl; + return -EBADMSG; + } + auto group = item["group"].get_str(); + + scheduled_groups.push_back({schedule_time, group}); + } + + } catch (std::runtime_error &) { + std::cerr << "rbd: invalid schedule JSON received" << std::endl; + return -EBADMSG; + } + + return 0; + } + + void dump(Formatter *f) { + f->open_array_section("scheduled_groups"); + for (auto &group : scheduled_groups) { + f->open_object_section("group"); + f->dump_string("schedule_time", group.first); + f->dump_string("group", group.second); + f->close_section(); // group + } + f->close_section(); // scheduled_groups + } + + friend std::ostream& operator<<(std::ostream& os, ScheduleStatus &d); + +private: + + std::list> scheduled_groups; +}; + +std::ostream& operator<<(std::ostream& os, ScheduleStatus &s) { + TextTable tbl; + tbl.define_column("SCHEDULE TIME", TextTable::LEFT, TextTable::LEFT); + tbl.define_column("GROUP", TextTable::LEFT, TextTable::LEFT); + + for (auto &[schedule_time, group] : s.scheduled_groups) { + tbl << schedule_time << group << TextTable::endrow; + } + + os << tbl; + return os; +} + +} // anonymous namespace + +void get_arguments_add(po::options_description *positional, + po::options_description *options) { + add_level_spec_options(options, false, true); + add_schedule_options(positional, true); +} + +int execute_add(const po::variables_map &vm, + const std::vector &ceph_global_init_args) { + std::map args; + + int r = get_level_spec_args(vm, &args); + if (r < 0) { + return r; + } + r = get_schedule_args(vm, true, &args); + if (r < 0) { + return r; + } + + librados::Rados rados; + r = utils::init_rados(&rados); + if (r < 0) { + return r; + } + + normalize_level_spec_args(&args); + r = utils::mgr_command(rados, "rbd mirror group snapshot schedule add", args, + &std::cout, &std::cerr); + if (r < 0) { + return r; + } + + return 0; +} + +void get_arguments_remove(po::options_description *positional, + po::options_description *options) { + add_level_spec_options(options, false, true); + add_schedule_options(positional, false); +} + +int execute_remove(const po::variables_map &vm, + const std::vector &ceph_global_init_args) { + std::map args; + + int r = get_level_spec_args(vm, &args); + if (r < 0) { + return r; + } + r = get_schedule_args(vm, false, &args); + if (r < 0) { + return r; + } + + librados::Rados rados; + r = utils::init_rados(&rados); + if (r < 0) { + return r; + } + + normalize_level_spec_args(&args); + r = utils::mgr_command(rados, "rbd mirror group snapshot schedule remove", args, + &std::cout, &std::cerr); + if (r < 0) { + return r; + } + + return 0; +} + +void get_arguments_list(po::options_description *positional, + po::options_description *options) { + add_level_spec_options(options, false, true); + options->add_options() + ("recursive,R", po::bool_switch(), "list all schedules"); + at::add_format_options(options); +} + +int execute_list(const po::variables_map &vm, + const std::vector &ceph_global_init_args) { + std::map args; + + int r = get_level_spec_args(vm, &args); + if (r < 0) { + return r; + } + + at::Format::Formatter formatter; + r = utils::get_formatter(vm, &formatter); + if (r < 0) { + return r; + } + + librados::Rados rados; + r = utils::init_rados(&rados); + if (r < 0) { + return r; + } + + normalize_level_spec_args(&args); + std::stringstream out; + r = utils::mgr_command(rados, "rbd mirror group snapshot schedule list", args, &out, + &std::cerr); + if (r < 0) { + return r; + } + + ScheduleList schedule_list(false, true); + r = schedule_list.parse(out.str()); + if (r < 0) { + return r; + } + + if (vm["recursive"].as()) { + if (formatter.get()) { + schedule_list.dump(formatter.get()); + formatter->flush(std::cout); + } else { + std::cout << schedule_list; + } + } else { + auto schedule = schedule_list.find(args["level_spec"]); + if (schedule == nullptr) { + return -ENOENT; + } + + if (formatter.get()) { + schedule->dump(formatter.get()); + formatter->flush(std::cout); + } else { + std::cout << *schedule << std::endl; + } + } + + return 0; +} + +void get_arguments_status(po::options_description *positional, + po::options_description *options) { + add_level_spec_options(options, false, true); + at::add_format_options(options); +} + +int execute_status(const po::variables_map &vm, + const std::vector &ceph_global_init_args) { + std::map args; + + int r = get_level_spec_args(vm, &args); + if (r < 0) { + return r; + } + + at::Format::Formatter formatter; + r = utils::get_formatter(vm, &formatter); + if (r < 0) { + return r; + } + + librados::Rados rados; + r = utils::init_rados(&rados); + if (r < 0) { + return r; + } + + normalize_level_spec_args(&args); + std::stringstream out; + r = utils::mgr_command(rados, "rbd mirror group snapshot schedule status", + args, &out, &std::cerr); + ScheduleStatus schedule_status; + r = schedule_status.parse(out.str()); + if (r < 0) { + return r; + } + + if (formatter.get()) { + schedule_status.dump(formatter.get()); + formatter->flush(std::cout); + } else { + std::cout << schedule_status; + } + + return 0; +} + +Shell::Action add_action( + {"mirror", "group", "snapshot", "schedule", "add"}, {}, + "Add mirror group snapshot schedule.", "", &get_arguments_add, &execute_add); +Shell::Action remove_action( + {"mirror", "group", "snapshot", "schedule", "remove"}, + {"mirror", "group", "snapshot", "schedule", "rm"}, + "Remove mirror group snapshot schedule.", "", &get_arguments_remove, + &execute_remove); +Shell::Action list_action( + {"mirror", "group", "snapshot", "schedule", "list"}, + {"mirror", "group", "snapshot", "schedule", "ls"}, + "List mirror group snapshot schedule.", "", &get_arguments_list, + &execute_list); +Shell::Action status_action( + {"mirror", "group", "snapshot", "schedule", "status"}, {}, + "Show mirror group snapshot schedule status.", "", &get_arguments_status, + &execute_status); + +} // namespace mirror_group_snapshot_schedule +} // namespace action +} // namespace rbd -- 2.39.5