summaryrefslogtreecommitdiffstats
path: root/src/tools/rbd/Schedule.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rbd/Schedule.cc')
-rw-r--r--src/tools/rbd/Schedule.cc367
1 files changed, 367 insertions, 0 deletions
diff --git a/src/tools/rbd/Schedule.cc b/src/tools/rbd/Schedule.cc
new file mode 100644
index 000000000..15dda3aee
--- /dev/null
+++ b/src/tools/rbd/Schedule.cc
@@ -0,0 +1,367 @@
+// -*- 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>();
+ }
+
+ 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>();
+ }
+
+ 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 normalize_level_spec_args(std::map<std::string, std::string> *args) {
+ std::map<std::string, std::string> raw_args;
+ std::swap(raw_args, *args);
+
+ auto default_pool_name = utils::get_default_pool_name();
+ for (auto [key, value] : raw_args) {
+ if (key == "level_spec" && !value.empty() && value[0] == '/') {
+ value = default_pool_name + value;
+ }
+
+ (*args)[key] = value;
+ }
+}
+
+void add_schedule_options(po::options_description *positional,
+ bool mandatory) {
+ if (mandatory) {
+ positional->add_options()
+ ("interval", "schedule interval");
+ } else {
+ positional->add_options()
+ ("interval", po::value<std::string>()->default_value(""),
+ "schedule interval");
+ }
+ positional->add_options()
+ ("start-time", po::value<std::string>()->default_value(""),
+ "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
+