]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rbd: generalize schedule reusable code
authorMykola Golub <mgolub@suse.com>
Sun, 23 Feb 2020 16:49:38 +0000 (16:49 +0000)
committerMykola Golub <mgolub@suse.com>
Mon, 24 Feb 2020 13:40:28 +0000 (13:40 +0000)
Signed-off-by: Mykola Golub <mgolub@suse.com>
src/tools/rbd/CMakeLists.txt
src/tools/rbd/Schedule.cc [new file with mode: 0644]
src/tools/rbd/Schedule.h [new file with mode: 0644]
src/tools/rbd/Utils.cc
src/tools/rbd/Utils.h
src/tools/rbd/action/MirrorSnapshotSchedule.cc

index bcf3cff1db3b0beaff3a4c74562f7e5c81c9b44b..2abf9341363eaf5630089fe8b84d7a94595d4308 100644 (file)
@@ -7,6 +7,7 @@ set(rbd_srcs
   IndentStream.cc
   MirrorDaemonServiceInfo.cc
   OptionPrinter.cc
+  Schedule.cc
   Shell.cc
   Utils.cc
   action/Bench.cc
diff --git a/src/tools/rbd/Schedule.cc b/src/tools/rbd/Schedule.cc
new file mode 100644 (file)
index 0000000..5964081
--- /dev/null
@@ -0,0 +1,349 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "common/Formatter.h"
+#include "common/TextTable.h"
+#include "common/ceph_json.h"
+#include "tools/rbd/ArgumentTypes.h"
+#include "tools/rbd/Schedule.h"
+#include "tools/rbd/Utils.h"
+
+#include <iostream>
+#include <regex>
+
+namespace rbd {
+
+namespace at = argument_types;
+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) {
+  // parse names like:
+  // '', 'rbd/', 'rbd/ns/', 'rbd/image', 'rbd/ns/image'
+  std::regex pattern("^(?:([^/]+)/(?:(?:([^/]+)/|)(?:([^/@]+))?)?)?$");
+  std::smatch match;
+  if (!std::regex_match(name, match, pattern)) {
+    return -EINVAL;
+  }
+
+  if (match[1].matched) {
+    *pool_name = match[1];
+  } else {
+    *pool_name = "-";
+  }
+
+  if (match[2].matched) {
+    *namespace_name = match[2];
+  } else if (match[3].matched) {
+    *namespace_name = "";
+  } else {
+    *namespace_name = "-";
+  }
+
+  if (match[3].matched) {
+    if (!allow_images) {
+        return -EINVAL;
+    }
+    *image_name = match[3];
+  } else {
+    *image_name = "-";
+  }
+
+  return 0;
+}
+
+} // anonymous namespace
+
+void add_level_spec_options(po::options_description *options,
+                            bool allow_image) {
+  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);
+  }
+}
+
+int get_level_spec_args(const po::variables_map &vm,
+                        std::map<std::string, std::string> *args) {
+  if (vm.count(at::IMAGE_NAME)) {
+    std::string pool_name;
+    std::string namespace_name;
+    std::string image_name;
+
+    int r = utils::extract_spec(vm[at::IMAGE_NAME].as<std::string>(),
+                                &pool_name, &namespace_name, &image_name,
+                                nullptr, utils::SPEC_VALIDATION_FULL);
+    if (r < 0) {
+      return r;
+    }
+
+    if (!pool_name.empty()) {
+      if (vm.count(at::POOL_NAME)) {
+        std::cerr << "rbd: pool is specified both via pool and image options"
+                  << std::endl;
+        return -EINVAL;
+      }
+      if (vm.count(at::NAMESPACE_NAME)) {
+        std::cerr << "rbd: namespace is specified both via namespace and image"
+                  << " options" << std::endl;
+        return -EINVAL;
+      }
+    }
+
+    if (vm.count(at::POOL_NAME)) {
+      pool_name = vm[at::POOL_NAME].as<std::string>();
+    } else if (pool_name.empty()) {
+      pool_name = utils::get_default_pool_name();
+    }
+
+    if (vm.count(at::NAMESPACE_NAME)) {
+      namespace_name = vm[at::NAMESPACE_NAME].as<std::string>();
+    }
+
+    if (namespace_name.empty()) {
+      (*args)["level_spec"] = pool_name + "/" + image_name;
+    } else {
+      (*args)["level_spec"] = pool_name + "/" + namespace_name + "/" +
+        image_name;
+    }
+    return 0;
+  }
+
+  if (vm.count(at::NAMESPACE_NAME)) {
+    std::string pool_name;
+    std::string namespace_name;
+
+    if (vm.count(at::POOL_NAME)) {
+      pool_name = vm[at::POOL_NAME].as<std::string>();
+    } else {
+      pool_name = utils::get_default_pool_name();
+    }
+
+    namespace_name = vm[at::NAMESPACE_NAME].as<std::string>();
+
+    (*args)["level_spec"] = pool_name + "/" + namespace_name + "/";
+
+    return 0;
+  }
+
+  if (vm.count(at::POOL_NAME)) {
+    std::string pool_name = vm[at::POOL_NAME].as<std::string>();
+
+    (*args)["level_spec"] = pool_name + "/";
+
+    return 0;
+  }
+
+  (*args)["level_spec"] = "";
+
+  return 0;
+}
+
+void add_schedule_options(po::options_description *positional) {
+  positional->add_options()
+    ("interval", "schedule interval");
+  positional->add_options()
+    ("start-time", "schedule start time");
+}
+
+int get_schedule_args(const po::variables_map &vm, bool mandatory,
+                      std::map<std::string, std::string> *args) {
+  size_t arg_index = 0;
+
+  std::string interval = utils::get_positional_argument(vm, arg_index++);
+  if (interval.empty()) {
+    if (mandatory) {
+      std::cerr << "rbd: missing 'interval' argument" << std::endl;
+      return -EINVAL;
+    }
+    return 0;
+  }
+  (*args)["interval"] = interval;
+
+  std::string start_time = utils::get_positional_argument(vm, arg_index++);
+  if (!start_time.empty()) {
+    (*args)["start_time"] = start_time;
+  }
+
+  return 0;
+}
+
+int Schedule::parse(json_spirit::mValue &schedule_val) {
+  if (schedule_val.type() != json_spirit::array_type) {
+    std::cerr << "rbd: unexpected schedule JSON received: "
+              << "schedule is not array" << std::endl;
+    return -EBADMSG;
+  }
+
+  try {
+    for (auto &item_val : schedule_val.get_array()) {
+      if (item_val.type() != json_spirit::obj_type) {
+        std::cerr << "rbd: unexpected schedule JSON received: "
+                  << "schedule item is not object" << std::endl;
+        return -EBADMSG;
+      }
+
+      auto &item = item_val.get_obj();
+
+      if (item["interval"].type() != json_spirit::str_type) {
+        std::cerr << "rbd: unexpected schedule JSON received: "
+                  << "interval is not string" << std::endl;
+        return -EBADMSG;
+      }
+      auto interval = item["interval"].get_str();
+
+      std::string start_time;
+      if (item["start_time"].type() == json_spirit::str_type) {
+        start_time = item["start_time"].get_str();
+      }
+
+      items.push_back({interval, start_time});
+    }
+
+  } catch (std::runtime_error &) {
+    std::cerr << "rbd: invalid schedule JSON received" << std::endl;
+    return -EBADMSG;
+  }
+
+  return 0;
+}
+
+void Schedule::dump(ceph::Formatter *f) {
+  f->open_array_section("items");
+  for (auto &item : items) {
+    f->open_object_section("item");
+    f->dump_string("interval", item.first);
+    f->dump_string("start_time", item.second);
+    f->close_section(); // item
+  }
+  f->close_section(); // items
+}
+
+std::ostream& operator<<(std::ostream& os, Schedule &s) {
+  std::string delimiter;
+  for (auto &item : s.items) {
+    os << delimiter << "every " << item.first;
+    if (!item.second.empty()) {
+      os << " starting at " << item.second;
+    }
+    delimiter = ", ";
+  }
+  return os;
+}
+
+int ScheduleList::parse(const std::string &list) {
+  json_spirit::mValue json_root;
+  if (!json_spirit::read(list, json_root)) {
+    std::cerr << "rbd: invalid schedule list JSON received" << std::endl;
+    return -EBADMSG;
+  }
+
+  try {
+    for (auto &[id, schedule_val] : json_root.get_obj()) {
+      if (schedule_val.type() != json_spirit::obj_type) {
+        std::cerr << "rbd: unexpected schedule list JSON received: "
+                  << "schedule_val is not object" << std::endl;
+        return -EBADMSG;
+      }
+      auto &schedule = schedule_val.get_obj();
+      if (schedule["name"].type() != json_spirit::str_type) {
+        std::cerr << "rbd: unexpected schedule list JSON received: "
+                  << "schedule name is not string" << std::endl;
+        return -EBADMSG;
+      }
+      auto name = schedule["name"].get_str();
+
+      if (schedule["schedule"].type() != json_spirit::array_type) {
+        std::cerr << "rbd: unexpected schedule list JSON received: "
+                  << "schedule is not array" << std::endl;
+        return -EBADMSG;
+      }
+
+      Schedule s;
+      int r = s.parse(schedule["schedule"]);
+      if (r < 0) {
+        return r;
+      }
+      schedules[name] = s;
+    }
+  } catch (std::runtime_error &) {
+    std::cerr << "rbd: invalid schedule list JSON received" << std::endl;
+    return -EBADMSG;
+  }
+
+  return 0;
+}
+
+Schedule *ScheduleList::find(const std::string &name) {
+  auto it = schedules.find(name);
+  if (it == schedules.end()) {
+    return nullptr;
+  }
+
+  return &it->second;
+}
+
+void ScheduleList::dump(ceph::Formatter *f) {
+  f->open_array_section("schedules");
+  for (auto &[name, s] : schedules) {
+    std::string pool_name;
+    std::string namespace_name;
+    std::string image_name;
+
+    int r = parse_schedule_name(name, allow_images, &pool_name, &namespace_name,
+                                &image_name);
+    if (r < 0) {
+      continue;
+    }
+
+    f->open_object_section("schedule");
+    f->dump_string("pool", pool_name);
+    f->dump_string("namespace", namespace_name);
+    if (allow_images) {
+      f->dump_string("image", image_name);
+    }
+    s.dump(f);
+    f->close_section();
+  }
+  f->close_section();
+}
+
+std::ostream& operator<<(std::ostream& os, ScheduleList &l) {
+  TextTable tbl;
+  tbl.define_column("POOL", TextTable::LEFT, TextTable::LEFT);
+  tbl.define_column("NAMESPACE", TextTable::LEFT, TextTable::LEFT);
+  if (l.allow_images) {
+    tbl.define_column("IMAGE", TextTable::LEFT, TextTable::LEFT);
+  }
+  tbl.define_column("SCHEDULE", TextTable::LEFT, TextTable::LEFT);
+
+  for (auto &[name, s] : l.schedules) {
+    std::string pool_name;
+    std::string namespace_name;
+    std::string image_name;
+
+    int r = parse_schedule_name(name, l.allow_images, &pool_name,
+                                &namespace_name, &image_name);
+    if (r < 0) {
+      continue;
+    }
+
+    std::stringstream ss;
+    ss << s;
+
+    tbl << pool_name << namespace_name;
+    if (l.allow_images) {
+      tbl << image_name;
+    }
+    tbl << ss.str() << TextTable::endrow;
+  }
+
+  os << tbl;
+  return os;
+}
+
+} // namespace rbd
+
diff --git a/src/tools/rbd/Schedule.h b/src/tools/rbd/Schedule.h
new file mode 100644 (file)
index 0000000..c92b935
--- /dev/null
@@ -0,0 +1,65 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_RBD_SCHEDULE_H
+#define CEPH_RBD_SCHEDULE_H
+
+#include "json_spirit/json_spirit.h"
+
+#include <iostream>
+#include <list>
+#include <map>
+#include <string>
+#include <boost/program_options.hpp>
+
+namespace ceph { class Formatter; }
+
+namespace rbd {
+
+void add_level_spec_options(
+  boost::program_options::options_description *options, bool allow_image=true);
+int get_level_spec_args(const boost::program_options::variables_map &vm,
+                        std::map<std::string, std::string> *args);
+void add_schedule_options(
+  boost::program_options::options_description *positional);
+int get_schedule_args(const boost::program_options::variables_map &vm,
+                      bool mandatory, std::map<std::string, std::string> *args);
+
+class Schedule {
+public:
+  Schedule() {
+  }
+  
+  int parse(json_spirit::mValue &schedule_val);
+  void dump(ceph::Formatter *f);
+
+  friend std::ostream& operator<<(std::ostream& os, Schedule &s);
+
+private:
+  std::string name;
+  std::list<std::pair<std::string, std::string>> items;
+};
+
+std::ostream& operator<<(std::ostream& os, Schedule &s);
+
+class ScheduleList {
+public:
+  ScheduleList(bool allow_images=true) : allow_images(allow_images) {
+  }
+
+  int parse(const std::string &list);
+  Schedule *find(const std::string &name);
+  void dump(ceph::Formatter *f);
+
+  friend std::ostream& operator<<(std::ostream& os, ScheduleList &l);
+
+private:
+  bool allow_images;
+  std::map<std::string, Schedule> schedules;
+};
+
+std::ostream& operator<<(std::ostream& os, ScheduleList &l);
+
+} // namespace rbd
+
+#endif // CEPH_RBD_SCHEDULE_H
index ea5ce46b84878f49bfc7a0ed60dfe6ff061a34bd..520b654714b8cea012194b33cc4f6d1cae7f31b9 100644 (file)
@@ -10,6 +10,7 @@
 #include "include/rbd/features.h"
 #include "common/config.h"
 #include "common/errno.h"
