]>
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" | |
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 | ||
31 | namespace librbd { | |
32 | namespace api { | |
33 | ||
eafe8130 TL |
34 | template <typename I> |
35 | const 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 |
40 | namespace { |
41 | ||
42 | template <typename I> | |
43 | int 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 | ||
76 | template <typename I> | |
77 | int 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 | ||
127 | template <typename I> | |
128 | int 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 | ¬ify_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 | ||
221 | template <typename I> | |
222 | int 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 | ||
251 | template <typename I> | |
252 | int 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 | ||
274 | template <typename I> | |
275 | int 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 | ||
317 | template <typename I> | |
318 | int 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 | ||
501 | template <typename I> | |
502 | int 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, ¬ify_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 | ||
552 | template <typename I> | |
eafe8130 TL |
553 | int 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, ¬ify_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 | ||
679 | template class librbd::api::Trash<librbd::ImageCtx>; |