]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | ||
4 | #include "librbd/api/Trash.h" | |
5 | #include "include/rados/librados.hpp" | |
6 | #include "common/dout.h" | |
7 | #include "common/errno.h" | |
8 | #include "common/Cond.h" | |
9 | #include "cls/rbd/cls_rbd_client.h" | |
10 | #include "librbd/ExclusiveLock.h" | |
11 | #include "librbd/ImageCtx.h" | |
12 | #include "librbd/ImageState.h" | |
13 | #include "librbd/internal.h" | |
14 | #include "librbd/Operations.h" | |
15 | #include "librbd/TrashWatcher.h" | |
16 | #include "librbd/Utils.h" | |
17 | #include "librbd/api/DiffIterate.h" | |
18 | #include "librbd/image/RemoveRequest.h" | |
19 | #include "librbd/mirror/DisableRequest.h" | |
20 | #include "librbd/mirror/EnableRequest.h" | |
21 | #include "librbd/trash/MoveRequest.h" | |
22 | #include <json_spirit/json_spirit.h> | |
23 | #include "librbd/journal/DisabledPolicy.h" | |
24 | #include "librbd/image/ListWatchersRequest.h" | |
25 | ||
26 | #define dout_subsys ceph_subsys_rbd | |
27 | #undef dout_prefix | |
28 | #define dout_prefix *_dout << "librbd::api::Trash: " << __func__ << ": " | |
29 | ||
30 | namespace librbd { | |
31 | namespace api { | |
32 | ||
33 | namespace { | |
34 | ||
35 | template <typename I> | |
36 | int disable_mirroring(I *ictx) { | |
37 | if (!ictx->test_features(RBD_FEATURE_JOURNALING)) { | |
38 | return 0; | |
39 | } | |
40 | ||
41 | cls::rbd::MirrorImage mirror_image; | |
42 | int r = cls_client::mirror_image_get(&ictx->md_ctx, ictx->id, &mirror_image); | |
43 | if (r == -ENOENT) { | |
44 | ldout(ictx->cct, 10) << "mirroring is not enabled for this image" << dendl; | |
45 | return 0; | |
46 | } | |
47 | ||
48 | if (r < 0) { | |
49 | lderr(ictx->cct) << "failed to retrieve mirror image: " << cpp_strerror(r) | |
50 | << dendl; | |
51 | return r; | |
52 | } | |
53 | ||
54 | ldout(ictx->cct, 10) << dendl; | |
55 | ||
56 | C_SaferCond ctx; | |
57 | auto req = mirror::DisableRequest<I>::create(ictx, false, true, &ctx); | |
58 | req->send(); | |
59 | r = ctx.wait(); | |
60 | if (r < 0) { | |
61 | lderr(ictx->cct) << "failed to disable mirroring: " << cpp_strerror(r) | |
62 | << dendl; | |
63 | return r; | |
64 | } | |
65 | ||
66 | return 0; | |
67 | } | |
68 | ||
69 | template <typename I> | |
70 | int enable_mirroring(IoCtx &io_ctx, const std::string &image_id) { | |
71 | auto cct = reinterpret_cast<CephContext*>(io_ctx.cct()); | |
72 | ||
73 | uint64_t features; | |
74 | uint64_t incompatible_features; | |
75 | int r = cls_client::get_features(&io_ctx, util::header_name(image_id), true, | |
76 | &features, &incompatible_features); | |
77 | if (r < 0) { | |
78 | lderr(cct) << "failed to retrieve features: " << cpp_strerror(r) << dendl; | |
79 | return r; | |
80 | } | |
81 | ||
82 | if ((features & RBD_FEATURE_JOURNALING) == 0) { | |
83 | return 0; | |
84 | } | |
85 | ||
86 | cls::rbd::MirrorMode mirror_mode; | |
87 | r = cls_client::mirror_mode_get(&io_ctx, &mirror_mode); | |
88 | if (r < 0 && r != -ENOENT) { | |
89 | lderr(cct) << "failed to retrieve mirror mode: " << cpp_strerror(r) | |
90 | << dendl; | |
91 | return r; | |
92 | } | |
93 | ||
94 | if (mirror_mode != cls::rbd::MIRROR_MODE_POOL) { | |
95 | ldout(cct, 10) << "not pool mirroring mode" << dendl; | |
96 | return 0; | |
97 | } | |
98 | ||
99 | ldout(cct, 10) << dendl; | |
100 | ||
101 | ThreadPool *thread_pool; | |
102 | ContextWQ *op_work_queue; | |
103 | ImageCtx::get_thread_pool_instance(cct, &thread_pool, &op_work_queue); | |
104 | C_SaferCond ctx; | |
105 | auto req = mirror::EnableRequest<I>::create(io_ctx, image_id, "", | |
106 | op_work_queue, &ctx); | |
107 | req->send(); | |
108 | r = ctx.wait(); | |
109 | if (r < 0) { | |
110 | lderr(cct) << "failed to enable mirroring: " << cpp_strerror(r) | |
111 | << dendl; | |
112 | return r; | |
113 | } | |
114 | ||
115 | return 0; | |
116 | } | |
117 | ||
118 | } // anonymous namespace | |
119 | ||
120 | template <typename I> | |
121 | int Trash<I>::move(librados::IoCtx &io_ctx, rbd_trash_image_source_t source, | |
122 | const std::string &image_name, const std::string &image_id, | |
123 | uint64_t delay) { | |
124 | ceph_assert(!image_name.empty() && !image_id.empty()); | |
125 | CephContext *cct((CephContext *)io_ctx.cct()); | |
126 | ldout(cct, 20) << &io_ctx << " name=" << image_name << ", id=" << image_id | |
127 | << dendl; | |
128 | ||
129 | auto ictx = new I("", image_id, nullptr, io_ctx, false); | |
130 | int r = ictx->state->open(OPEN_FLAG_SKIP_OPEN_PARENT); | |
131 | ||
132 | if (r < 0 && r != -ENOENT) { | |
133 | lderr(cct) << "failed to open image: " << cpp_strerror(r) << dendl; | |
134 | return r; | |
135 | } | |
136 | ||
137 | if (r == 0) { | |
138 | if (ictx->test_features(RBD_FEATURE_JOURNALING)) { | |
139 | RWLock::WLocker snap_locker(ictx->snap_lock); | |
140 | ictx->set_journal_policy(new journal::DisabledPolicy()); | |
141 | } | |
142 | ||
143 | ictx->owner_lock.get_read(); | |
144 | if (ictx->exclusive_lock != nullptr) { | |
145 | ictx->exclusive_lock->block_requests(0); | |
146 | ||
147 | r = ictx->operations->prepare_image_update(false); | |
148 | if (r < 0) { | |
149 | lderr(cct) << "cannot obtain exclusive lock - not removing" << dendl; | |
150 | ictx->owner_lock.put_read(); | |
151 | ictx->state->close(); | |
152 | return -EBUSY; | |
153 | } | |
154 | } | |
155 | ictx->owner_lock.put_read(); | |
156 | ||
157 | ictx->snap_lock.get_read(); | |
158 | if (!ictx->migration_info.empty()) { | |
159 | lderr(cct) << "cannot move migrating image to trash" << dendl; | |
160 | ictx->snap_lock.put_read(); | |
161 | ictx->state->close(); | |
162 | return -EBUSY; | |
163 | } | |
164 | ictx->snap_lock.put_read(); | |
165 | ||
166 | r = disable_mirroring<I>(ictx); | |
167 | if (r < 0) { | |
168 | ictx->state->close(); | |
169 | return r; | |
170 | } | |
171 | ||
172 | ictx->state->close(); | |
173 | } | |
174 | ||
175 | utime_t delete_time{ceph_clock_now()}; | |
176 | utime_t deferment_end_time{delete_time}; | |
177 | deferment_end_time += delay; | |
178 | cls::rbd::TrashImageSpec trash_image_spec{ | |
179 | static_cast<cls::rbd::TrashImageSource>(source), image_name, | |
180 | delete_time, deferment_end_time}; | |
181 | ||
182 | trash_image_spec.state = cls::rbd::TRASH_IMAGE_STATE_MOVING; | |
183 | C_SaferCond ctx; | |
184 | auto req = trash::MoveRequest<I>::create(io_ctx, image_id, trash_image_spec, | |
185 | &ctx); | |
186 | req->send(); | |
187 | ||
188 | r = ctx.wait(); | |
189 | trash_image_spec.state = cls::rbd::TRASH_IMAGE_STATE_NORMAL; | |
190 | int ret = cls_client::trash_state_set(&io_ctx, image_id, | |
191 | trash_image_spec.state, | |
192 | cls::rbd::TRASH_IMAGE_STATE_MOVING); | |
193 | if (ret < 0 && ret != -EOPNOTSUPP) { | |
194 | lderr(cct) << "error setting trash image state: " | |
195 | << cpp_strerror(ret) << dendl; | |
196 | return ret; | |
197 | } | |
198 | if (r < 0) { | |
199 | return r; | |
200 | } | |
201 | ||
202 | C_SaferCond notify_ctx; | |
203 | TrashWatcher<I>::notify_image_added(io_ctx, image_id, trash_image_spec, | |
204 | ¬ify_ctx); | |
205 | r = notify_ctx.wait(); | |
206 | if (r < 0) { | |
207 | lderr(cct) << "failed to send update notification: " << cpp_strerror(r) | |
208 | << dendl; | |
209 | } | |
210 | ||
211 | return 0; | |
212 | } | |
213 | ||
214 | template <typename I> | |
215 | int Trash<I>::move(librados::IoCtx &io_ctx, rbd_trash_image_source_t source, | |
216 | const std::string &image_name, uint64_t delay) { | |
217 | CephContext *cct((CephContext *)io_ctx.cct()); | |
218 | ldout(cct, 20) << &io_ctx << " name=" << image_name << dendl; | |
219 | ||
220 | // try to get image id from the directory | |
221 | std::string image_id; | |
222 | int r = cls_client::dir_get_id(&io_ctx, RBD_DIRECTORY, image_name, | |
223 | &image_id); | |
224 | if (r == -ENOENT) { | |
225 | r = io_ctx.stat(util::old_header_name(image_name), nullptr, nullptr); | |
226 | if (r == 0) { | |
227 | // cannot move V1 image to trash | |
228 | ldout(cct, 10) << "cannot move v1 image to trash" << dendl; | |
229 | return -EOPNOTSUPP; | |
230 | } | |
231 | ||
232 | // image doesn't exist -- perhaps already in the trash since removing | |
233 | // from the directory is the last step | |
234 | return -ENOENT; | |
235 | } else if (r < 0) { | |
236 | lderr(cct) << "failed to retrieve image id: " << cpp_strerror(r) << dendl; | |
237 | return r; | |
238 | } | |
239 | ||
240 | ceph_assert(!image_name.empty() && !image_id.empty()); | |
241 | return Trash<I>::move(io_ctx, source, image_name, image_id, delay); | |
242 | } | |
243 | ||
244 | template <typename I> | |
245 | int Trash<I>::get(IoCtx &io_ctx, const std::string &id, | |
246 | trash_image_info_t *info) { | |
247 | CephContext *cct((CephContext *)io_ctx.cct()); | |
248 | ldout(cct, 20) << __func__ << " " << &io_ctx << dendl; | |
249 | ||
250 | cls::rbd::TrashImageSpec spec; | |
251 | int r = cls_client::trash_get(&io_ctx, id, &spec); | |
252 | if (r == -ENOENT) { | |
253 | return r; | |
254 | } else if (r < 0) { | |
255 | lderr(cct) << "error retrieving trash entry: " << cpp_strerror(r) | |
256 | << dendl; | |
257 | return r; | |
258 | } | |
259 | ||
260 | rbd_trash_image_source_t source = static_cast<rbd_trash_image_source_t>( | |
261 | spec.source); | |
262 | *info = trash_image_info_t{id, spec.name, source, spec.deletion_time.sec(), | |
263 | spec.deferment_end_time.sec()}; | |
264 | return 0; | |
265 | } | |
266 | ||
267 | template <typename I> | |
268 | int Trash<I>::list(IoCtx &io_ctx, vector<trash_image_info_t> &entries, | |
269 | bool exclude_user_remove_source) { | |
270 | CephContext *cct((CephContext *)io_ctx.cct()); | |
271 | ldout(cct, 20) << "trash_list " << &io_ctx << dendl; | |
272 | ||
273 | bool more_entries; | |
274 | uint32_t max_read = 1024; | |
275 | std::string last_read = ""; | |
276 | do { | |
277 | map<string, cls::rbd::TrashImageSpec> trash_entries; | |
278 | int r = cls_client::trash_list(&io_ctx, last_read, max_read, | |
279 | &trash_entries); | |
280 | if (r < 0 && r != -ENOENT) { | |
281 | lderr(cct) << "error listing rbd trash entries: " << cpp_strerror(r) | |
282 | << dendl; | |
283 | return r; | |
284 | } else if (r == -ENOENT) { | |
285 | break; | |
286 | } | |
287 | ||
288 | if (trash_entries.empty()) { | |
289 | break; | |
290 | } | |
291 | ||
292 | for (const auto &entry : trash_entries) { | |
293 | rbd_trash_image_source_t source = | |
294 | static_cast<rbd_trash_image_source_t>(entry.second.source); | |
295 | if (exclude_user_remove_source && | |
296 | source == RBD_TRASH_IMAGE_SOURCE_REMOVING) { | |
297 | continue; | |
298 | } | |
299 | entries.push_back({entry.first, entry.second.name, source, | |
300 | entry.second.deletion_time.sec(), | |
301 | entry.second.deferment_end_time.sec()}); | |
302 | } | |
303 | last_read = trash_entries.rbegin()->first; | |
304 | more_entries = (trash_entries.size() >= max_read); | |
305 | } while (more_entries); | |
306 | ||
307 | return 0; | |
308 | } | |
309 | ||
310 | template <typename I> | |
311 | int Trash<I>::purge(IoCtx& io_ctx, time_t expire_ts, | |
312 | float threshold, ProgressContext& pctx) { | |
313 | auto *cct((CephContext *) io_ctx.cct()); | |
314 | ldout(cct, 20) << &io_ctx << dendl; | |
315 | ||
316 | std::vector<librbd::trash_image_info_t> trash_entries; | |
317 | int r = librbd::api::Trash<I>::list(io_ctx, trash_entries, true); | |
318 | if (r < 0) { | |
319 | return r; | |
320 | } | |
321 | ||
322 | trash_entries.erase( | |
323 | std::remove_if(trash_entries.begin(), trash_entries.end(), | |
324 | [](librbd::trash_image_info_t info) { | |
325 | return info.source != RBD_TRASH_IMAGE_SOURCE_USER; | |
326 | }), | |
327 | trash_entries.end()); | |
328 | ||
329 | std::set<std::string> to_be_removed; | |
330 | if (threshold != -1) { | |
331 | if (threshold < 0 || threshold > 1) { | |
332 | lderr(cct) << "argument 'threshold' is out of valid range" | |
333 | << dendl; | |
334 | return -EINVAL; | |
335 | } | |
336 | ||
337 | librados::bufferlist inbl; | |
338 | librados::bufferlist outbl; | |
339 | std::string pool_name = io_ctx.get_pool_name(); | |
340 | ||
341 | librados::Rados rados(io_ctx); | |
342 | rados.mon_command(R"({"prefix": "df", "format": "json"})", inbl, | |
343 | &outbl, nullptr); | |
344 | ||
345 | json_spirit::mValue json; | |
346 | if (!json_spirit::read(outbl.to_str(), json)) { | |
347 | lderr(cct) << "ceph df json output could not be parsed" | |
348 | << dendl; | |
349 | return -EBADMSG; | |
350 | } | |
351 | ||
352 | json_spirit::mArray arr = json.get_obj()["pools"].get_array(); | |
353 | ||
354 | double pool_percent_used = 0; | |
355 | uint64_t pool_total_bytes = 0; | |
356 | ||
357 | std::map<std::string, std::vector<std::string>> datapools; | |
358 | ||
359 | std::sort(trash_entries.begin(), trash_entries.end(), | |
360 | [](librbd::trash_image_info_t a, librbd::trash_image_info_t b) { | |
361 | return a.deferment_end_time < b.deferment_end_time; | |
362 | } | |
363 | ); | |
364 | ||
365 | for (const auto &entry : trash_entries) { | |
366 | int64_t data_pool_id = -1; | |
367 | r = cls_client::get_data_pool(&io_ctx, util::header_name(entry.id), | |
368 | &data_pool_id); | |
369 | if (r < 0 && r != -ENOENT && r != -EOPNOTSUPP) { | |
370 | lderr(cct) << "failed to query data pool: " << cpp_strerror(r) << dendl; | |
371 | return r; | |
372 | } else if (data_pool_id == -1) { | |
373 | data_pool_id = io_ctx.get_id(); | |
374 | } | |
375 | ||
376 | if (data_pool_id != io_ctx.get_id()) { | |
377 | librados::IoCtx data_io_ctx; | |
378 | r = util::create_ioctx(io_ctx, "image", data_pool_id, | |
379 | {}, &data_io_ctx); | |
380 | if (r < 0) { | |
381 | lderr(cct) << "error accessing data pool" << dendl; | |
382 | continue; | |
383 | } | |
384 | auto data_pool = data_io_ctx.get_pool_name(); | |
385 | datapools[data_pool].push_back(entry.id); | |
386 | } else { | |
387 | datapools[pool_name].push_back(entry.id); | |
388 | } | |
389 | } | |
390 | ||
391 | uint64_t bytes_to_free = 0; | |
392 | ||
393 | for (uint8_t i = 0; i < arr.size(); ++i) { | |
394 | json_spirit::mObject obj = arr[i].get_obj(); | |
395 | std::string name = obj.find("name")->second.get_str(); | |
396 | auto img = datapools.find(name); | |
397 | if (img != datapools.end()) { | |
398 | json_spirit::mObject stats = arr[i].get_obj()["stats"].get_obj(); | |
399 | pool_percent_used = stats["percent_used"].get_real(); | |
400 | if (pool_percent_used <= threshold) continue; | |
401 | ||
402 | bytes_to_free = 0; | |
403 | ||
404 | pool_total_bytes = stats["max_avail"].get_uint64() + | |
405 | stats["bytes_used"].get_uint64(); | |
406 | ||
407 | auto bytes_threshold = (uint64_t) (pool_total_bytes * | |
408 | (pool_percent_used - threshold)); | |
409 | ||
410 | for (const auto &it : img->second) { | |
411 | auto ictx = new I("", it, nullptr, io_ctx, false); | |
412 | r = ictx->state->open(OPEN_FLAG_SKIP_OPEN_PARENT); | |
413 | if (r == -ENOENT) { | |
414 | continue; | |
415 | } else if (r < 0) { | |
416 | lderr(cct) << "failed to open image " << it << ": " | |
417 | << cpp_strerror(r) << dendl; | |
418 | } | |
419 | ||
420 | r = librbd::api::DiffIterate<I>::diff_iterate( | |
421 | ictx, cls::rbd::UserSnapshotNamespace(), nullptr, 0, ictx->size, | |
422 | false, true, | |
423 | [](uint64_t offset, size_t len, int exists, void *arg) { | |
424 | auto *to_free = reinterpret_cast<uint64_t *>(arg); | |
425 | if (exists) | |
426 | (*to_free) += len; | |
427 | return 0; | |
428 | }, &bytes_to_free); | |
429 | ||
430 | ictx->state->close(); | |
431 | if (r < 0) { | |
432 | lderr(cct) << "failed to calculate disk usage for image " << it | |
433 | << ": " << cpp_strerror(r) << dendl; | |
434 | continue; | |
435 | } | |
436 | ||
437 | to_be_removed.insert(it); | |
438 | if (bytes_to_free >= bytes_threshold) { | |
439 | break; | |
440 | } | |
441 | } | |
442 | } | |
443 | } | |
444 | ||
445 | if (bytes_to_free == 0) { | |
446 | ldout(cct, 10) << "pool usage is lower than or equal to " | |
447 | << (threshold * 100) | |
448 | << "%" << dendl; | |
449 | return 0; | |
450 | } | |
451 | } | |
452 | ||
453 | if (expire_ts == 0) { | |
454 | struct timespec now; | |
455 | clock_gettime(CLOCK_REALTIME, &now); | |
456 | expire_ts = now.tv_sec; | |
457 | } | |
458 | ||
459 | for (const auto &entry : trash_entries) { | |
460 | if (expire_ts >= entry.deferment_end_time) { | |
461 | to_be_removed.insert(entry.id); | |
462 | } | |
463 | } | |
464 | ||
465 | NoOpProgressContext remove_pctx; | |
466 | uint64_t list_size = to_be_removed.size(), i = 0; | |
467 | for (const auto &entry_id : to_be_removed) { | |
468 | r = librbd::api::Trash<I>::remove(io_ctx, entry_id, true, remove_pctx); | |
469 | if (r < 0) { | |
470 | if (r == -ENOTEMPTY) { | |
471 | ldout(cct, 5) << "image has snapshots - these must be deleted " | |
472 | << "with 'rbd snap purge' before the image can be " | |
473 | << "removed." << dendl; | |
474 | } else if (r == -EBUSY) { | |
475 | ldout(cct, 5) << "error: image still has watchers" << std::endl | |
476 | << "This means the image is still open or the client " | |
477 | << "using it crashed. Try again after closing/unmapping " | |
478 | << "it or waiting 30s for the crashed client to timeout." | |
479 | << dendl; | |
480 | } else if (r == -EMLINK) { | |
481 | ldout(cct, 5) << "Remove the image from the group and try again." | |
482 | << dendl; | |
483 | } else { | |
484 | lderr(cct) << "remove error: " << cpp_strerror(r) << dendl; | |
485 | } | |
486 | return r; | |
487 | } | |
488 | pctx.update_progress(++i, list_size); | |
489 | } | |
490 | ||
491 | return 0; | |
492 | } | |
493 | ||
494 | template <typename I> | |
495 | int Trash<I>::remove(IoCtx &io_ctx, const std::string &image_id, bool force, | |
496 | ProgressContext& prog_ctx) { | |
497 | CephContext *cct((CephContext *)io_ctx.cct()); | |
498 | ldout(cct, 20) << "trash_remove " << &io_ctx << " " << image_id | |
499 | << " " << force << dendl; | |
500 | ||
501 | cls::rbd::TrashImageSpec trash_spec; | |
502 | int r = cls_client::trash_get(&io_ctx, image_id, &trash_spec); | |
503 | if (r < 0) { | |
504 | lderr(cct) << "error getting image id " << image_id | |
505 | << " info from trash: " << cpp_strerror(r) << dendl; | |
506 | return r; | |
507 | } | |
508 | ||
509 | utime_t now = ceph_clock_now(); | |
510 | if (now < trash_spec.deferment_end_time && !force) { | |
511 | lderr(cct) << "error: deferment time has not expired." << dendl; | |
512 | return -EPERM; | |
513 | } | |
514 | if (trash_spec.state != cls::rbd::TRASH_IMAGE_STATE_NORMAL && | |
515 | trash_spec.state != cls::rbd::TRASH_IMAGE_STATE_REMOVING) { | |
516 | lderr(cct) << "error: image is pending restoration." << dendl; | |
517 | return -EBUSY; | |
518 | } | |
519 | ||
520 | r = cls_client::trash_state_set(&io_ctx, image_id, | |
521 | cls::rbd::TRASH_IMAGE_STATE_REMOVING, | |
522 | cls::rbd::TRASH_IMAGE_STATE_NORMAL); | |
523 | if (r < 0 && r != -EOPNOTSUPP) { | |
524 | lderr(cct) << "error setting trash image state: " | |
525 | << cpp_strerror(r) << dendl; | |
526 | return r; | |
527 | } | |
528 | ||
529 | ThreadPool *thread_pool; | |
530 | ContextWQ *op_work_queue; | |
531 | ImageCtx::get_thread_pool_instance(cct, &thread_pool, &op_work_queue); | |
532 | ||
533 | C_SaferCond cond; | |
534 | auto req = librbd::image::RemoveRequest<I>::create( | |
535 | io_ctx, "", image_id, force, true, prog_ctx, op_work_queue, &cond); | |
536 | req->send(); | |
537 | ||
538 | r = cond.wait(); | |
539 | if (r < 0) { | |
540 | lderr(cct) << "error removing image " << image_id | |
541 | << ", which is pending deletion" << dendl; | |
542 | int ret = cls_client::trash_state_set(&io_ctx, image_id, | |
543 | cls::rbd::TRASH_IMAGE_STATE_NORMAL, | |
544 | cls::rbd::TRASH_IMAGE_STATE_REMOVING); | |
545 | if (ret < 0 && ret != -EOPNOTSUPP) { | |
546 | lderr(cct) << "error setting trash image state: " | |
547 | << cpp_strerror(ret) << dendl; | |
548 | } | |
549 | return r; | |
550 | } | |
551 | r = cls_client::trash_remove(&io_ctx, image_id); | |
552 | if (r < 0 && r != -ENOENT) { | |
553 | lderr(cct) << "error removing image " << image_id | |
554 | << " from rbd_trash object" << dendl; | |
555 | return r; | |
556 | } | |
557 | ||
558 | C_SaferCond notify_ctx; | |
559 | TrashWatcher<I>::notify_image_removed(io_ctx, image_id, ¬ify_ctx); | |
560 | r = notify_ctx.wait(); | |
561 | if (r < 0) { | |
562 | lderr(cct) << "failed to send update notification: " << cpp_strerror(r) | |
563 | << dendl; | |
564 | } | |
565 | ||
566 | return 0; | |
567 | } | |
568 | ||
569 | template <typename I> | |
570 | int Trash<I>::restore(librados::IoCtx &io_ctx, rbd_trash_image_source_t source, | |
571 | const std::string &image_id, | |
572 | const std::string &image_new_name) { | |
573 | CephContext *cct((CephContext *)io_ctx.cct()); | |
574 | ldout(cct, 20) << "trash_restore " << &io_ctx << " " << image_id << " " | |
575 | << image_new_name << dendl; | |
576 | ||
577 | cls::rbd::TrashImageSpec trash_spec; | |
578 | int r = cls_client::trash_get(&io_ctx, image_id, &trash_spec); | |
579 | if (r < 0) { | |
580 | lderr(cct) << "error getting image id " << image_id | |
581 | << " info from trash: " << cpp_strerror(r) << dendl; | |
582 | return r; | |
583 | } | |
584 | ||
585 | if (trash_spec.source != static_cast<cls::rbd::TrashImageSource>(source)) { | |
586 | lderr(cct) << "Current trash source: " << trash_spec.source | |
587 | << " does not match expected: " | |
588 | << static_cast<cls::rbd::TrashImageSource>(source) << dendl; | |
589 | return -EINVAL; | |
590 | } | |
591 | ||
592 | std::string image_name = image_new_name; | |
593 | if (trash_spec.state != cls::rbd::TRASH_IMAGE_STATE_NORMAL && | |
594 | trash_spec.state != cls::rbd::TRASH_IMAGE_STATE_RESTORING) { | |
595 | lderr(cct) << "error restoring image id " << image_id | |
596 | << ", which is pending deletion" << dendl; | |
597 | return -EBUSY; | |
598 | } | |
599 | r = cls_client::trash_state_set(&io_ctx, image_id, | |
600 | cls::rbd::TRASH_IMAGE_STATE_RESTORING, | |
601 | cls::rbd::TRASH_IMAGE_STATE_NORMAL); | |
602 | if (r < 0 && r != -EOPNOTSUPP) { | |
603 | lderr(cct) << "error setting trash image state: " | |
604 | << cpp_strerror(r) << dendl; | |
605 | return r; | |
606 | } | |
607 | ||
608 | if (image_name.empty()) { | |
609 | // if user didn't specify a new name, let's try using the old name | |
610 | image_name = trash_spec.name; | |
611 | ldout(cct, 20) << "restoring image id " << image_id << " with name " | |
612 | << image_name << dendl; | |
613 | } | |
614 | ||
615 | // check if no image exists with the same name | |
616 | bool create_id_obj = true; | |
617 | std::string existing_id; | |
618 | r = cls_client::get_id(&io_ctx, util::id_obj_name(image_name), &existing_id); | |
619 | if (r < 0 && r != -ENOENT) { | |
620 | lderr(cct) << "error checking if image " << image_name << " exists: " | |
621 | << cpp_strerror(r) << dendl; | |
622 | int ret = cls_client::trash_state_set(&io_ctx, image_id, | |
623 | cls::rbd::TRASH_IMAGE_STATE_NORMAL, | |
624 | cls::rbd::TRASH_IMAGE_STATE_RESTORING); | |
625 | if (ret < 0 && ret != -EOPNOTSUPP) { | |
626 | lderr(cct) << "error setting trash image state: " | |
627 | << cpp_strerror(ret) << dendl; | |
628 | } | |
629 | return r; | |
630 | } else if (r != -ENOENT){ | |
631 | // checking if we are recovering from an incomplete restore | |
632 | if (existing_id != image_id) { | |
633 | ldout(cct, 2) << "an image with the same name already exists" << dendl; | |
634 | int r2 = cls_client::trash_state_set(&io_ctx, image_id, | |
635 | cls::rbd::TRASH_IMAGE_STATE_NORMAL, | |
636 | cls::rbd::TRASH_IMAGE_STATE_RESTORING); | |
637 | if (r2 < 0 && r2 != -EOPNOTSUPP) { | |
638 | lderr(cct) << "error setting trash image state: " | |
639 | << cpp_strerror(r2) << dendl; | |
640 | } | |
641 | return -EEXIST; | |
642 | } | |
643 | create_id_obj = false; | |
644 | } | |
645 | ||
646 | if (create_id_obj) { | |
647 | ldout(cct, 2) << "adding id object" << dendl; | |
648 | librados::ObjectWriteOperation op; | |
649 | op.create(true); | |
650 | cls_client::set_id(&op, image_id); | |
651 | r = io_ctx.operate(util::id_obj_name(image_name), &op); | |
652 | if (r < 0) { | |
653 | lderr(cct) << "error adding id object for image " << image_name | |
654 | << ": " << cpp_strerror(r) << dendl; | |
655 | return r; | |
656 | } | |
657 | } | |
658 | ||
659 | ldout(cct, 2) << "adding rbd image to v2 directory..." << dendl; | |
660 | r = cls_client::dir_add_image(&io_ctx, RBD_DIRECTORY, image_name, | |
661 | image_id); | |
662 | if (r < 0 && r != -EEXIST) { | |
663 | lderr(cct) << "error adding image to v2 directory: " | |
664 | << cpp_strerror(r) << dendl; | |
665 | return r; | |
666 | } | |
667 | ||
668 | r = enable_mirroring<I>(io_ctx, image_id); | |
669 | if (r < 0) { | |
670 | // not fatal -- ignore | |
671 | } | |
672 | ||
673 | ldout(cct, 2) << "removing image from trash..." << dendl; | |
674 | r = cls_client::trash_remove(&io_ctx, image_id); | |
675 | if (r < 0 && r != -ENOENT) { | |
676 | lderr(cct) << "error removing image id " << image_id << " from trash: " | |
677 | << cpp_strerror(r) << dendl; | |
678 | return r; | |
679 | } | |
680 | ||
681 | C_SaferCond notify_ctx; | |
682 | TrashWatcher<I>::notify_image_removed(io_ctx, image_id, ¬ify_ctx); | |
683 | r = notify_ctx.wait(); | |
684 | if (r < 0) { | |
685 | lderr(cct) << "failed to send update notification: " << cpp_strerror(r) | |
686 | << dendl; | |
687 | } | |
688 | ||
689 | return 0; | |
690 | } | |
691 | ||
692 | } // namespace api | |
693 | } // namespace librbd | |
694 | ||
695 | template class librbd::api::Trash<librbd::ImageCtx>; |