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