]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | /* | |
4 | * Ceph - scalable distributed file system | |
5 | * | |
6 | * Copyright (C) 2017 SUSE LINUX GmbH | |
7 | * | |
8 | * This is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU Lesser General Public | |
10 | * License version 2.1, as published by the Free Software | |
11 | * Foundation. See file COPYING. | |
12 | * | |
13 | */ | |
14 | ||
15 | #include "tools/rbd/ArgumentTypes.h" | |
16 | #include "tools/rbd/Shell.h" | |
17 | #include "tools/rbd/Utils.h" | |
18 | #include "common/errno.h" | |
19 | #include "include/stringify.h" | |
20 | #include "common/Formatter.h" | |
21 | #include "common/TextTable.h" | |
22 | #include "common/Clock.h" | |
23 | #include <iostream> | |
24 | #include <sstream> | |
25 | #include <boost/program_options.hpp> | |
11fdf7f2 | 26 | #include <boost/bind.hpp> |
7c673cae FG |
27 | |
28 | namespace rbd { | |
29 | namespace action { | |
30 | namespace trash { | |
31 | ||
32 | namespace at = argument_types; | |
33 | namespace po = boost::program_options; | |
34 | ||
11fdf7f2 TL |
35 | //Optional arguments used only by this set of commands (rbd trash *) |
36 | static const std::string EXPIRES_AT("expires-at"); | |
37 | static const std::string EXPIRED_BEFORE("expired-before"); | |
38 | static const std::string THRESHOLD("threshold"); | |
39 | ||
40 | static bool is_not_trash_user(const librbd::trash_image_info_t &trash_info) { | |
9f95a23c TL |
41 | return trash_info.source != RBD_TRASH_IMAGE_SOURCE_USER && |
42 | trash_info.source != RBD_TRASH_IMAGE_SOURCE_USER_PARENT; | |
11fdf7f2 | 43 | } |
7c673cae FG |
44 | |
45 | void get_move_arguments(po::options_description *positional, | |
46 | po::options_description *options) { | |
47 | at::add_image_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE); | |
48 | options->add_options() | |
11fdf7f2 TL |
49 | (EXPIRES_AT.c_str(), po::value<std::string>()->default_value("now"), |
50 | "set the expiration time of an image so it can be purged when it is stale"); | |
7c673cae FG |
51 | } |
52 | ||
11fdf7f2 TL |
53 | int execute_move(const po::variables_map &vm, |
54 | const std::vector<std::string> &ceph_global_init_args) { | |
7c673cae FG |
55 | size_t arg_index = 0; |
56 | std::string pool_name; | |
11fdf7f2 | 57 | std::string namespace_name; |
7c673cae FG |
58 | std::string image_name; |
59 | std::string snap_name; | |
60 | ||
61 | int r = utils::get_pool_image_snapshot_names( | |
11fdf7f2 TL |
62 | vm, at::ARGUMENT_MODIFIER_NONE, &arg_index, &pool_name, &namespace_name, |
63 | &image_name, &snap_name, true, utils::SNAPSHOT_PRESENCE_NONE, | |
64 | utils::SPEC_VALIDATION_NONE); | |
7c673cae FG |
65 | if (r < 0) { |
66 | return r; | |
67 | } | |
68 | ||
69 | librados::Rados rados; | |
70 | librados::IoCtx io_ctx; | |
11fdf7f2 | 71 | r = utils::init(pool_name, namespace_name, &rados, &io_ctx); |
7c673cae FG |
72 | if (r < 0) { |
73 | return r; | |
74 | } | |
75 | ||
11fdf7f2 TL |
76 | utime_t now = ceph_clock_now(); |
77 | utime_t exp_time = now; | |
78 | std::string expires_at; | |
79 | if (vm.find(EXPIRES_AT) != vm.end()) { | |
80 | expires_at = vm[EXPIRES_AT].as<std::string>(); | |
81 | r = utime_t::invoke_date(expires_at, &exp_time); | |
82 | if (r < 0) { | |
83 | std::cerr << "rbd: error calling /bin/date: " << cpp_strerror(r) | |
84 | << std::endl; | |
85 | return r; | |
86 | } | |
87 | } | |
88 | ||
89 | time_t dt = (exp_time - now).sec(); | |
90 | if(dt < 0) { | |
91 | std::cerr << "rbd: cannot use a date in the past as an expiration date" | |
92 | << std::endl; | |
93 | return -EINVAL; | |
7c673cae FG |
94 | } |
95 | ||
96 | librbd::RBD rbd; | |
11fdf7f2 | 97 | r = rbd.trash_move(io_ctx, image_name.c_str(), dt); |
7c673cae FG |
98 | if (r < 0) { |
99 | std::cerr << "rbd: deferred delete error: " << cpp_strerror(r) | |
100 | << std::endl; | |
101 | } | |
102 | ||
103 | return r; | |
7c673cae FG |
104 | } |
105 | ||
106 | void get_remove_arguments(po::options_description *positional, | |
107 | po::options_description *options) { | |
108 | positional->add_options() | |
11fdf7f2 | 109 | (at::IMAGE_ID.c_str(), "image id\n(example: [<pool-name>/[<namespace>/]]<image-id>)"); |
7c673cae | 110 | at::add_pool_option(options, at::ARGUMENT_MODIFIER_NONE); |
11fdf7f2 | 111 | at::add_namespace_option(options, at::ARGUMENT_MODIFIER_NONE); |
7c673cae FG |
112 | at::add_image_id_option(options); |
113 | ||
114 | at::add_no_progress_option(options); | |
115 | options->add_options() | |
116 | ("force", po::bool_switch(), "force remove of non-expired delayed images"); | |
117 | } | |
118 | ||
11fdf7f2 TL |
119 | int execute_remove(const po::variables_map &vm, |
120 | const std::vector<std::string> &ceph_global_init_args) { | |
7c673cae FG |
121 | size_t arg_index = 0; |
122 | std::string pool_name; | |
11fdf7f2 | 123 | std::string namespace_name; |
7c673cae | 124 | std::string image_id; |
11fdf7f2 TL |
125 | int r = utils::get_pool_image_id(vm, &arg_index, &pool_name, &namespace_name, |
126 | &image_id); | |
7c673cae FG |
127 | if (r < 0) { |
128 | return r; | |
129 | } | |
130 | ||
131 | librados::Rados rados; | |
132 | librados::IoCtx io_ctx; | |
11fdf7f2 | 133 | r = utils::init(pool_name, namespace_name, &rados, &io_ctx); |
7c673cae FG |
134 | if (r < 0) { |
135 | return r; | |
136 | } | |
137 | ||
9f95a23c | 138 | io_ctx.set_pool_full_try(); |
7c673cae FG |
139 | librbd::RBD rbd; |
140 | ||
141 | utils::ProgressContext pc("Removing image", vm[at::NO_PROGRESS].as<bool>()); | |
142 | r = rbd.trash_remove_with_progress(io_ctx, image_id.c_str(), | |
143 | vm["force"].as<bool>(), pc); | |
144 | if (r < 0) { | |
145 | if (r == -ENOTEMPTY) { | |
146 | std::cerr << "rbd: image has snapshots - these must be deleted" | |
147 | << " with 'rbd snap purge' before the image can be removed." | |
148 | << std::endl; | |
149 | } else if (r == -EBUSY) { | |
150 | std::cerr << "rbd: error: image still has watchers" | |
151 | << std::endl | |
152 | << "This means the image is still open or the client using " | |
153 | << "it crashed. Try again after closing/unmapping it or " | |
154 | << "waiting 30s for the crashed client to timeout." | |
155 | << std::endl; | |
156 | } else if (r == -EMLINK) { | |
157 | std::cerr << std::endl | |
11fdf7f2 TL |
158 | << "Remove the image from the group and try again." |
159 | << std::endl; | |
7c673cae FG |
160 | } else if (r == -EPERM) { |
161 | std::cerr << std::endl | |
162 | << "Deferment time has not expired, please use --force if you " | |
163 | << "really want to remove the image" | |
164 | << std::endl; | |
165 | } else { | |
166 | std::cerr << "rbd: remove error: " << cpp_strerror(r) << std::endl; | |
167 | } | |
168 | pc.fail(); | |
169 | return r; | |
170 | } | |
171 | ||
172 | pc.finish(); | |
173 | ||
174 | return r; | |
175 | } | |
176 | ||
177 | std::string delete_status(time_t deferment_end_time) { | |
11fdf7f2 | 178 | time_t now = time(nullptr); |
7c673cae FG |
179 | |
180 | std::string time_str = ctime(&deferment_end_time); | |
181 | time_str = time_str.substr(0, time_str.length() - 1); | |
182 | ||
183 | std::stringstream ss; | |
184 | if (now < deferment_end_time) { | |
185 | ss << "protected until " << time_str; | |
11fdf7f2 TL |
186 | } else { |
187 | ss << "expired at " << time_str; | |
7c673cae FG |
188 | } |
189 | ||
190 | return ss.str(); | |
191 | } | |
192 | ||
193 | int do_list(librbd::RBD &rbd, librados::IoCtx& io_ctx, bool long_flag, | |
194 | bool all_flag, Formatter *f) { | |
195 | std::vector<librbd::trash_image_info_t> trash_entries; | |
196 | int r = rbd.trash_list(io_ctx, trash_entries); | |
31f18b77 | 197 | if (r < 0) { |
7c673cae FG |
198 | return r; |
199 | } | |
7c673cae | 200 | |
11fdf7f2 TL |
201 | if (!all_flag) { |
202 | trash_entries.erase(remove_if(trash_entries.begin(), | |
203 | trash_entries.end(), | |
204 | boost::bind(is_not_trash_user, _1)), | |
205 | trash_entries.end()); | |
206 | } | |
207 | ||
7c673cae FG |
208 | if (!long_flag) { |
209 | if (f) { | |
210 | f->open_array_section("trash"); | |
211 | } | |
212 | for (const auto& entry : trash_entries) { | |
7c673cae | 213 | if (f) { |
11fdf7f2 | 214 | f->open_object_section("image"); |
7c673cae FG |
215 | f->dump_string("id", entry.id); |
216 | f->dump_string("name", entry.name); | |
11fdf7f2 | 217 | f->close_section(); |
7c673cae FG |
218 | } else { |
219 | std::cout << entry.id << " " << entry.name << std::endl; | |
220 | } | |
221 | } | |
222 | if (f) { | |
223 | f->close_section(); | |
224 | f->flush(std::cout); | |
225 | } | |
226 | return 0; | |
227 | } | |
228 | ||
229 | TextTable tbl; | |
230 | ||
231 | if (f) { | |
232 | f->open_array_section("trash"); | |
233 | } else { | |
234 | tbl.define_column("ID", TextTable::LEFT, TextTable::LEFT); | |
235 | tbl.define_column("NAME", TextTable::LEFT, TextTable::LEFT); | |
236 | tbl.define_column("SOURCE", TextTable::LEFT, TextTable::LEFT); | |
237 | tbl.define_column("DELETED_AT", TextTable::LEFT, TextTable::LEFT); | |
238 | tbl.define_column("STATUS", TextTable::LEFT, TextTable::LEFT); | |
11fdf7f2 | 239 | tbl.define_column("PARENT", TextTable::LEFT, TextTable::LEFT); |
7c673cae FG |
240 | } |
241 | ||
242 | for (const auto& entry : trash_entries) { | |
7c673cae FG |
243 | librbd::Image im; |
244 | ||
245 | r = rbd.open_by_id_read_only(io_ctx, im, entry.id.c_str(), NULL); | |
246 | // image might disappear between rbd.list() and rbd.open(); ignore | |
247 | // that, warn about other possible errors (EPERM, say, for opening | |
248 | // an old-format image, because you need execute permission for the | |
249 | // class method) | |
250 | if (r < 0) { | |
251 | if (r != -ENOENT) { | |
252 | std::cerr << "rbd: error opening " << entry.id << ": " | |
253 | << cpp_strerror(r) << std::endl; | |
254 | } | |
255 | // in any event, continue to next image | |
256 | continue; | |
257 | } | |
258 | ||
259 | std::string del_source; | |
260 | switch (entry.source) { | |
261 | case RBD_TRASH_IMAGE_SOURCE_USER: | |
262 | del_source = "USER"; | |
263 | break; | |
264 | case RBD_TRASH_IMAGE_SOURCE_MIRRORING: | |
265 | del_source = "MIRRORING"; | |
266 | break; | |
11fdf7f2 TL |
267 | case RBD_TRASH_IMAGE_SOURCE_MIGRATION: |
268 | del_source = "MIGRATION"; | |
269 | break; | |
270 | case RBD_TRASH_IMAGE_SOURCE_REMOVING: | |
271 | del_source = "REMOVING"; | |
272 | break; | |
9f95a23c TL |
273 | case RBD_TRASH_IMAGE_SOURCE_USER_PARENT: |
274 | del_source = "USER_PARENT"; | |
275 | break; | |
7c673cae FG |
276 | } |
277 | ||
278 | std::string time_str = ctime(&entry.deletion_time); | |
279 | time_str = time_str.substr(0, time_str.length() - 1); | |
280 | ||
11fdf7f2 TL |
281 | bool has_parent = false; |
282 | std::string parent; | |
283 | librbd::linked_image_spec_t parent_image; | |
284 | librbd::snap_spec_t parent_snap; | |
285 | r = im.get_parent(&parent_image, &parent_snap); | |
286 | if (r == -ENOENT) { | |
287 | r = 0; | |
288 | } else if (r < 0) { | |
289 | return r; | |
290 | } else { | |
291 | parent = parent_image.pool_name + "/"; | |
292 | if (!parent_image.pool_namespace.empty()) { | |
293 | parent += parent_image.pool_namespace + "/"; | |
294 | } | |
295 | parent += parent_image.image_name + "@" + parent_snap.name; | |
296 | has_parent = true; | |
297 | } | |
298 | ||
7c673cae FG |
299 | if (f) { |
300 | f->open_object_section("image"); | |
301 | f->dump_string("id", entry.id); | |
302 | f->dump_string("name", entry.name); | |
303 | f->dump_string("source", del_source); | |
304 | f->dump_string("deleted_at", time_str); | |
305 | f->dump_string("status", | |
306 | delete_status(entry.deferment_end_time)); | |
11fdf7f2 TL |
307 | if (has_parent) { |
308 | f->open_object_section("parent"); | |
309 | f->dump_string("pool", parent_image.pool_name); | |
310 | f->dump_string("pool_namespace", parent_image.pool_namespace); | |
311 | f->dump_string("image", parent_image.image_name); | |
312 | f->dump_string("snapshot", parent_snap.name); | |
313 | f->close_section(); | |
314 | } | |
7c673cae FG |
315 | f->close_section(); |
316 | } else { | |
317 | tbl << entry.id | |
318 | << entry.name | |
319 | << del_source | |
320 | << time_str | |
11fdf7f2 TL |
321 | << delete_status(entry.deferment_end_time); |
322 | if (has_parent) | |
323 | tbl << parent; | |
324 | tbl << TextTable::endrow; | |
7c673cae FG |
325 | } |
326 | } | |
327 | ||
328 | if (f) { | |
329 | f->close_section(); | |
330 | f->flush(std::cout); | |
331 | } else if (!trash_entries.empty()) { | |
332 | std::cout << tbl; | |
333 | } | |
334 | ||
335 | return r < 0 ? r : 0; | |
336 | } | |
337 | ||
338 | void get_list_arguments(po::options_description *positional, | |
339 | po::options_description *options) { | |
11fdf7f2 | 340 | at::add_pool_options(positional, options, true); |
7c673cae FG |
341 | options->add_options() |
342 | ("all,a", po::bool_switch(), "list images from all sources"); | |
343 | options->add_options() | |
344 | ("long,l", po::bool_switch(), "long listing format"); | |
345 | at::add_format_options(options); | |
346 | } | |
347 | ||
11fdf7f2 TL |
348 | int execute_list(const po::variables_map &vm, |
349 | const std::vector<std::string> &ceph_global_init_args) { | |
350 | std::string pool_name; | |
351 | std::string namespace_name; | |
7c673cae | 352 | size_t arg_index = 0; |
11fdf7f2 TL |
353 | int r = utils::get_pool_and_namespace_names(vm, true, false, &pool_name, |
354 | &namespace_name, &arg_index); | |
355 | if (r < 0) { | |
356 | return r; | |
357 | } | |
7c673cae FG |
358 | |
359 | at::Format::Formatter formatter; | |
11fdf7f2 | 360 | r = utils::get_formatter(vm, &formatter); |
7c673cae FG |
361 | if (r < 0) { |
362 | return r; | |
363 | } | |
364 | ||
365 | librados::Rados rados; | |
366 | librados::IoCtx io_ctx; | |
11fdf7f2 | 367 | r = utils::init(pool_name, namespace_name, &rados, &io_ctx); |
7c673cae FG |
368 | if (r < 0) { |
369 | return r; | |
370 | } | |
371 | ||
11fdf7f2 TL |
372 | utils::disable_cache(); |
373 | ||
7c673cae FG |
374 | librbd::RBD rbd; |
375 | r = do_list(rbd, io_ctx, vm["long"].as<bool>(), vm["all"].as<bool>(), | |
376 | formatter.get()); | |
377 | if (r < 0) { | |
378 | std::cerr << "rbd: trash list: " << cpp_strerror(r) << std::endl; | |
379 | return r; | |
380 | } | |
381 | ||
382 | return 0; | |
383 | } | |
384 | ||
11fdf7f2 TL |
385 | void get_purge_arguments(po::options_description *positional, |
386 | po::options_description *options) { | |
387 | at::add_pool_options(positional, options, true); | |
388 | at::add_no_progress_option(options); | |
389 | ||
390 | options->add_options() | |
391 | (EXPIRED_BEFORE.c_str(), po::value<std::string>()->value_name("date"), | |
392 | "purges images that expired before the given date"); | |
393 | options->add_options() | |
394 | (THRESHOLD.c_str(), po::value<float>(), | |
395 | "purges images until the current pool data usage is reduced to X%, " | |
396 | "value range: 0.0-1.0"); | |
397 | } | |
398 | ||
399 | int execute_purge (const po::variables_map &vm, | |
400 | const std::vector<std::string> &ceph_global_init_args) { | |
401 | std::string pool_name; | |
402 | std::string namespace_name; | |
403 | size_t arg_index = 0; | |
404 | int r = utils::get_pool_and_namespace_names(vm, true, false, &pool_name, | |
405 | &namespace_name, &arg_index); | |
406 | if (r < 0) { | |
407 | return r; | |
408 | } | |
409 | ||
410 | utils::disable_cache(); | |
411 | ||
412 | librbd::RBD rbd; | |
413 | ||
414 | librados::Rados rados; | |
415 | librados::IoCtx io_ctx; | |
416 | r = utils::init(pool_name, namespace_name, &rados, &io_ctx); | |
417 | if (r < 0) { | |
418 | return r; | |
419 | } | |
420 | ||
9f95a23c | 421 | io_ctx.set_pool_full_try(); |
11fdf7f2 TL |
422 | |
423 | float threshold = -1; | |
424 | time_t expire_ts = 0; | |
425 | ||
426 | if (vm.find(THRESHOLD) != vm.end()) { | |
427 | threshold = vm[THRESHOLD].as<float>(); | |
428 | } else { | |
429 | if (vm.find(EXPIRED_BEFORE) != vm.end()) { | |
430 | utime_t new_time; | |
431 | r = utime_t::invoke_date(vm[EXPIRED_BEFORE].as<std::string>(), &new_time); | |
432 | if (r < 0) { | |
433 | std::cerr << "rbd: error calling /bin/date: " << cpp_strerror(r) | |
434 | << std::endl; | |
435 | return r; | |
436 | } | |
437 | expire_ts = new_time.sec(); | |
438 | } | |
439 | } | |
440 | ||
441 | utils::ProgressContext pc("Removing images", vm[at::NO_PROGRESS].as<bool>()); | |
442 | r = rbd.trash_purge_with_progress(io_ctx, expire_ts, threshold, pc); | |
443 | if (r < 0) { | |
444 | pc.fail(); | |
445 | } else { | |
446 | pc.finish(); | |
447 | } | |
448 | ||
449 | return 0; | |
450 | } | |
451 | ||
7c673cae FG |
452 | void get_restore_arguments(po::options_description *positional, |
453 | po::options_description *options) { | |
454 | positional->add_options() | |
455 | (at::IMAGE_ID.c_str(), "image id\n(example: [<pool-name>/]<image-id>)"); | |
456 | at::add_pool_option(options, at::ARGUMENT_MODIFIER_NONE); | |
11fdf7f2 | 457 | at::add_namespace_option(options, at::ARGUMENT_MODIFIER_NONE); |
7c673cae FG |
458 | at::add_image_id_option(options); |
459 | at::add_image_option(options, at::ARGUMENT_MODIFIER_NONE, ""); | |
460 | } | |
461 | ||
11fdf7f2 TL |
462 | int execute_restore(const po::variables_map &vm, |
463 | const std::vector<std::string> &ceph_global_init_args) { | |
7c673cae FG |
464 | size_t arg_index = 0; |
465 | std::string pool_name; | |
11fdf7f2 | 466 | std::string namespace_name; |
7c673cae | 467 | std::string image_id; |
11fdf7f2 TL |
468 | int r = utils::get_pool_image_id(vm, &arg_index, &pool_name, &namespace_name, |
469 | &image_id); | |
7c673cae FG |
470 | if (r < 0) { |
471 | return r; | |
472 | } | |
473 | ||
474 | librados::Rados rados; | |
475 | librados::IoCtx io_ctx; | |
11fdf7f2 | 476 | r = utils::init(pool_name, namespace_name, &rados, &io_ctx); |
7c673cae FG |
477 | if (r < 0) { |
478 | return r; | |
479 | } | |
480 | ||
481 | std::string name; | |
482 | if (vm.find(at::IMAGE_NAME) != vm.end()) { | |
483 | name = vm[at::IMAGE_NAME].as<std::string>(); | |
484 | } | |
485 | ||
486 | librbd::RBD rbd; | |
487 | r = rbd.trash_restore(io_ctx, image_id.c_str(), name.c_str()); | |
488 | if (r < 0) { | |
489 | if (r == -ENOENT) { | |
490 | std::cerr << "rbd: error: image does not exist in trash" | |
491 | << std::endl; | |
492 | } else if (r == -EEXIST) { | |
493 | std::cerr << "rbd: error: an image with the same name already exists, " | |
9f95a23c | 494 | << "try again with a different name" |
7c673cae FG |
495 | << std::endl; |
496 | } else { | |
497 | std::cerr << "rbd: restore error: " << cpp_strerror(r) << std::endl; | |
498 | } | |
499 | return r; | |
500 | } | |
501 | ||
502 | return r; | |
503 | } | |
504 | ||
505 | ||
506 | Shell::Action action_move( | |
11fdf7f2 TL |
507 | {"trash", "move"}, {"trash", "mv"}, "Move an image to the trash.", "", |
508 | &get_move_arguments, &execute_move); | |
7c673cae FG |
509 | |
510 | Shell::Action action_remove( | |
c07f9fc5 | 511 | {"trash", "remove"}, {"trash", "rm"}, "Remove an image from trash.", "", |
7c673cae FG |
512 | &get_remove_arguments, &execute_remove); |
513 | ||
11fdf7f2 TL |
514 | Shell::Action action_purge( |
515 | {"trash", "purge"}, {}, "Remove all expired images from trash.", "", | |
516 | &get_purge_arguments, &execute_purge); | |
517 | ||
7c673cae FG |
518 | Shell::SwitchArguments switched_arguments({"long", "l"}); |
519 | Shell::Action action_list( | |
520 | {"trash", "list"}, {"trash", "ls"}, "List trash images.", "", | |
521 | &get_list_arguments, &execute_list); | |
522 | ||
523 | Shell::Action action_restore( | |
11fdf7f2 TL |
524 | {"trash", "restore"}, {}, "Restore an image from trash.", "", |
525 | &get_restore_arguments, &execute_restore); | |
7c673cae FG |
526 | |
527 | } // namespace trash | |
528 | } // namespace action | |
529 | } // namespace rbd |