]> git.proxmox.com Git - ceph.git/blob - ceph/src/tools/rbd/Schedule.cc
596408199ee23543a4fc0d52c7120397f9c6ff8c
[ceph.git] / ceph / src / tools / rbd / Schedule.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3
4 #include "common/Formatter.h"
5 #include "common/TextTable.h"
6 #include "common/ceph_json.h"
7 #include "tools/rbd/ArgumentTypes.h"
8 #include "tools/rbd/Schedule.h"
9 #include "tools/rbd/Utils.h"
10
11 #include <iostream>
12 #include <regex>
13
14 namespace rbd {
15
16 namespace at = argument_types;
17 namespace po = boost::program_options;
18
19 namespace {
20
21 int parse_schedule_name(const std::string &name, bool allow_images,
22 std::string *pool_name, std::string *namespace_name,
23 std::string *image_name) {
24 // parse names like:
25 // '', 'rbd/', 'rbd/ns/', 'rbd/image', 'rbd/ns/image'
26 std::regex pattern("^(?:([^/]+)/(?:(?:([^/]+)/|)(?:([^/@]+))?)?)?$");
27 std::smatch match;
28 if (!std::regex_match(name, match, pattern)) {
29 return -EINVAL;
30 }
31
32 if (match[1].matched) {
33 *pool_name = match[1];
34 } else {
35 *pool_name = "-";
36 }
37
38 if (match[2].matched) {
39 *namespace_name = match[2];
40 } else if (match[3].matched) {
41 *namespace_name = "";
42 } else {
43 *namespace_name = "-";
44 }
45
46 if (match[3].matched) {
47 if (!allow_images) {
48 return -EINVAL;
49 }
50 *image_name = match[3];
51 } else {
52 *image_name = "-";
53 }
54
55 return 0;
56 }
57
58 } // anonymous namespace
59
60 void add_level_spec_options(po::options_description *options,
61 bool allow_image) {
62 at::add_pool_option(options, at::ARGUMENT_MODIFIER_NONE);
63 at::add_namespace_option(options, at::ARGUMENT_MODIFIER_NONE);
64 if (allow_image) {
65 at::add_image_option(options, at::ARGUMENT_MODIFIER_NONE);
66 }
67 }
68
69 int get_level_spec_args(const po::variables_map &vm,
70 std::map<std::string, std::string> *args) {
71 if (vm.count(at::IMAGE_NAME)) {
72 std::string pool_name;
73 std::string namespace_name;
74 std::string image_name;
75
76 int r = utils::extract_spec(vm[at::IMAGE_NAME].as<std::string>(),
77 &pool_name, &namespace_name, &image_name,
78 nullptr, utils::SPEC_VALIDATION_FULL);
79 if (r < 0) {
80 return r;
81 }
82
83 if (!pool_name.empty()) {
84 if (vm.count(at::POOL_NAME)) {
85 std::cerr << "rbd: pool is specified both via pool and image options"
86 << std::endl;
87 return -EINVAL;
88 }
89 if (vm.count(at::NAMESPACE_NAME)) {
90 std::cerr << "rbd: namespace is specified both via namespace and image"
91 << " options" << std::endl;
92 return -EINVAL;
93 }
94 }
95
96 if (vm.count(at::POOL_NAME)) {
97 pool_name = vm[at::POOL_NAME].as<std::string>();
98 } else if (pool_name.empty()) {
99 pool_name = utils::get_default_pool_name();
100 }
101
102 if (vm.count(at::NAMESPACE_NAME)) {
103 namespace_name = vm[at::NAMESPACE_NAME].as<std::string>();
104 }
105
106 if (namespace_name.empty()) {
107 (*args)["level_spec"] = pool_name + "/" + image_name;
108 } else {
109 (*args)["level_spec"] = pool_name + "/" + namespace_name + "/" +
110 image_name;
111 }
112 return 0;
113 }
114
115 if (vm.count(at::NAMESPACE_NAME)) {
116 std::string pool_name;
117 std::string namespace_name;
118
119 if (vm.count(at::POOL_NAME)) {
120 pool_name = vm[at::POOL_NAME].as<std::string>();
121 } else {
122 pool_name = utils::get_default_pool_name();
123 }
124
125 namespace_name = vm[at::NAMESPACE_NAME].as<std::string>();
126
127 (*args)["level_spec"] = pool_name + "/" + namespace_name + "/";
128
129 return 0;
130 }
131
132 if (vm.count(at::POOL_NAME)) {
133 std::string pool_name = vm[at::POOL_NAME].as<std::string>();
134
135 (*args)["level_spec"] = pool_name + "/";
136
137 return 0;
138 }
139
140 (*args)["level_spec"] = "";
141
142 return 0;
143 }
144
145 void add_schedule_options(po::options_description *positional) {
146 positional->add_options()
147 ("interval", "schedule interval");
148 positional->add_options()
149 ("start-time", "schedule start time");
150 }
151
152 int get_schedule_args(const po::variables_map &vm, bool mandatory,
153 std::map<std::string, std::string> *args) {
154 size_t arg_index = 0;
155
156 std::string interval = utils::get_positional_argument(vm, arg_index++);
157 if (interval.empty()) {
158 if (mandatory) {
159 std::cerr << "rbd: missing 'interval' argument" << std::endl;
160 return -EINVAL;
161 }
162 return 0;
163 }
164 (*args)["interval"] = interval;
165
166 std::string start_time = utils::get_positional_argument(vm, arg_index++);
167 if (!start_time.empty()) {
168 (*args)["start_time"] = start_time;
169 }
170
171 return 0;
172 }
173
174 int Schedule::parse(json_spirit::mValue &schedule_val) {
175 if (schedule_val.type() != json_spirit::array_type) {
176 std::cerr << "rbd: unexpected schedule JSON received: "
177 << "schedule is not array" << std::endl;
178 return -EBADMSG;
179 }
180
181 try {
182 for (auto &item_val : schedule_val.get_array()) {
183 if (item_val.type() != json_spirit::obj_type) {
184 std::cerr << "rbd: unexpected schedule JSON received: "
185 << "schedule item is not object" << std::endl;
186 return -EBADMSG;
187 }
188
189 auto &item = item_val.get_obj();
190
191 if (item["interval"].type() != json_spirit::str_type) {
192 std::cerr << "rbd: unexpected schedule JSON received: "
193 << "interval is not string" << std::endl;
194 return -EBADMSG;
195 }
196 auto interval = item["interval"].get_str();
197
198 std::string start_time;
199 if (item["start_time"].type() == json_spirit::str_type) {
200 start_time = item["start_time"].get_str();
201 }
202
203 items.push_back({interval, start_time});
204 }
205
206 } catch (std::runtime_error &) {
207 std::cerr << "rbd: invalid schedule JSON received" << std::endl;
208 return -EBADMSG;
209 }
210
211 return 0;
212 }
213
214 void Schedule::dump(ceph::Formatter *f) {
215 f->open_array_section("items");
216 for (auto &item : items) {
217 f->open_object_section("item");
218 f->dump_string("interval", item.first);
219 f->dump_string("start_time", item.second);
220 f->close_section(); // item
221 }
222 f->close_section(); // items
223 }
224
225 std::ostream& operator<<(std::ostream& os, Schedule &s) {
226 std::string delimiter;
227 for (auto &item : s.items) {
228 os << delimiter << "every " << item.first;
229 if (!item.second.empty()) {
230 os << " starting at " << item.second;
231 }
232 delimiter = ", ";
233 }
234 return os;
235 }
236
237 int ScheduleList::parse(const std::string &list) {
238 json_spirit::mValue json_root;
239 if (!json_spirit::read(list, json_root)) {
240 std::cerr << "rbd: invalid schedule list JSON received" << std::endl;
241 return -EBADMSG;
242 }
243
244 try {
245 for (auto &[id, schedule_val] : json_root.get_obj()) {
246 if (schedule_val.type() != json_spirit::obj_type) {
247 std::cerr << "rbd: unexpected schedule list JSON received: "
248 << "schedule_val is not object" << std::endl;
249 return -EBADMSG;
250 }
251 auto &schedule = schedule_val.get_obj();
252 if (schedule["name"].type() != json_spirit::str_type) {
253 std::cerr << "rbd: unexpected schedule list JSON received: "
254 << "schedule name is not string" << std::endl;
255 return -EBADMSG;
256 }
257 auto name = schedule["name"].get_str();
258
259 if (schedule["schedule"].type() != json_spirit::array_type) {
260 std::cerr << "rbd: unexpected schedule list JSON received: "
261 << "schedule is not array" << std::endl;
262 return -EBADMSG;
263 }
264
265 Schedule s;
266 int r = s.parse(schedule["schedule"]);
267 if (r < 0) {
268 return r;
269 }
270 schedules[name] = s;
271 }
272 } catch (std::runtime_error &) {
273 std::cerr << "rbd: invalid schedule list JSON received" << std::endl;
274 return -EBADMSG;
275 }
276
277 return 0;
278 }
279
280 Schedule *ScheduleList::find(const std::string &name) {
281 auto it = schedules.find(name);
282 if (it == schedules.end()) {
283 return nullptr;
284 }
285
286 return &it->second;
287 }
288
289 void ScheduleList::dump(ceph::Formatter *f) {
290 f->open_array_section("schedules");
291 for (auto &[name, s] : schedules) {
292 std::string pool_name;
293 std::string namespace_name;
294 std::string image_name;
295
296 int r = parse_schedule_name(name, allow_images, &pool_name, &namespace_name,
297 &image_name);
298 if (r < 0) {
299 continue;
300 }
301
302 f->open_object_section("schedule");
303 f->dump_string("pool", pool_name);
304 f->dump_string("namespace", namespace_name);
305 if (allow_images) {
306 f->dump_string("image", image_name);
307 }
308 s.dump(f);
309 f->close_section();
310 }
311 f->close_section();
312 }
313
314 std::ostream& operator<<(std::ostream& os, ScheduleList &l) {
315 TextTable tbl;
316 tbl.define_column("POOL", TextTable::LEFT, TextTable::LEFT);
317 tbl.define_column("NAMESPACE", TextTable::LEFT, TextTable::LEFT);
318 if (l.allow_images) {
319 tbl.define_column("IMAGE", TextTable::LEFT, TextTable::LEFT);
320 }
321 tbl.define_column("SCHEDULE", TextTable::LEFT, TextTable::LEFT);
322
323 for (auto &[name, s] : l.schedules) {
324 std::string pool_name;
325 std::string namespace_name;
326 std::string image_name;
327
328 int r = parse_schedule_name(name, l.allow_images, &pool_name,
329 &namespace_name, &image_name);
330 if (r < 0) {
331 continue;
332 }
333
334 std::stringstream ss;
335 ss << s;
336
337 tbl << pool_name << namespace_name;
338 if (l.allow_images) {
339 tbl << image_name;
340 }
341 tbl << ss.str() << TextTable::endrow;
342 }
343
344 os << tbl;
345 return os;
346 }
347
348 } // namespace rbd
349