+#include "common/escape.h"
 #include "common/safe_io.h"
 #include "global/global_context.h"
 #include <iostream>
@@ -23,6 +24,24 @@ namespace utils {
 namespace at = argument_types;
 namespace po = boost::program_options;
 
+namespace {
+
+static std::string mgr_command_args_to_str(
+    const std::map<std::string, std::string> &args) {
+  std::string out = "";
+
+  std::string delimiter;
+  for (auto &it : args) {
+    out += delimiter + "\"" + it.first + "\": \"" +
+      stringify(json_stream_escaper(it.second)) + "\"";
+    delimiter = ",\n";
+  }
+
+  return out;
+}
+
+} // anonymous namespace
+
 int ProgressContext::update_progress(uint64_t offset, uint64_t total) {
   if (progress) {
     int pc = total ? (offset * 100ull / total) : 0;
@@ -1017,5 +1036,33 @@ void populate_unknown_mirror_image_site_statuses(
   std::swap(global_status->site_statuses, site_statuses);
 }
 
+int mgr_command(librados::Rados& rados, const std::string& cmd,
+                const std::map<std::string, std::string> &args,
+                std::ostream *out_os, std::ostream *err_os) {
+  std::string command = R"(
+    {
+      "prefix": ")" + cmd + R"(", )" + mgr_command_args_to_str(args) + R"(
+    })";
+
+  bufferlist in_bl;
+  bufferlist out_bl;
+  std::string outs;
+  int r = rados.mgr_command(command, in_bl, &out_bl, &outs);
+  if (r < 0) {
+    (*err_os) << "rbd: " << cmd << " failed: " << cpp_strerror(r);
+    if (!outs.empty()) {
+      (*err_os) << ": " << outs;
+    }
+    (*err_os) << std::endl;
+    return r;
+  }
+
+  if (out_bl.length() != 0) {
+    (*out_os) << out_bl.c_str();
+  }
+
+  return 0;
+}
+
 } // namespace utils
 } // namespace rbd
