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