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