index bc9786b4ff214c96643b532a982199b60c653294..14e46d5c47886ec8c06ce3e68135a78300990120 100644 (file)
@@ -8,6 +8,7 @@
 #include "include/rados/librados.hpp"
 #include "include/rbd/librbd.hpp"
 #include "tools/rbd/ArgumentTypes.h"
+#include <map>
 #include <string>
 #include <boost/program_options.hpp>
 
@@ -219,6 +220,10 @@ void populate_unknown_mirror_image_site_statuses(
     const std::vector<librbd::mirror_peer_site_t>& mirror_peers,
     librbd::mirror_image_global_status_t* global_status);
 
+int mgr_command(librados::Rados& rados, const std::string& cmd,
+                const std::map<std::string, std::string> &args,
+                std::ostream *out_os, std::ostream *err_os);
+
 } // namespace utils
 } // namespace rbd
 
index 79e77f906d4e9852124a07bb42f7ae2627192281..f8dbb5ec6b2d5fe083634d6ed3623d5a215ce77a 100644 (file)
@@ -2,12 +2,12 @@
 // 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/escape.h"
 #include "common/Formatter.h"
 #include "common/TextTable.h"
 #include "global/global_context.h"
@@ -16,7 +16,6 @@
 #include <iostream>
 #include <list>
 #include <map>
