]>
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); | |
163 | if (r < 0 && r != -ENOENT) { | |
164 | return r; | |
165 | } | |
166 | r = 0; | |
167 | ||
168 | if (!long_flag) { | |
169 | if (f) { | |
170 | f->open_array_section("trash"); | |
171 | } | |
172 | for (const auto& entry : trash_entries) { | |
173 | if (!all_flag && | |
174 | entry.source == RBD_TRASH_IMAGE_SOURCE_MIRRORING) { | |
175 | continue; | |
176 | } | |
177 | if (f) { | |
178 | f->dump_string("id", entry.id); | |
179 | f->dump_string("name", entry.name); | |
180 | } else { | |
181 | std::cout << entry.id << " " << entry.name << std::endl; | |
182 | } | |
183 | } | |
184 | if (f) { | |
185 | f->close_section(); | |
186 | f->flush(std::cout); | |
187 | } | |
188 | return 0; | |
189 | } | |
190 | ||
191 | TextTable tbl; | |
192 | ||
193 | if (f) { | |
194 | f->open_array_section("trash"); | |
195 | } else { | |
196 | tbl.define_column("ID", TextTable::LEFT, TextTable::LEFT); | |
197 | tbl.define_column("NAME", TextTable::LEFT, TextTable::LEFT); | |
198 | tbl.define_column("SOURCE", TextTable::LEFT, TextTable::LEFT); | |
199 | tbl.define_column("DELETED_AT", TextTable::LEFT, TextTable::LEFT); | |
200 | tbl.define_column("STATUS", TextTable::LEFT, TextTable::LEFT); | |
201 | } | |
202 | ||
203 | for (const auto& entry : trash_entries) { | |
204 | if (!all_flag && | |
205 | entry.source == RBD_TRASH_IMAGE_SOURCE_MIRRORING) { | |
206 | continue; | |
207 | } | |
208 | librbd::Image im; | |
209 | ||
210 | r = rbd.open_by_id_read_only(io_ctx, im, entry.id.c_str(), NULL); | |
211 | // image might disappear between rbd.list() and rbd.open(); ignore | |
212 | // that, warn about other possible errors (EPERM, say, for opening | |
213 | // an old-format image, because you need execute permission for the | |
214 | // class method) | |
215 | if (r < 0) { | |
216 | if (r != -ENOENT) { | |
217 | std::cerr << "rbd: error opening " << entry.id << ": " | |
218 | << cpp_strerror(r) << std::endl; | |
219 | } | |
220 | // in any event, continue to next image | |
221 | continue; | |
222 | } | |
223 | ||
224 | std::string del_source; | |
225 | switch (entry.source) { | |
226 | case RBD_TRASH_IMAGE_SOURCE_USER: | |
227 | del_source = "USER"; | |
228 | break; | |
229 | case RBD_TRASH_IMAGE_SOURCE_MIRRORING: | |
230 | del_source = "MIRRORING"; | |
231 | break; | |
232 | } | |
233 | ||
234 | std::string time_str = ctime(&entry.deletion_time); | |
235 | time_str = time_str.substr(0, time_str.length() - 1); | |
236 | ||
237 | if (f) { | |
238 | f->open_object_section("image"); | |
239 | f->dump_string("id", entry.id); | |
240 | f->dump_string("name", entry.name); | |
241 | f->dump_string("source", del_source); | |
242 | f->dump_string("deleted_at", time_str); | |
243 | f->dump_string("status", | |
244 | delete_status(entry.deferment_end_time)); | |
245 | f->close_section(); | |
246 | } else { | |
247 | tbl << entry.id | |
248 | << entry.name | |
249 | << del_source | |
250 | << time_str | |
251 | << delete_status(entry.deferment_end_time) | |
252 | << TextTable::endrow; | |
253 | } | |
254 | } | |
255 | ||
256 | if (f) { | |
257 | f->close_section(); | |
258 | f->flush(std::cout); | |
259 | } else if (!trash_entries.empty()) { | |
260 | std::cout << tbl; | |
261 | } | |
262 | ||
263 | return r < 0 ? r : 0; | |
264 | } | |
265 | ||
266 | void get_list_arguments(po::options_description *positional, | |
267 | po::options_description *options) { | |
268 | at::add_pool_options(positional, options); | |
269 | options->add_options() | |
270 | ("all,a", po::bool_switch(), "list images from all sources"); | |
271 | options->add_options() | |
272 | ("long,l", po::bool_switch(), "long listing format"); | |
273 | at::add_format_options(options); | |
274 | } | |
275 | ||
276 | int execute_list(const po::variables_map &vm) { | |
277 | size_t arg_index = 0; | |
278 | std::string pool_name = utils::get_pool_name(vm, &arg_index); | |
279 | ||
280 | at::Format::Formatter formatter; | |
281 | int r = utils::get_formatter(vm, &formatter); | |
282 | if (r < 0) { | |
283 | return r; | |
284 | } | |
285 | ||
286 | librados::Rados rados; | |
287 | librados::IoCtx io_ctx; | |
288 | r = utils::init(pool_name, &rados, &io_ctx); | |
289 | if (r < 0) { | |
290 | return r; | |
291 | } | |
292 | ||
293 | librbd::RBD rbd; | |
294 | r = do_list(rbd, io_ctx, vm["long"].as<bool>(), vm["all"].as<bool>(), | |
295 | formatter.get()); | |
296 | if (r < 0) { | |
297 | std::cerr << "rbd: trash list: " << cpp_strerror(r) << std::endl; | |
298 | return r; | |
299 | } | |
300 | ||
301 | return 0; | |
302 | } | |
303 | ||
304 | void get_restore_arguments(po::options_description *positional, | |
305 | po::options_description *options) { | |
306 | positional->add_options() | |
307 | (at::IMAGE_ID.c_str(), "image id\n(example: [<pool-name>/]<image-id>)"); | |
308 | at::add_pool_option(options, at::ARGUMENT_MODIFIER_NONE); | |
309 | at::add_image_id_option(options); | |
310 | at::add_image_option(options, at::ARGUMENT_MODIFIER_NONE, ""); | |
311 | } | |
312 | ||
313 | int execute_restore(const po::variables_map &vm) { | |
314 | size_t arg_index = 0; | |
315 | std::string pool_name; | |
316 | std::string image_id; | |
317 | int r = utils::get_pool_image_id(vm, &arg_index, &pool_name, &image_id); | |
318 | if (r < 0) { | |
319 | return r; | |
320 | } | |
321 | ||
322 | librados::Rados rados; | |
323 | librados::IoCtx io_ctx; | |
324 | r = utils::init(pool_name, &rados, &io_ctx); | |
325 | if (r < 0) { | |
326 | return r; | |
327 | } | |
328 | ||
329 | std::string name; | |
330 | if (vm.find(at::IMAGE_NAME) != vm.end()) { | |
331 | name = vm[at::IMAGE_NAME].as<std::string>(); | |
332 | } | |
333 | ||
334 | librbd::RBD rbd; | |
335 | r = rbd.trash_restore(io_ctx, image_id.c_str(), name.c_str()); | |
336 | if (r < 0) { | |
337 | if (r == -ENOENT) { | |
338 | std::cerr << "rbd: error: image does not exist in trash" | |
339 | << std::endl; | |
340 | } else if (r == -EEXIST) { | |
341 | std::cerr << "rbd: error: an image with the same name already exists, " | |
342 | << "try again with with a different name" | |
343 | << std::endl; | |
344 | } else { | |
345 | std::cerr << "rbd: restore error: " << cpp_strerror(r) << std::endl; | |
346 | } | |
347 | return r; | |
348 | } | |
349 | ||
350 | return r; | |
351 | } | |
352 | ||
353 | ||
354 | Shell::Action action_move( | |
355 | {"trash", "move"}, {"trash", "mv"}, "Moves an image to the trash.", "", | |
356 | &get_move_arguments, &execute_move); | |
357 | ||
358 | Shell::Action action_remove( | |
359 | {"trash", "remove"}, {"trash", "rm"}, "Removes an image from trash.", "", | |
360 | &get_remove_arguments, &execute_remove); | |
361 | ||
362 | Shell::SwitchArguments switched_arguments({"long", "l"}); | |
363 | Shell::Action action_list( | |
364 | {"trash", "list"}, {"trash", "ls"}, "List trash images.", "", | |
365 | &get_list_arguments, &execute_list); | |
366 | ||
367 | Shell::Action action_restore( | |
368 | {"trash", "restore"}, {}, "Restores an image from trash.", "", | |
369 | &get_restore_arguments, &execute_restore); | |
370 | ||
371 | } // namespace trash | |
372 | } // namespace action | |
373 | } // namespace rbd |