]>
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" | |
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" | |
28 | ||
29 | #define dout_subsys ceph_subsys_rbd | |
30 | #undef dout_prefix | |
31 | #define dout_prefix *_dout << "librbd::api::Trash: " << __func__ << ": " | |
32 | ||
33 | namespace librbd { | |
34 | namespace api { | |
35 | ||
eafe8130 | 36 | template <typename I> |
f67539c2 | 37 | const typename Trash<I>::TrashImageSources Trash<I>::ALLOWED_RESTORE_SOURCES { |
eafe8130 | 38 | cls::rbd::TRASH_IMAGE_SOURCE_USER, |
9f95a23c TL |
39 | cls::rbd::TRASH_IMAGE_SOURCE_MIRRORING, |
40 | cls::rbd::TRASH_IMAGE_SOURCE_USER_PARENT | |
eafe8130 TL |
41 | }; |
42 | ||
11fdf7f2 TL |
43 | namespace { |
44 | ||
45 | template <typename I> | |
46 | int disable_mirroring(I *ictx) { | |
11fdf7f2 TL |
47 | ldout(ictx->cct, 10) << dendl; |
48 | ||
49 | C_SaferCond ctx; | |
50 | auto req = mirror::DisableRequest<I>::create(ictx, false, true, &ctx); | |
51 | req->send(); | |
9f95a23c | 52 | int r = ctx.wait(); |
11fdf7f2 TL |
53 | if (r < 0) { |
54 | lderr(ictx->cct) << "failed to disable mirroring: " << cpp_strerror(r) | |
55 | << dendl; | |
56 | return r; | |
57 | } | |
58 | ||
59 | return 0; | |
60 | } | |
61 | ||
62 | template <typename I> | |
63 | int enable_mirroring(IoCtx &io_ctx, const std::string &image_id) { | |
64 | auto cct = reinterpret_cast<CephContext*>(io_ctx.cct()); | |
65 | ||
66 | uint64_t features; | |
67 | uint64_t incompatible_features; | |
68 | int r = cls_client::get_features(&io_ctx, util::header_name(image_id), true, | |
69 | &features, &incompatible_features); | |
70 | if (r < 0) { | |
71 | lderr(cct) << "failed to retrieve features: " << cpp_strerror(r) << dendl; | |
72 | return r; | |
73 | } | |
74 | ||
75 | if ((features & RBD_FEATURE_JOURNALING) == 0) { | |
76 | return 0; | |
77 | } | |
78 | ||
79 | cls::rbd::MirrorMode mirror_mode; | |
80 | r = cls_client::mirror_mode_get(&io_ctx, &mirror_mode); | |
81 | if (r < 0 && r != -ENOENT) { | |
82 | lderr(cct) << "failed to retrieve mirror mode: " << cpp_strerror(r) | |
83 | << dendl; | |
84 | return r; | |
85 | } | |
86 | ||
87 | if (mirror_mode != cls::rbd::MIRROR_MODE_POOL) { | |
88 | ldout(cct, 10) << "not pool mirroring mode" << dendl; | |
89 | return 0; | |
90 | } | |
91 | ||
92 | ldout(cct, 10) << dendl; | |
93 | ||
f67539c2 TL |
94 | AsioEngine asio_engine(io_ctx); |
95 | ||
11fdf7f2 | 96 | C_SaferCond ctx; |
9f95a23c | 97 | auto req = mirror::EnableRequest<I>::create( |
1911f103 | 98 | io_ctx, image_id, cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", false, |
f67539c2 | 99 | asio_engine.get_work_queue(), &ctx); |
11fdf7f2 TL |
100 | req->send(); |
101 | r = ctx.wait(); | |
102 | if (r < 0) { | |
103 | lderr(cct) << "failed to enable mirroring: " << cpp_strerror(r) | |
104 | << dendl; | |
105 | return r; | |
106 | } | |
107 | ||
108 | return 0; | |
109 | } | |
110 | ||
f67539c2 TL |
111 | int list_trash_image_specs( |
112 | librados::IoCtx &io_ctx, | |
113 | std::map<std::string, cls::rbd::TrashImageSpec>* trash_image_specs, | |
114 | bool exclude_user_remove_source) { | |
115 | CephContext *cct((CephContext *)io_ctx.cct()); | |
116 | ldout(cct, 20) << "list_trash_image_specs " << &io_ctx << dendl; | |
117 | ||
118 | bool more_entries; | |
119 | uint32_t max_read = 1024; | |
120 | std::string last_read; | |
121 | do { | |
20effc67 | 122 | std::map<std::string, cls::rbd::TrashImageSpec> trash_entries; |
f67539c2 TL |
123 | int r = cls_client::trash_list(&io_ctx, last_read, max_read, |
124 | &trash_entries); | |
125 | if (r < 0 && r != -ENOENT) { | |
126 | lderr(cct) << "error listing rbd trash entries: " << cpp_strerror(r) | |
127 | << dendl; | |
128 | return r; | |
129 | } else if (r == -ENOENT) { | |
130 | break; | |
131 | } | |
132 | ||
133 | if (trash_entries.empty()) { | |
134 | break; | |
135 | } | |
136 | ||
137 | for (const auto &entry : trash_entries) { | |
138 | if (exclude_user_remove_source && | |
139 | entry.second.source == cls::rbd::TRASH_IMAGE_SOURCE_REMOVING) { | |
140 | continue; | |
141 | } | |
142 | ||
143 | trash_image_specs->insert({entry.first, entry.second}); | |
144 | } | |
145 | ||
146 | last_read = trash_entries.rbegin()->first; | |
147 | more_entries = (trash_entries.size() >= max_read); | |
148 | } while (more_entries); | |
149 | ||
150 | return 0; | |
151 | } | |
152 | ||
11fdf7f2 TL |
153 | } // anonymous namespace |
154 | ||
155 | template <typename I> | |
156 | int Trash<I>::move(librados::IoCtx &io_ctx, rbd_trash_image_source_t source, | |
157 | const std::string &image_name, const std::string &image_id, | |
158 | uint64_t delay) { | |
159 | ceph_assert(!image_name.empty() && !image_id.empty()); | |
160 | CephContext *cct((CephContext *)io_ctx.cct()); | |
161 | ldout(cct, 20) << &io_ctx << " name=" << image_name << ", id=" << image_id | |
162 | << dendl; | |
163 | ||
164 | auto ictx = new I("", image_id, nullptr, io_ctx, false); | |
165 | int r = ictx->state->open(OPEN_FLAG_SKIP_OPEN_PARENT); | |
166 | ||
167 | if (r < 0 && r != -ENOENT) { | |
168 | lderr(cct) << "failed to open image: " << cpp_strerror(r) << dendl; | |
169 | return r; | |
170 | } | |
171 | ||
172 | if (r == 0) { | |
9f95a23c TL |
173 | cls::rbd::MirrorImage mirror_image; |
174 | int mirror_r = cls_client::mirror_image_get(&ictx->md_ctx, ictx->id, | |
175 | &mirror_image); | |
176 | if (mirror_r == -ENOENT) { | |
177 | ldout(ictx->cct, 10) << "mirroring is not enabled for this image" | |
178 | << dendl; | |
179 | } else if (mirror_r < 0) { | |
180 | lderr(ictx->cct) << "failed to retrieve mirror image: " | |
181 | << cpp_strerror(mirror_r) << dendl; | |
182 | return mirror_r; | |
183 | } else if (mirror_image.mode == cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT) { | |
184 | // a remote rbd-mirror might own the exclusive-lock on this image | |
185 | // and therefore we need to disable mirroring so that it closes the image | |
186 | r = disable_mirroring<I>(ictx); | |
187 | if (r < 0) { | |
188 | ictx->state->close(); | |
189 | return r; | |
190 | } | |
191 | } | |
192 | ||
11fdf7f2 | 193 | if (ictx->test_features(RBD_FEATURE_JOURNALING)) { |
9f95a23c | 194 | std::unique_lock image_locker{ictx->image_lock}; |
11fdf7f2 TL |
195 | ictx->set_journal_policy(new journal::DisabledPolicy()); |
196 | } | |
197 | ||
9f95a23c | 198 | ictx->owner_lock.lock_shared(); |
11fdf7f2 TL |
199 | if (ictx->exclusive_lock != nullptr) { |
200 | ictx->exclusive_lock->block_requests(0); | |
201 | ||
92f5a8d4 | 202 | r = ictx->operations->prepare_image_update( |
9f95a23c | 203 | exclusive_lock::OPERATION_REQUEST_TYPE_GENERAL, true); |
11fdf7f2 TL |
204 | if (r < 0) { |
205 | lderr(cct) << "cannot obtain exclusive lock - not removing" << dendl; | |
9f95a23c | 206 | ictx->owner_lock.unlock_shared(); |
11fdf7f2 TL |
207 | ictx->state->close(); |
208 | return -EBUSY; | |
209 | } | |
210 | } | |
9f95a23c | 211 | ictx->owner_lock.unlock_shared(); |
11fdf7f2 | 212 | |
9f95a23c | 213 | ictx->image_lock.lock_shared(); |
11fdf7f2 TL |
214 | if (!ictx->migration_info.empty()) { |
215 | lderr(cct) << "cannot move migrating image to trash" << dendl; | |
9f95a23c | 216 | ictx->image_lock.unlock_shared(); |
11fdf7f2 TL |
217 | ictx->state->close(); |
218 | return -EBUSY; | |
219 | } | |
9f95a23c | 220 | ictx->image_lock.unlock_shared(); |
11fdf7f2 | 221 | |
9f95a23c TL |
222 | if (mirror_r >= 0 && |
223 | mirror_image.mode != cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT) { | |
224 | r = disable_mirroring<I>(ictx); | |
225 | if (r < 0) { | |
226 | ictx->state->close(); | |
227 | return r; | |
228 | } | |
11fdf7f2 TL |
229 | } |
230 | ||
231 | ictx->state->close(); | |
232 | } | |
233 | ||
234 | utime_t delete_time{ceph_clock_now()}; | |
235 | utime_t deferment_end_time{delete_time}; | |
236 | deferment_end_time += delay; | |
237 | cls::rbd::TrashImageSpec trash_image_spec{ | |
238 | static_cast<cls::rbd::TrashImageSource>(source), image_name, | |
239 | delete_time, deferment_end_time}; | |
240 | ||
241 | trash_image_spec.state = cls::rbd::TRASH_IMAGE_STATE_MOVING; | |
242 | C_SaferCond ctx; | |
243 | auto req = trash::MoveRequest<I>::create(io_ctx, image_id, trash_image_spec, | |
244 | &ctx); | |
245 | req->send(); | |
246 | ||
247 | r = ctx.wait(); | |
248 | trash_image_spec.state = cls::rbd::TRASH_IMAGE_STATE_NORMAL; | |
249 | int ret = cls_client::trash_state_set(&io_ctx, image_id, | |
250 | trash_image_spec.state, | |
251 | cls::rbd::TRASH_IMAGE_STATE_MOVING); | |
252 | if (ret < 0 && ret != -EOPNOTSUPP) { | |
253 | lderr(cct) << "error setting trash image state: " | |
254 | << cpp_strerror(ret) << dendl; | |
255 | return ret; | |
256 | } | |
257 | if (r < 0) { | |
258 | return r; | |
259 | } | |
260 | ||
261 | C_SaferCond notify_ctx; | |
262 | TrashWatcher<I>::notify_image_added(io_ctx, image_id, trash_image_spec, | |
263 | ¬ify_ctx); | |
264 | r = notify_ctx.wait(); | |
265 | if (r < 0) { | |
266 | lderr(cct) << "failed to send update notification: " << cpp_strerror(r) | |
267 | << dendl; | |
268 | } | |
269 | ||
270 | return 0; | |
271 | } | |
272 | ||
273 | template <typename I> | |
274 | int Trash<I>::move(librados::IoCtx &io_ctx, rbd_trash_image_source_t source, | |
275 | const std::string &image_name, uint64_t delay) { | |
276 | CephContext *cct((CephContext *)io_ctx.cct()); | |
277 | ldout(cct, 20) << &io_ctx << " name=" << image_name << dendl; | |
278 | ||
279 | // try to get image id from the directory | |
280 | std::string image_id; | |
281 | int r = cls_client::dir_get_id(&io_ctx, RBD_DIRECTORY, image_name, | |
282 | &image_id); | |
283 | if (r == -ENOENT) { | |
284 | r = io_ctx.stat(util::old_header_name(image_name), nullptr, nullptr); | |
285 | if (r == 0) { | |
286 | // cannot move V1 image to trash | |
287 | ldout(cct, 10) << "cannot move v1 image to trash" << dendl; | |
288 | return -EOPNOTSUPP; | |
289 | } | |
290 | ||
f67539c2 TL |
291 | // search for an interrupted trash move request |
292 | std::map<std::string, cls::rbd::TrashImageSpec> trash_image_specs; | |
293 | int r = list_trash_image_specs(io_ctx, &trash_image_specs, true); | |
294 | if (r < 0) { | |
295 | return r; | |
296 | } | |
20effc67 TL |
297 | if (auto found_image = |
298 | std::find_if( | |
299 | trash_image_specs.begin(), trash_image_specs.end(), | |
300 | [&](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 | found_image != trash_image_specs.end()) { | |
307 | image_id = found_image->first; | |
308 | } else { | |
f67539c2 TL |
309 | return -ENOENT; |
310 | } | |
f67539c2 TL |
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 | ||
326 | template <typename I> | |
327 | int 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 | ||
349 | template <typename I> | |
20effc67 | 350 | int Trash<I>::list(IoCtx &io_ctx, std::vector<trash_image_info_t> &entries, |
11fdf7f2 TL |
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 | ||
374 | template <typename I> | |
375 | int 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 | ||
579 | template <typename I> | |
580 | int 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, ¬ify_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 | ||
632 | template <typename I> | |
eafe8130 TL |
633 | int 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, ¬ify_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 | ||
759 | template class librbd::api::Trash<librbd::ImageCtx>; |