]>
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> | |
f67539c2 | 26 | #include <boost/bind/bind.hpp> |
7c673cae FG |
27 | |
28 | namespace rbd { | |
29 | namespace action { | |
30 | namespace trash { | |
f67539c2 | 31 | using namespace boost::placeholders; |
7c673cae FG |
32 | |
33 | namespace at = argument_types; | |
34 | namespace po = boost::program_options; | |
35 | ||
11fdf7f2 TL |
36 | //Optional arguments used only by this set of commands (rbd trash *) |
37 | static const std::string EXPIRES_AT("expires-at"); | |
38 | static const std::string EXPIRED_BEFORE("expired-before"); | |
39 | static const std::string THRESHOLD("threshold"); | |
40 | ||
41 | static 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 | |
46 | void 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 |
54 | int 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 | ||
110 | void 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 |
123 | int 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 | ||
184 | std::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 | ||
200 | int 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 | ||
345 | void 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 |
355 | int 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 |
392 | void 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 |
406 | int 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 |
468 | void 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 |
478 | int 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 | 521 | Shell::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 | |
525 | Shell::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 |
529 | Shell::Action action_purge( |
530 | {"trash", "purge"}, {}, "Remove all expired images from trash.", "", | |
531 | &get_purge_arguments, &execute_purge); | |
532 | ||
7c673cae FG |
533 | Shell::Action action_list( |
534 | {"trash", "list"}, {"trash", "ls"}, "List trash images.", "", | |
535 | &get_list_arguments, &execute_list); | |
536 | ||
537 | Shell::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 |