]>
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> | |
26 | ||
27 | namespace rbd { | |
28 | namespace action { | |
29 | namespace trash { | |
30 | ||
31 | namespace at = argument_types; | |
32 | namespace po = boost::program_options; | |
33 | ||
34 | ||
35 | void get_move_arguments(po::options_description *positional, | |
36 | po::options_description *options) { | |
37 | at::add_image_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE); | |
38 | options->add_options() | |
39 | (at::DELAY.c_str(), po::value<uint64_t>(), | |
40 | "time delay in seconds until effectively remove the image"); | |
41 | } | |
42 | ||
43 | int execute_move(const po::variables_map &vm) { | |
44 | size_t arg_index = 0; | |
45 | std::string pool_name; | |
46 | std::string image_name; | |
47 | std::string snap_name; | |
48 | ||
49 | int r = utils::get_pool_image_snapshot_names( | |
50 | vm, at::ARGUMENT_MODIFIER_NONE, &arg_index, &pool_name, &image_name, | |
51 | &snap_name, utils::SNAPSHOT_PRESENCE_NONE, utils::SPEC_VALIDATION_NONE); | |
52 | if (r < 0) { | |
53 | return r; | |
54 | } | |
55 | ||
56 | librados::Rados rados; | |
57 | librados::IoCtx io_ctx; | |
58 | r = utils::init(pool_name, &rados, &io_ctx); | |
59 | if (r < 0) { | |
60 | return r; | |
61 | } | |
62 | ||
63 | uint64_t delay = 0; | |
64 | if (vm.find(at::DELAY) != vm.end()) { | |
65 | delay = vm[at::DELAY].as<uint64_t>(); | |
66 | } | |
67 | ||
68 | librbd::RBD rbd; | |
69 | r = rbd.trash_move(io_ctx, image_name.c_str(), delay); | |
70 | if (r < 0) { | |
71 | std::cerr << "rbd: deferred delete error: " << cpp_strerror(r) | |
72 | << std::endl; | |
73 | } | |
74 | ||
75 | return r; | |
76 | ||
77 | } | |
78 | ||
79 | void get_remove_arguments(po::options_description *positional, | |
80 | po::options_description *options) { | |
81 | positional->add_options() | |
82 | (at::IMAGE_ID.c_str(), "image id\n(example: [<pool-name>/]<image-id>)"); | |
83 | at::add_pool_option(options, at::ARGUMENT_MODIFIER_NONE); | |
84 | at::add_image_id_option(options); | |
85 | ||
86 | at::add_no_progress_option(options); | |
87 | options->add_options() | |
88 | ("force", po::bool_switch(), "force remove of non-expired delayed images"); | |
89 | } | |
90 | ||
91 | int execute_remove(const po::variables_map &vm) { | |
92 | size_t arg_index = 0; | |
93 | std::string pool_name; | |
94 | std::string image_id; | |
95 | int r = utils::get_pool_image_id(vm, &arg_index, &pool_name, &image_id); | |
96 | if (r < 0) { | |
97 | return r; | |
98 | } | |
99 | ||
100 | librados::Rados rados; | |
101 | librados::IoCtx io_ctx; | |
102 | r = utils::init(pool_name, &rados, &io_ctx); | |
103 | if (r < 0) { | |
104 | return r; | |
105 | } | |
106 | ||
107 | librbd::RBD rbd; | |
108 | ||
109 | utils::ProgressContext pc("Removing image", vm[at::NO_PROGRESS].as<bool>()); | |
110 | r = rbd.trash_remove_with_progress(io_ctx, image_id.c_str(), | |
111 | vm["force"].as<bool>(), pc); | |
112 | if (r < 0) { | |
113 | if (r == -ENOTEMPTY) { | |
114 | std::cerr << "rbd: image has snapshots - these must be deleted" | |
115 | << " with 'rbd snap purge' before the image can be removed." | |
116 | << std::endl; | |
117 | } else if (r == -EBUSY) { | |
118 | std::cerr << "rbd: error: image still has watchers" | |
119 | << std::endl | |
120 | << "This means the image is still open or the client using " | |
121 | << "it crashed. Try again after closing/unmapping it or " | |
122 | << "waiting 30s for the crashed client to timeout." | |
123 | << std::endl; | |
124 | } else if (r == -EMLINK) { | |
125 | std::cerr << std::endl | |
126 | << "Remove the image from the consistency group and try again." | |
127 | << std::endl; | |
128 | } else if (r == -EPERM) { | |
129 | std::cerr << std::endl | |
130 | << "Deferment time has not expired, please use --force if you " | |
131 | << "really want to remove the image" | |
132 | << std::endl; | |
133 | } else { | |
134 | std::cerr << "rbd: remove error: " << cpp_strerror(r) << std::endl; | |
135 | } | |
136 | pc.fail(); | |
137 | return r; | |
138 | } | |
139 | ||
140 | pc.finish(); | |
141 | ||
142 | return r; | |
143 | } | |
144 | ||
145 | std::string delete_status(time_t deferment_end_time) { | |
146 | time_t now = ceph_clock_gettime(); | |
147 | ||
148 | std::string time_str = ctime(&deferment_end_time); | |
149 | time_str = time_str.substr(0, time_str.length() - 1); | |
150 | ||
151 | std::stringstream ss; | |
152 | if (now < deferment_end_time) { | |
153 | ss << "protected until " << time_str; | |
154 | } | |
155 | ||
156 | return ss.str(); | |
157 | } | |
158 | ||
159 | int do_list(librbd::RBD &rbd, librados::IoCtx& io_ctx, bool long_flag, | |
160 | bool all_flag, Formatter *f) { | |
161 | std::vector<librbd::trash_image_info_t> trash_entries; | |
162 | int r = rbd.trash_list(io_ctx, trash_entries); | |
31f18b77 | 163 | if (r < 0) { |
7c673cae FG |
164 | return r; |
165 | } | |
7c673cae FG |
166 | |
167 | if (!long_flag) { | |
168 | if (f) { | |
169 | f->open_array_section("trash"); | |
170 | } | |
171 | for (const auto& entry : trash_entries) { | |
172 | if (!all_flag && | |
173 | entry.source == RBD_TRASH_IMAGE_SOURCE_MIRRORING) { | |
174 | continue; | |
175 | } | |
176 | if (f) { | |
177 | f->dump_string("id", entry.id); | |
178 | f->dump_string("name", entry.name); | |
179 | } else { | |
180 | std::cout << entry.id << " " << entry.name << std::endl; | |
181 | } | |
182 | } | |
183 | if (f) { | |
184 | f->close_section(); | |
185 | f->flush(std::cout); | |
186 | } | |
187 | return 0; | |
188 | } | |
189 | ||
190 | TextTable tbl; | |
191 | ||
192 | if (f) { | |
193 | f->open_array_section("trash"); | |
194 | } else { | |
195 | tbl.define_column("ID", TextTable::LEFT, TextTable::LEFT); | |
196 | tbl.define_column("NAME", TextTable::LEFT, TextTable::LEFT); | |
197 | tbl.define_column("SOURCE", TextTable::LEFT, TextTable::LEFT); | |
198 | tbl.define_column("DELETED_AT", TextTable::LEFT, TextTable::LEFT); | |
199 | tbl.define_column("STATUS", TextTable::LEFT, TextTable::LEFT); | |
200 | } | |
201 | ||
202 | for (const auto& entry : trash_entries) { | |
203 | if (!all_flag && | |
204 | entry.source == RBD_TRASH_IMAGE_SOURCE_MIRRORING) { | |
205 | continue; | |
206 | } | |
207 | librbd::Image im; | |
208 | ||
209 | r = rbd.open_by_id_read_only(io_ctx, im, entry.id.c_str(), NULL); | |
210 | // image might disappear between rbd.list() and rbd.open(); ignore | |
211 | // that, warn about other possible errors (EPERM, say, for opening | |
212 | // an old-format image, because you need execute permission for the | |
213 | // class method) | |
214 | if (r < 0) { | |
215 | if (r != -ENOENT) { | |
216 | std::cerr << "rbd: error opening " << entry.id << ": " | |
217 | << cpp_strerror(r) << std::endl; | |
218 | } | |
219 | // in any event, continue to next image | |
220 | continue; | |
221 | } | |
222 | ||
223 | std::string del_source; | |
224 | switch (entry.source) { | |
225 | case RBD_TRASH_IMAGE_SOURCE_USER: | |
226 | del_source = "USER"; | |
227 | break; | |
228 | case RBD_TRASH_IMAGE_SOURCE_MIRRORING: | |
229 | del_source = "MIRRORING"; | |
230 | break; | |
231 | } | |
232 | ||
233 | std::string time_str = ctime(&entry.deletion_time); | |
234 | time_str = time_str.substr(0, time_str.length() - 1); | |
235 | ||
236 | if (f) { | |
237 | f->open_object_section("image"); | |
238 | f->dump_string("id", entry.id); | |
239 | f->dump_string("name", entry.name); | |
240 | f->dump_string("source", del_source); | |
241 | f->dump_string("deleted_at", time_str); | |
242 | f->dump_string("status", | |
243 | delete_status(entry.deferment_end_time)); | |
244 | f->close_section(); | |
245 | } else { | |
246 | tbl << entry.id | |
247 | << entry.name | |
248 | << del_source | |
249 | << time_str | |
250 | << delete_status(entry.deferment_end_time) | |
251 | << TextTable::endrow; | |
252 | } | |
253 | } | |
254 | ||
255 | if (f) { | |
256 | f->close_section(); | |
257 | f->flush(std::cout); | |
258 | } else if (!trash_entries.empty()) { | |
259 | std::cout << tbl; | |
260 | } | |
261 | ||
262 | return r < 0 ? r : 0; | |
263 | } | |
264 | ||
265 | void get_list_arguments(po::options_description *positional, | |
266 | po::options_description *options) { | |
267 | at::add_pool_options(positional, options); | |
268 | options->add_options() | |
269 | ("all,a", po::bool_switch(), "list images from all sources"); | |
270 | options->add_options() | |
271 | ("long,l", po::bool_switch(), "long listing format"); | |
272 | at::add_format_options(options); | |
273 | } | |
274 | ||
275 | int execute_list(const po::variables_map &vm) { | |
276 | size_t arg_index = 0; | |
277 | std::string pool_name = utils::get_pool_name(vm, &arg_index); | |
278 | ||
279 | at::Format::Formatter formatter; | |
280 | int r = utils::get_formatter(vm, &formatter); | |
281 | if (r < 0) { | |
282 | return r; | |
283 | } | |
284 | ||
285 | librados::Rados rados; | |
286 | librados::IoCtx io_ctx; | |
287 | r = utils::init(pool_name, &rados, &io_ctx); | |
288 | if (r < 0) { | |
289 | return r; | |
290 | } | |
291 | ||
292 | librbd::RBD rbd; | |
293 | r = do_list(rbd, io_ctx, vm["long"].as<bool>(), vm["all"].as<bool>(), | |
294 | formatter.get()); | |
295 | if (r < 0) { | |
296 | std::cerr << "rbd: trash list: " << cpp_strerror(r) << std::endl; | |
297 | return r; | |
298 | } | |
299 | ||
300 | return 0; | |
301 | } | |
302 | ||
303 | void get_restore_arguments(po::options_description *positional, | |
304 | po::options_description *options) { | |
305 | positional->add_options() | |
306 | (at::IMAGE_ID.c_str(), "image id\n(example: [<pool-name>/]<image-id>)"); | |
307 | at::add_pool_option(options, at::ARGUMENT_MODIFIER_NONE); | |
308 | at::add_image_id_option(options); | |
309 | at::add_image_option(options, at::ARGUMENT_MODIFIER_NONE, ""); | |
310 | } | |
311 | ||
312 | int execute_restore(const po::variables_map &vm) { | |
313 | size_t arg_index = 0; | |
314 | std::string pool_name; | |
315 | std::string image_id; | |
316 | int r = utils::get_pool_image_id(vm, &arg_index, &pool_name, &image_id); | |
317 | if (r < 0) { | |
318 | return r; | |
319 | } | |
320 | ||
321 | librados::Rados rados; | |
322 | librados::IoCtx io_ctx; | |
323 | r = utils::init(pool_name, &rados, &io_ctx); | |
324 | if (r < 0) { | |
325 | return r; | |
326 | } | |
327 | ||
328 | std::string name; | |
329 | if (vm.find(at::IMAGE_NAME) != vm.end()) { | |
330 | name = vm[at::IMAGE_NAME].as<std::string>(); | |
331 | } | |
332 | ||
333 | librbd::RBD rbd; | |
334 | r = rbd.trash_restore(io_ctx, image_id.c_str(), name.c_str()); | |
335 | if (r < 0) { | |
336 | if (r == -ENOENT) { | |
337 | std::cerr << "rbd: error: image does not exist in trash" | |
338 | << std::endl; | |
339 | } else if (r == -EEXIST) { | |
340 | std::cerr << "rbd: error: an image with the same name already exists, " | |
341 | << "try again with with a different name" | |
342 | << std::endl; | |
343 | } else { | |
344 | std::cerr << "rbd: restore error: " << cpp_strerror(r) << std::endl; | |
345 | } | |
346 | return r; | |
347 | } | |
348 | ||
349 | return r; | |
350 | } | |
351 | ||
352 | ||
353 | Shell::Action action_move( | |
c07f9fc5 | 354 | {"trash", "move"}, {"trash", "mv"}, "Move an image to the trash.", "", |
7c673cae FG |
355 | &get_move_arguments, &execute_move); |
356 | ||
357 | Shell::Action action_remove( | |
c07f9fc5 | 358 | {"trash", "remove"}, {"trash", "rm"}, "Remove an image from trash.", "", |
7c673cae FG |
359 | &get_remove_arguments, &execute_remove); |
360 | ||
361 | Shell::SwitchArguments switched_arguments({"long", "l"}); | |
362 | Shell::Action action_list( | |
363 | {"trash", "list"}, {"trash", "ls"}, "List trash images.", "", | |
364 | &get_list_arguments, &execute_list); | |
365 | ||
366 | Shell::Action action_restore( | |
c07f9fc5 | 367 | {"trash", "restore"}, {}, "Restore an image from trash.", "", |
7c673cae FG |
368 | &get_restore_arguments, &execute_restore); |
369 | ||
370 | } // namespace trash | |
371 | } // namespace action | |
372 | } // namespace rbd |