-#include <regex>
 #include <string>
 #include <boost/program_options.hpp>
 
@@ -31,252 +30,6 @@ namespace po = boost::program_options;
 
 namespace {
 
-struct Args {
-  std::map<std::string, std::string> args;
-
-  Args() {
-  }
-  Args(const std::map<std::string, std::string> &args)
-    : args(args) {
-  }
-
-  std::string str() const {
-    std::string out = "";
-
-    std::string delimiter;
-    for (auto &it : args) {
-      out += delimiter + "\"" + it.first + "\": \"" +
-        stringify(json_stream_escaper(it.second)) + "\"";
-      delimiter = ",\n";
-    }
-
-    return out;
-  }
-};
-
-class Schedule {
-public:
-  Schedule() {
-  }
-
-  int parse(json_spirit::mValue &schedule_val) {
-    if (schedule_val.type() != json_spirit::array_type) {
-      std::cerr << "rbd: unexpected schedule JSON received: "
-                << "schedule is not array" << std::endl;
-      return -EBADMSG;
-    }
-
-    try {
-      for (auto &item_val : schedule_val.get_array()) {
-        if (item_val.type() != json_spirit::obj_type) {
-          std::cerr << "rbd: unexpected schedule JSON received: "
-                    << "schedule item is not object" << std::endl;
-          return -EBADMSG;
-        }
-
-        auto &item = item_val.get_obj();
-
-        if (item["interval"].type() != json_spirit::str_type) {
-          std::cerr << "rbd: unexpected schedule JSON received: "
-                    << "interval is not string" << std::endl;
-          return -EBADMSG;
-        }
-        auto interval = item["interval"].get_str();
-
-        std::string start_time;
-        if (item["start_time"].type() == json_spirit::str_type) {
-          start_time = item["start_time"].get_str();
-        }
-
-        items.push_back({interval, start_time});
-      }
-
-    } 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("items");
-    for (auto &item : items) {
-      f->open_object_section("item");
-      f->dump_string("interval", item.first);
-      f->dump_string("start_time", item.second);
-      f->close_section(); // item
-    }
-    f->close_section(); // items
-  }
-
-  friend std::ostream& operator<<(std::ostream& os, Schedule &s);
-
-private:
-  std::string name;
-  std::list<std::pair<std::string, std::string>> items;
-};
-
-std::ostream& operator<<(std::ostream& os, Schedule &s) {
-  std::string delimiter;
-  for (auto &item : s.items) {
-    os << delimiter << "every " << item.first;
-    if (!item.second.empty()) {
-      os << " starting at " << item.second;
-    }
-    delimiter = ", ";
-  }
-  return os;
-}
-
-int parse_schedule_name(const std::string &name, std::string *pool_name,
-                        std::string *namespace_name, std::string *image_name) {
-  // parse names like:
-  // '', 'rbd/', 'rbd/ns/', 'rbd/image', 'rbd/ns/image'
-  std::regex pattern("^(?:([^/]+)/(?:(?:([^/]+)/|)(?:([^/@]+))?)?)?$");
-  std::smatch match;
-  if (!std::regex_match(name, match, pattern)) {
-    return -EINVAL;
-  }
-
-  if (match[1].matched) {
-    *pool_name = match[1];
-  } else {
-    *pool_name = "-";
-  }
-
-  if (match[2].matched) {
-    *namespace_name = match[2];
-  } else if (match[3].matched) {
-    *namespace_name = "";
-  } else {
-    *namespace_name = "-";
-  }
-
-  if (match[3].matched) {
-    *image_name = match[3];
-  } else {
-    *image_name = "-";
-  }
-
-  return 0;
-}
-
-class ScheduleList {
-public:
-  ScheduleList() {
-  }
-
-  int parse(const std::string &list) {
-    json_spirit::mValue json_root;
-    if (!json_spirit::read(list, json_root)) {
-      std::cerr << "rbd: invalid schedule list JSON received" << std::endl;
-      return -EBADMSG;
-    }
-
-    try {
-      for (auto &[id, schedule_val] : json_root.get_obj()) {
-        if (schedule_val.type() != json_spirit::obj_type) {
-          std::cerr << "rbd: unexpected schedule list JSON received: "
-                    << "schedule_val is not object" << std::endl;
-          return -EBADMSG;
-        }
-        auto &schedule = schedule_val.get_obj();
-        if (schedule["name"].type() != json_spirit::str_type) {
-          std::cerr << "rbd: unexpected schedule list JSON received: "
-                    << "schedule name is not string" << std::endl;
-          return -EBADMSG;
-        }
-        auto name = schedule["name"].get_str();
-
-        if (schedule["schedule"].type() != json_spirit::array_type) {
-          std::cerr << "rbd: unexpected schedule list JSON received: "
-                    << "schedule is not array" << std::endl;
-          return -EBADMSG;
-        }
-
-        Schedule s;
-        int r = s.parse(schedule["schedule"]);
-        if (r < 0) {
-          return r;
-        }
-        schedules[name] = s;
-      }
-    } catch (std::runtime_error &) {
-      std::cerr << "rbd: invalid schedule list JSON received" << std::endl;
-      return -EBADMSG;
-    }
-
-    return 0;
-  }
-
-  Schedule *find(const std::string &name) {
-    auto it = schedules.find(name);
-    if (it == schedules.end()) {
-      return nullptr;
-    }
-
-    return &it->second;
-  }
-
-  void dump(Formatter *f) {
-    f->open_array_section("schedules");
-    for (auto &[name, s] : schedules) {
-      std::string pool_name;
-      std::string namespace_name;
-      std::string image_name;
-
-      int r = parse_schedule_name(name, &pool_name, &namespace_name,
-                                  &image_name);
-      if (r < 0) {
-        continue;
-      }
-
-      f->open_object_section("schedule");
-      f->dump_string("pool", pool_name);
-      f->dump_string("namespace", namespace_name);
-      f->dump_string("image", image_name);
-      s.dump(f);
-      f->close_section();
-    }
-    f->close_section();
-  }
-
-  friend std::ostream& operator<<(std::ostream& os, ScheduleList &d);
-
-private:
-  std::map<std::string, Schedule> schedules;
-};
-
-std::ostream& operator<<(std::ostream& os, ScheduleList &l) {
-  TextTable tbl;
-  tbl.define_column("POOL", TextTable::LEFT, TextTable::LEFT);
-  tbl.define_column("NAMESPACE", TextTable::LEFT, TextTable::LEFT);
-  tbl.define_column("IMAGE", TextTable::LEFT, TextTable::LEFT);
-  tbl.define_column("SCHEDULE", TextTable::LEFT, TextTable::LEFT);
-
-  for (auto &[name, s] : l.schedules) {
-    std::string pool_name;
-    std::string namespace_name;
-    std::string image_name;
-
-    int r = parse_schedule_name(name, &pool_name, &namespace_name,
-                                &image_name);
-    if (r < 0) {
-      continue;
-    }
-
-    std::stringstream ss;
-    ss << s;
-
-    tbl << pool_name << namespace_name << image_name << ss.str()
-        << TextTable::endrow;
-  }
-
-  os << tbl;
-  return os;
-}
-
 class ScheduleStatus {
 public:
   ScheduleStatus() {
@@ -363,155 +116,25 @@ std::ostream& operator<<(std::ostream& os, ScheduleStatus &s) {
   return os;
 }
 
-int ceph_rbd_mirror_snapshot_schedule(librados::Rados& rados,
-                                      const std::string& cmd,
-                                      const Args& args,
-                                      std::ostream *out_os,
-                                      std::ostream *err_os) {
-  std::string command = R"(
-    {
-      "prefix": "rbd mirror snapshot schedule )" + cmd + R"(",
-      )" + args.str() + R"(
-    })";
-
-  bufferlist in_bl;
-  bufferlist out_bl;
-  std::string outs;
-  int r = rados.mgr_command(command, in_bl, &out_bl, &outs);
-  if (r == -EOPNOTSUPP) {
-    (*err_os) << "rbd: 'rbd_support' mgr module is not enabled."
-              << std::endl << std::endl
-              << "Use 'ceph mgr module enable rbd_support' to enable."
-              << std::endl;
-    return r;
-  } else if (r < 0) {
-    (*err_os) << "rbd: " << cmd << " failed: " << cpp_strerror(r);
-    if (!outs.empty()) {
-      (*err_os) << ": " << outs;
-    }
-    (*err_os) << std::endl;
-    return r;
-  }
-
-  if (out_bl.length() != 0) {
-    (*out_os) << out_bl.c_str();
-  }
-
-  return 0;
-}
-
-void add_level_spec_options(po::options_description *options) {
-  at::add_pool_option(options, at::ARGUMENT_MODIFIER_NONE);
-  at::add_namespace_option(options, at::ARGUMENT_MODIFIER_NONE);
-  at::add_image_option(options, at::ARGUMENT_MODIFIER_NONE);
-}
-
-int get_level_spec_name(const po::variables_map &vm,
-                        std::string *level_spec_name) {
-  if (vm.count(at::IMAGE_NAME)) {
-    std::string pool_name;
-    std::string namespace_name;
-    std::string image_name;
-
-    int r = utils::extract_spec(vm[at::IMAGE_NAME].as<std::string>(),
-                                &pool_name, &namespace_name, &image_name,
-                                nullptr, utils::SPEC_VALIDATION_FULL);
-    if (r < 0) {
-      return r;
-    }
-
-    if (!pool_name.empty()) {
-      if (vm.count(at::POOL_NAME)) {
-        std::cerr << "rbd: pool is specified both via pool and image options"
-                  << std::endl;
-        return -EINVAL;
-      }
-      if (vm.count(at::NAMESPACE_NAME)) {
-        std::cerr << "rbd: namespace is specified both via namespace and image"
-                  << " options" << std::endl;
-        return -EINVAL;
-      }
-    }
-
-    if (vm.count(at::POOL_NAME)) {
-      pool_name = vm[at::POOL_NAME].as<std::string>();
-    } else if (pool_name.empty()) {
-      pool_name = utils::get_default_pool_name();
-    }
-
-    if (vm.count(at::NAMESPACE_NAME)) {
-      namespace_name = vm[at::NAMESPACE_NAME].as<std::string>();
-    }
-
-    if (namespace_name.empty()) {
-      *level_spec_name = pool_name + "/" + image_name;
-    } else {
-      *level_spec_name = pool_name + "/" + namespace_name + "/" + image_name;
-    }
-    return 0;
-  }
-
-  if (vm.count(at::NAMESPACE_NAME)) {
-    std::string pool_name;
-    std::string namespace_name;
-
-    if (vm.count(at::POOL_NAME)) {
-      pool_name = vm[at::POOL_NAME].as<std::string>();
-    } else {
-      pool_name = utils::get_default_pool_name();
-    }
-
-    namespace_name = vm[at::NAMESPACE_NAME].as<std::string>();
-
-    *level_spec_name = pool_name + "/" + namespace_name + "/";
-
-    return 0;
-  }
-
-  if (vm.count(at::POOL_NAME)) {
-    std::string pool_name = vm[at::POOL_NAME].as<std::string>();
-
-    *level_spec_name = pool_name + "/";
-
-    return 0;
-  }
-
-  *level_spec_name = "";
-
-  return 0;
-}
-
 } // anonymous namespace
 
 void get_arguments_add(po::options_description *positional,
                        po::options_description *options) {
   add_level_spec_options(options);
-  positional->add_options()
-    ("interval", "schedule interval");
-  positional->add_options()
-    ("start-time", "schedule start time");
+  add_schedule_options(positional);
 }
 
 int execute_add(const po::variables_map &vm,
                 const std::vector<std::string> &ceph_global_init_args) {
-  std::string level_spec_name;
-  int r = get_level_spec_name(vm, &level_spec_name);
+  std::map<std::string, std::string> args;
+
+  int r = get_level_spec_args(vm, &args);
   if (r < 0) {
     return r;
   }
-
-  size_t arg_index = 0;
-  std::string interval = utils::get_positional_argument(vm, arg_index++);
-  if (interval.empty()) {
-    std::cerr << "rbd: missing 'interval' argument" << std::endl;
-    return -EINVAL;
-  }
-
-  Args args({{"level_spec", level_spec_name}, {"interval", interval}});
-
-  std::string start_time = utils::get_positional_argument(vm, arg_index++);
-  if (!start_time.empty()) {
-    args.args["start_time"] = start_time;
+  r = get_schedule_args(vm, true, &args);
+  if (r < 0) {
+    return r;
   }
 
   librados::Rados rados;
@@ -520,8 +143,8 @@ int execute_add(const po::variables_map &vm,
     return r;
   }
 
-  r = ceph_rbd_mirror_snapshot_schedule(rados, "add", args, &std::cout,
-                                        &std::cerr);
+  r = utils::mgr_command(rados, "rbd mirror snapshot schedule add", args,
+                         &std::cout, &std::cerr);
   if (r < 0) {
     return r;
   }
@@ -532,31 +155,20 @@ int execute_add(const po::variables_map &vm,
 void get_arguments_remove(po::options_description *positional,
                           po::options_description *options) {
   add_level_spec_options(options);
-  positional->add_options()
-    ("interval", "schedule interval");
-  positional->add_options()
-    ("start-time", "schedule start time");
+  add_schedule_options(positional);
 }
 
 int execute_remove(const po::variables_map &vm,
                    const std::vector<std::string> &ceph_global_init_args) {
-  std::string level_spec_name;
-  int r = get_level_spec_name(vm, &level_spec_name);
+  std::map<std::string, std::string> args;
+
+  int r = get_level_spec_args(vm, &args);
   if (r < 0) {
     return r;
   }
-
-  Args args({{"level_spec", level_spec_name}});
-
-  size_t arg_index = 0;
-  std::string interval = utils::get_positional_argument(vm, arg_index++);
-  if (!interval.empty()) {
-    args.args["interval"] = interval;
-  }
-
-  std::string start_time = utils::get_positional_argument(vm, arg_index++);
-  if (!start_time.empty()) {
-    args.args["start_time"] = start_time;
+  r = get_schedule_args(vm, false, &args);
+  if (r < 0) {
+    return r;
   }
 
   librados::Rados rados;
@@ -565,8 +177,8 @@ int execute_remove(const po::variables_map &vm,
     return r;
   }
 
-  r = ceph_rbd_mirror_snapshot_schedule(rados, "remove", args, &std::cout,
-                                        &std::cerr);
+  r = utils::mgr_command(rados, "rbd mirror snapshot schedule remove", args,
+                         &std::cout, &std::cerr);
   if (r < 0) {
     return r;
   }
@@ -584,8 +196,9 @@ void get_arguments_list(po::options_description *positional,
 
 int execute_list(const po::variables_map &vm,
                  const std::vector<std::string> &ceph_global_init_args) {
-  std::string level_spec_name;
-  int r = get_level_spec_name(vm, &level_spec_name);
+  std::map<std::string, std::string> args;
+
+  int r = get_level_spec_args(vm, &args);
   if (r < 0) {
     return r;
   }
@@ -602,9 +215,9 @@ int execute_list(const po::variables_map &vm,
     return r;
   }
 
-  Args args({{"level_spec", level_spec_name}});
   std::stringstream out;
-  r = ceph_rbd_mirror_snapshot_schedule(rados, "list", args, &out, &std::cerr);
+  r = utils::mgr_command(rados, "rbd mirror snapshot schedule list", args, &out,
+                         &std::cerr);
   if (r < 0) {
     return r;
   }
@@ -623,7 +236,7 @@ int execute_list(const po::variables_map &vm,
       std::cout << schedule_list;
     }
   } else {
-    auto schedule = schedule_list.find(level_spec_name);
+    auto schedule = schedule_list.find(args["level_spec"]);
     if (schedule == nullptr) {
       return -ENOENT;
     }
@@ -647,8 +260,9 @@ void get_arguments_status(po::options_description *positional,
 
 int execute_status(const po::variables_map &vm,
                    const std::vector<std::string> &ceph_global_init_args) {
-  std::string level_spec_name;
-  int r = get_level_spec_name(vm, &level_spec_name);
+  std::map<std::string, std::string> args;
+
+  int r = get_level_spec_args(vm, &args);
   if (r < 0) {
     return r;
   }
@@ -665,11 +279,9 @@ int execute_status(const po::variables_map &vm,
     return r;
   }
 
-  Args args({{"level_spec", level_spec_name}});
-
   std::stringstream out;
-  r = ceph_rbd_mirror_snapshot_schedule(rados, "status", args, &out,
-                                        &std::cerr);
+  r = utils::mgr_command(rados, "rbd mirror snapshot schedule status", args,
+                         &out, &std::cerr);
   ScheduleStatus schedule_status;
   r = schedule_status.parse(out.str());
   if (r < 0) {