]> git.proxmox.com Git - ceph.git/blame - ceph/src/tools/rbd/action/Trash.cc
import 15.2.0 Octopus source
[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>
11fdf7f2 26#include <boost/bind.hpp>
7c673cae
FG
27
28namespace rbd {
29namespace action {
30namespace trash {
31
32namespace at = argument_types;
33namespace po = boost::program_options;
34
11fdf7f2
TL
35//Optional arguments used only by this set of commands (rbd trash *)
36static const std::string EXPIRES_AT("expires-at");
37static const std::string EXPIRED_BEFORE("expired-before");
38static const std::string THRESHOLD("threshold");
39
40static 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
45void 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
53int 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
106void 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
119int 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
177std::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
193int 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
338void 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
348int 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
385void 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
399int 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
452void 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
462int 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
506Shell::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
510Shell::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
514Shell::Action action_purge(
515 {"trash", "purge"}, {}, "Remove all expired images from trash.", "",
516 &get_purge_arguments, &execute_purge);
517
7c673cae
FG
518Shell::SwitchArguments switched_arguments({"long", "l"});
519Shell::Action action_list(
520 {"trash", "list"}, {"trash", "ls"}, "List trash images.", "",
521 &get_list_arguments, &execute_list);
522
523Shell::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