]> git.proxmox.com Git - ceph.git/blame - ceph/src/librbd/api/Trash.cc
update sources to ceph Nautilus 14.2.1
[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"
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
30namespace librbd {
31namespace api {
32
33namespace {
34
35template <typename I>
36int 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
69template <typename I>
70int 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
120template <typename I>
121int 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 &notify_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
214template <typename I>
215int 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
244template <typename I>
245int 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
267template <typename I>
268int 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
310template <typename I>
311int 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
494template <typename I>
495int 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, &notify_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
569template <typename I>
570int 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, &notify_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
695template class librbd::api::Trash<librbd::ImageCtx>;