]>
Commit | Line | Data |
---|---|---|
9f95a23c TL |
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>(); | |
9f95a23c TL |
98 | } |
99 | ||
100 | if (vm.count(at::NAMESPACE_NAME)) { | |
101 | namespace_name = vm[at::NAMESPACE_NAME].as<std::string>(); | |
102 | } | |
103 | ||
104 | if (namespace_name.empty()) { | |
105 | (*args)["level_spec"] = pool_name + "/" + image_name; | |
106 | } else { | |
107 | (*args)["level_spec"] = pool_name + "/" + namespace_name + "/" + | |
108 | image_name; | |
109 | } | |
110 | return 0; | |
111 | } | |
112 | ||
113 | if (vm.count(at::NAMESPACE_NAME)) { | |
114 | std::string pool_name; | |
115 | std::string namespace_name; | |
116 | ||
117 | if (vm.count(at::POOL_NAME)) { | |
118 | pool_name = vm[at::POOL_NAME].as<std::string>(); | |
9f95a23c TL |
119 | } |
120 | ||
121 | namespace_name = vm[at::NAMESPACE_NAME].as<std::string>(); | |
122 | ||
123 | (*args)["level_spec"] = pool_name + "/" + namespace_name + "/"; | |
124 | ||
125 | return 0; | |
126 | } | |
127 | ||
128 | if (vm.count(at::POOL_NAME)) { | |
129 | std::string pool_name = vm[at::POOL_NAME].as<std::string>(); | |
130 | ||
131 | (*args)["level_spec"] = pool_name + "/"; | |
132 | ||
133 | return 0; | |
134 | } | |
135 | ||
136 | (*args)["level_spec"] = ""; | |
137 | ||
138 | return 0; | |
139 | } | |
140 | ||
ec96510d FG |
141 | void normalize_level_spec_args(std::map<std::string, std::string> *args) { |
142 | std::map<std::string, std::string> raw_args; | |
143 | std::swap(raw_args, *args); | |
144 | ||
145 | auto default_pool_name = utils::get_default_pool_name(); | |
146 | for (auto [key, value] : raw_args) { | |
147 | if (key == "level_spec" && !value.empty() && value[0] == '/') { | |
148 | value = default_pool_name + value; | |
149 | } | |
150 | ||
151 | (*args)[key] = value; | |
152 | } | |
153 | } | |
154 | ||
9f95a23c TL |
155 | void add_schedule_options(po::options_description *positional) { |
156 | positional->add_options() | |
157 | ("interval", "schedule interval"); | |
158 | positional->add_options() | |
159 | ("start-time", "schedule start time"); | |
160 | } | |
161 | ||
162 | int get_schedule_args(const po::variables_map &vm, bool mandatory, | |
163 | std::map<std::string, std::string> *args) { | |
164 | size_t arg_index = 0; | |
165 | ||
166 | std::string interval = utils::get_positional_argument(vm, arg_index++); | |
167 | if (interval.empty()) { | |
168 | if (mandatory) { | |
169 | std::cerr << "rbd: missing 'interval' argument" << std::endl; | |
170 | return -EINVAL; | |
171 | } | |
172 | return 0; | |
173 | } | |
174 | (*args)["interval"] = interval; | |
175 | ||
176 | std::string start_time = utils::get_positional_argument(vm, arg_index++); | |
177 | if (!start_time.empty()) { | |
178 | (*args)["start_time"] = start_time; | |
179 | } | |
180 | ||
181 | return 0; | |
182 | } | |
183 | ||
184 | int Schedule::parse(json_spirit::mValue &schedule_val) { | |
185 | if (schedule_val.type() != json_spirit::array_type) { | |
186 | std::cerr << "rbd: unexpected schedule JSON received: " | |
187 | << "schedule is not array" << std::endl; | |
188 | return -EBADMSG; | |
189 | } | |
190 | ||
191 | try { | |
192 | for (auto &item_val : schedule_val.get_array()) { | |
193 | if (item_val.type() != json_spirit::obj_type) { | |
194 | std::cerr << "rbd: unexpected schedule JSON received: " | |
195 | << "schedule item is not object" << std::endl; | |
196 | return -EBADMSG; | |
197 | } | |
198 | ||
199 | auto &item = item_val.get_obj(); | |
200 | ||
201 | if (item["interval"].type() != json_spirit::str_type) { | |
202 | std::cerr << "rbd: unexpected schedule JSON received: " | |
203 | << "interval is not string" << std::endl; | |
204 | return -EBADMSG; | |
205 | } | |
206 | auto interval = item["interval"].get_str(); | |
207 | ||
208 | std::string start_time; | |
209 | if (item["start_time"].type() == json_spirit::str_type) { | |
210 | start_time = item["start_time"].get_str(); | |
211 | } | |
212 | ||
213 | items.push_back({interval, start_time}); | |
214 | } | |
215 | ||
216 | } catch (std::runtime_error &) { | |
217 | std::cerr << "rbd: invalid schedule JSON received" << std::endl; | |
218 | return -EBADMSG; | |
219 | } | |
220 | ||
221 | return 0; | |
222 | } | |
223 | ||
224 | void Schedule::dump(ceph::Formatter *f) { | |
225 | f->open_array_section("items"); | |
226 | for (auto &item : items) { | |
227 | f->open_object_section("item"); | |
228 | f->dump_string("interval", item.first); | |
229 | f->dump_string("start_time", item.second); | |
230 | f->close_section(); // item | |
231 | } | |
232 | f->close_section(); // items | |
233 | } | |
234 | ||
235 | std::ostream& operator<<(std::ostream& os, Schedule &s) { | |
236 | std::string delimiter; | |
237 | for (auto &item : s.items) { | |
238 | os << delimiter << "every " << item.first; | |
239 | if (!item.second.empty()) { | |
240 | os << " starting at " << item.second; | |
241 | } | |
242 | delimiter = ", "; | |
243 | } | |
244 | return os; | |
245 | } | |
246 | ||
247 | int ScheduleList::parse(const std::string &list) { | |
248 | json_spirit::mValue json_root; | |
249 | if (!json_spirit::read(list, json_root)) { | |
250 | std::cerr << "rbd: invalid schedule list JSON received" << std::endl; | |
251 | return -EBADMSG; | |
252 | } | |
253 | ||
254 | try { | |
255 | for (auto &[id, schedule_val] : json_root.get_obj()) { | |
256 | if (schedule_val.type() != json_spirit::obj_type) { | |
257 | std::cerr << "rbd: unexpected schedule list JSON received: " | |
258 | << "schedule_val is not object" << std::endl; | |
259 | return -EBADMSG; | |
260 | } | |
261 | auto &schedule = schedule_val.get_obj(); | |
262 | if (schedule["name"].type() != json_spirit::str_type) { | |
263 | std::cerr << "rbd: unexpected schedule list JSON received: " | |
264 | << "schedule name is not string" << std::endl; | |
265 | return -EBADMSG; | |
266 | } | |
267 | auto name = schedule["name"].get_str(); | |
268 | ||
269 | if (schedule["schedule"].type() != json_spirit::array_type) { | |
270 | std::cerr << "rbd: unexpected schedule list JSON received: " | |
271 | << "schedule is not array" << std::endl; | |
272 | return -EBADMSG; | |
273 | } | |
274 | ||
275 | Schedule s; | |
276 | int r = s.parse(schedule["schedule"]); | |
277 | if (r < 0) { | |
278 | return r; | |
279 | } | |
280 | schedules[name] = s; | |
281 | } | |
282 | } catch (std::runtime_error &) { | |
283 | std::cerr << "rbd: invalid schedule list JSON received" << std::endl; | |
284 | return -EBADMSG; | |
285 | } | |
286 | ||
287 | return 0; | |
288 | } | |
289 | ||
290 | Schedule *ScheduleList::find(const std::string &name) { | |
291 | auto it = schedules.find(name); | |
292 | if (it == schedules.end()) { | |
293 | return nullptr; | |
294 | } | |
295 | ||
296 | return &it->second; | |
297 | } | |
298 | ||
299 | void ScheduleList::dump(ceph::Formatter *f) { | |
300 | f->open_array_section("schedules"); | |
301 | for (auto &[name, s] : schedules) { | |
302 | std::string pool_name; | |
303 | std::string namespace_name; | |
304 | std::string image_name; | |
305 | ||
306 | int r = parse_schedule_name(name, allow_images, &pool_name, &namespace_name, | |
307 | &image_name); | |
308 | if (r < 0) { | |
309 | continue; | |
310 | } | |
311 | ||
312 | f->open_object_section("schedule"); | |
313 | f->dump_string("pool", pool_name); | |
314 | f->dump_string("namespace", namespace_name); | |
315 | if (allow_images) { | |
316 | f->dump_string("image", image_name); | |
317 | } | |
318 | s.dump(f); | |
319 | f->close_section(); | |
320 | } | |
321 | f->close_section(); | |
322 | } | |
323 | ||
324 | std::ostream& operator<<(std::ostream& os, ScheduleList &l) { | |
325 | TextTable tbl; | |
326 | tbl.define_column("POOL", TextTable::LEFT, TextTable::LEFT); | |
327 | tbl.define_column("NAMESPACE", TextTable::LEFT, TextTable::LEFT); | |
328 | if (l.allow_images) { | |
329 | tbl.define_column("IMAGE", TextTable::LEFT, TextTable::LEFT); | |
330 | } | |
331 | tbl.define_column("SCHEDULE", TextTable::LEFT, TextTable::LEFT); | |
332 | ||
333 | for (auto &[name, s] : l.schedules) { | |
334 | std::string pool_name; | |
335 | std::string namespace_name; | |
336 | std::string image_name; | |
337 | ||
338 | int r = parse_schedule_name(name, l.allow_images, &pool_name, | |
339 | &namespace_name, &image_name); | |
340 | if (r < 0) { | |
341 | continue; | |
342 | } | |
343 | ||
344 | std::stringstream ss; | |
345 | ss << s; | |
346 | ||
347 | tbl << pool_name << namespace_name; | |
348 | if (l.allow_images) { | |
349 | tbl << image_name; | |
350 | } | |
351 | tbl << ss.str() << TextTable::endrow; | |
352 | } | |
353 | ||
354 | os << tbl; | |
355 | return os; | |
356 | } | |
357 | ||
358 | } // namespace rbd | |
359 |