1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 * Ceph - scalable distributed file system
6 * Copyright (C) 2016 SUSE LINUX GmbH
8 * This is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License version 2.1, as published by the Free Software
11 * Foundation. See file COPYING.
15 #include "include/rados/librados.hpp"
16 #include "common/Formatter.h"
17 #include "common/admin_socket.h"
18 #include "common/debug.h"
19 #include "common/errno.h"
20 #include "common/Timer.h"
21 #include "global/global_context.h"
22 #include "librbd/internal.h"
23 #include "librbd/ImageCtx.h"
24 #include "librbd/ImageState.h"
25 #include "librbd/Operations.h"
26 #include "librbd/asio/ContextWQ.h"
27 #include "cls/rbd/cls_rbd_client.h"
28 #include "cls/rbd/cls_rbd_types.h"
29 #include "librbd/Utils.h"
30 #include "ImageDeleter.h"
31 #include "tools/rbd_mirror/Threads.h"
32 #include "tools/rbd_mirror/Throttler.h"
33 #include "tools/rbd_mirror/image_deleter/TrashMoveRequest.h"
34 #include "tools/rbd_mirror/image_deleter/TrashRemoveRequest.h"
35 #include "tools/rbd_mirror/image_deleter/TrashWatcher.h"
39 #define dout_context g_ceph_context
40 #define dout_subsys ceph_subsys_rbd_mirror
43 using std::stringstream
;
48 using librados::IoCtx
;
49 using namespace librbd
;
54 using librbd::util::create_async_context_callback
;
58 class ImageDeleterAdminSocketCommand
{
60 virtual ~ImageDeleterAdminSocketCommand() {}
61 virtual int call(Formatter
*f
) = 0;
65 class StatusCommand
: public ImageDeleterAdminSocketCommand
{
67 explicit StatusCommand(ImageDeleter
<I
> *image_del
) : image_del(image_del
) {}
69 int call(Formatter
*f
) override
{
70 image_del
->print_status(f
);
75 ImageDeleter
<I
> *image_del
;
78 } // anonymous namespace
81 class ImageDeleterAdminSocketHook
: public AdminSocketHook
{
83 ImageDeleterAdminSocketHook(CephContext
*cct
, const std::string
& pool_name
,
84 ImageDeleter
<I
> *image_del
) :
85 admin_socket(cct
->get_admin_socket()) {
90 command
= "rbd mirror deletion status " + pool_name
;
91 r
= admin_socket
->register_command(command
, this,
92 "get status for image deleter");
94 commands
[command
] = new StatusCommand
<I
>(image_del
);
99 ~ImageDeleterAdminSocketHook() override
{
100 (void)admin_socket
->unregister_commands(this);
101 for (Commands::const_iterator i
= commands
.begin(); i
!= commands
.end();
107 int call(std::string_view command
, const cmdmap_t
& cmdmap
,
111 bufferlist
& out
) override
{
112 Commands::const_iterator i
= commands
.find(command
);
113 ceph_assert(i
!= commands
.end());
114 return i
->second
->call(f
);
118 typedef std::map
<std::string
, ImageDeleterAdminSocketCommand
*,
119 std::less
<>> Commands
;
120 AdminSocket
*admin_socket
;
124 template <typename I
>
125 ImageDeleter
<I
>::ImageDeleter(
126 librados::IoCtx
& local_io_ctx
, Threads
<librbd::ImageCtx
>* threads
,
127 Throttler
<librbd::ImageCtx
>* image_deletion_throttler
,
128 ServiceDaemon
<librbd::ImageCtx
>* service_daemon
)
129 : m_local_io_ctx(local_io_ctx
), m_threads(threads
),
130 m_image_deletion_throttler(image_deletion_throttler
),
131 m_service_daemon(service_daemon
), m_trash_listener(this),
132 m_lock(ceph::make_mutex(
133 librbd::util::unique_lock_name("rbd::mirror::ImageDeleter::m_lock",
138 #define dout_prefix *_dout << "rbd::mirror::ImageDeleter: " << " " \
141 template <typename I
>
142 void ImageDeleter
<I
>::trash_move(librados::IoCtx
& local_io_ctx
,
143 const std::string
& global_image_id
,
145 librbd::asio::ContextWQ
* work_queue
,
146 Context
* on_finish
) {
147 dout(10) << "global_image_id=" << global_image_id
<< ", "
148 << "resync=" << resync
<< dendl
;
150 auto req
= rbd::mirror::image_deleter::TrashMoveRequest
<>::create(
151 local_io_ctx
, global_image_id
, resync
, work_queue
, on_finish
);
156 #define dout_prefix *_dout << "rbd::mirror::ImageDeleter: " << this << " " \
159 template <typename I
>
160 void ImageDeleter
<I
>::init(Context
* on_finish
) {
163 m_asok_hook
= new ImageDeleterAdminSocketHook
<I
>(
164 g_ceph_context
, m_local_io_ctx
.get_pool_name(), this);
166 m_trash_watcher
= image_deleter::TrashWatcher
<I
>::create(m_local_io_ctx
,
169 m_trash_watcher
->init(on_finish
);
172 template <typename I
>
173 void ImageDeleter
<I
>::shut_down(Context
* on_finish
) {
177 m_asok_hook
= nullptr;
179 m_image_deletion_throttler
->drain(m_local_io_ctx
.get_namespace(),
182 shut_down_trash_watcher(on_finish
);
185 template <typename I
>
186 void ImageDeleter
<I
>::shut_down_trash_watcher(Context
* on_finish
) {
188 ceph_assert(m_trash_watcher
);
189 auto ctx
= new LambdaContext([this, on_finish
](int r
) {
190 delete m_trash_watcher
;
191 m_trash_watcher
= nullptr;
193 wait_for_ops(on_finish
);
195 m_trash_watcher
->shut_down(ctx
);
198 template <typename I
>
199 void ImageDeleter
<I
>::wait_for_ops(Context
* on_finish
) {
201 std::scoped_lock locker
{m_threads
->timer_lock
, m_lock
};
203 cancel_retry_timer();
206 auto ctx
= new LambdaContext([this, on_finish
](int) {
207 cancel_all_deletions(on_finish
);
209 m_async_op_tracker
.wait_for_ops(ctx
);
212 template <typename I
>
213 void ImageDeleter
<I
>::cancel_all_deletions(Context
* on_finish
) {
214 m_image_deletion_throttler
->drain(m_local_io_ctx
.get_namespace(),
217 std::lock_guard locker
{m_lock
};
218 // wake up any external state machines waiting on deletions
219 ceph_assert(m_in_flight_delete_queue
.empty());
220 for (auto& queue
: {&m_delete_queue
, &m_retry_delete_queue
}) {
221 for (auto& info
: *queue
) {
222 notify_on_delete(info
->image_id
, -ECANCELED
);
227 on_finish
->complete(0);
230 template <typename I
>
231 void ImageDeleter
<I
>::wait_for_deletion(const std::string
& image_id
,
233 Context
* on_finish
) {
234 dout(5) << "image_id=" << image_id
<< dendl
;
236 on_finish
= new LambdaContext([this, on_finish
](int r
) {
237 m_threads
->work_queue
->queue(on_finish
, r
);
240 std::lock_guard locker
{m_lock
};
241 auto del_info
= find_delete_info(image_id
);
242 if (!del_info
&& scheduled_only
) {
243 // image not scheduled for deletion
244 on_finish
->complete(0);
248 notify_on_delete(image_id
, -ESTALE
);
249 m_on_delete_contexts
[image_id
] = on_finish
;
252 template <typename I
>
253 void ImageDeleter
<I
>::complete_active_delete(DeleteInfoRef
* delete_info
,
255 dout(20) << "info=" << *delete_info
<< ", r=" << r
<< dendl
;
256 std::lock_guard locker
{m_lock
};
257 notify_on_delete((*delete_info
)->image_id
, r
);
258 delete_info
->reset();
261 template <typename I
>
262 void ImageDeleter
<I
>::enqueue_failed_delete(DeleteInfoRef
* delete_info
,
264 double retry_delay
) {
265 dout(20) << "info=" << *delete_info
<< ", r=" << error_code
<< dendl
;
266 if (error_code
== -EBLOCKLISTED
) {
267 std::lock_guard locker
{m_lock
};
268 derr
<< "blocklisted while deleting local image" << dendl
;
269 complete_active_delete(delete_info
, error_code
);
273 std::scoped_lock locker
{m_threads
->timer_lock
, m_lock
};
274 auto& delete_info_ref
= *delete_info
;
275 notify_on_delete(delete_info_ref
->image_id
, error_code
);
276 delete_info_ref
->error_code
= error_code
;
277 ++delete_info_ref
->retries
;
278 delete_info_ref
->retry_time
= (clock_t::now() +
279 ceph::make_timespan(retry_delay
));
280 m_retry_delete_queue
.push_back(delete_info_ref
);
282 schedule_retry_timer();
285 template <typename I
>
286 typename ImageDeleter
<I
>::DeleteInfoRef
287 ImageDeleter
<I
>::find_delete_info(const std::string
&image_id
) {
288 ceph_assert(ceph_mutex_is_locked(m_lock
));
289 DeleteQueue delete_queues
[] = {m_in_flight_delete_queue
,
290 m_retry_delete_queue
,
293 DeleteInfo delete_info
{image_id
};
294 for (auto& queue
: delete_queues
) {
295 auto it
= std::find_if(queue
.begin(), queue
.end(),
296 [&delete_info
](const DeleteInfoRef
& ref
) {
297 return delete_info
== *ref
;
299 if (it
!= queue
.end()) {
306 template <typename I
>
307 void ImageDeleter
<I
>::print_status(Formatter
*f
) {
310 f
->open_object_section("image_deleter_status");
311 f
->open_array_section("delete_images_queue");
313 std::lock_guard l
{m_lock
};
314 for (const auto& image
: m_delete_queue
) {
315 image
->print_status(f
);
319 f
->open_array_section("failed_deletes_queue");
320 for (const auto& image
: m_retry_delete_queue
) {
321 image
->print_status(f
, true);
328 template <typename I
>
329 vector
<string
> ImageDeleter
<I
>::get_delete_queue_items() {
330 vector
<string
> items
;
332 std::lock_guard l
{m_lock
};
333 for (const auto& del_info
: m_delete_queue
) {
334 items
.push_back(del_info
->image_id
);
340 template <typename I
>
341 vector
<pair
<string
, int> > ImageDeleter
<I
>::get_failed_queue_items() {
342 vector
<pair
<string
, int> > items
;
344 std::lock_guard l
{m_lock
};
345 for (const auto& del_info
: m_retry_delete_queue
) {
346 items
.push_back(make_pair(del_info
->image_id
,
347 del_info
->error_code
));
353 template <typename I
>
354 void ImageDeleter
<I
>::remove_images() {
357 std::lock_guard locker
{m_lock
};
358 while (m_running
&& !m_delete_queue
.empty()) {
360 DeleteInfoRef delete_info
= m_delete_queue
.front();
361 m_delete_queue
.pop_front();
363 ceph_assert(delete_info
);
365 auto on_start
= create_async_context_callback(
366 m_threads
->work_queue
, new LambdaContext(
367 [this, delete_info
](int r
) {
369 notify_on_delete(delete_info
->image_id
, r
);
372 remove_image(delete_info
);
375 m_image_deletion_throttler
->start_op(m_local_io_ctx
.get_namespace(),
376 delete_info
->image_id
, on_start
);
380 template <typename I
>
381 void ImageDeleter
<I
>::remove_image(DeleteInfoRef delete_info
) {
382 dout(10) << "info=" << *delete_info
<< dendl
;
384 std::lock_guard locker
{m_lock
};
386 m_in_flight_delete_queue
.push_back(delete_info
);
387 m_async_op_tracker
.start_op();
389 auto ctx
= new LambdaContext([this, delete_info
](int r
) {
390 handle_remove_image(delete_info
, r
);
391 m_async_op_tracker
.finish_op();
394 auto req
= image_deleter::TrashRemoveRequest
<I
>::create(
395 m_local_io_ctx
, delete_info
->image_id
, &delete_info
->error_result
,
396 m_threads
->work_queue
, ctx
);
400 template <typename I
>
401 void ImageDeleter
<I
>::handle_remove_image(DeleteInfoRef delete_info
,
403 dout(10) << "info=" << *delete_info
<< ", r=" << r
<< dendl
;
405 m_image_deletion_throttler
->finish_op(m_local_io_ctx
.get_namespace(),
406 delete_info
->image_id
);
408 std::lock_guard locker
{m_lock
};
409 ceph_assert(ceph_mutex_is_locked(m_lock
));
410 auto it
= std::find(m_in_flight_delete_queue
.begin(),
411 m_in_flight_delete_queue
.end(), delete_info
);
412 ceph_assert(it
!= m_in_flight_delete_queue
.end());
413 m_in_flight_delete_queue
.erase(it
);
417 if (delete_info
->error_result
== image_deleter::ERROR_RESULT_COMPLETE
) {
418 complete_active_delete(&delete_info
, r
);
419 } else if (delete_info
->error_result
==
420 image_deleter::ERROR_RESULT_RETRY_IMMEDIATELY
) {
421 enqueue_failed_delete(&delete_info
, r
, m_busy_interval
);
423 auto cct
= reinterpret_cast<CephContext
*>(m_local_io_ctx
.cct());
424 double failed_interval
= cct
->_conf
.get_val
<double>(
425 "rbd_mirror_delete_retry_interval");
426 enqueue_failed_delete(&delete_info
, r
, failed_interval
);
429 complete_active_delete(&delete_info
, 0);
432 // process the next queued image to delete
436 template <typename I
>
437 void ImageDeleter
<I
>::schedule_retry_timer() {
438 ceph_assert(ceph_mutex_is_locked(m_threads
->timer_lock
));
439 ceph_assert(ceph_mutex_is_locked(m_lock
));
440 if (!m_running
|| m_timer_ctx
!= nullptr || m_retry_delete_queue
.empty()) {
445 auto &delete_info
= m_retry_delete_queue
.front();
446 m_timer_ctx
= new LambdaContext([this](int r
) {
447 handle_retry_timer();
449 m_threads
->timer
->add_event_at(delete_info
->retry_time
, m_timer_ctx
);
452 template <typename I
>
453 void ImageDeleter
<I
>::cancel_retry_timer() {
455 ceph_assert(ceph_mutex_is_locked(m_threads
->timer_lock
));
456 if (m_timer_ctx
!= nullptr) {
457 bool canceled
= m_threads
->timer
->cancel_event(m_timer_ctx
);
458 m_timer_ctx
= nullptr;
459 ceph_assert(canceled
);
463 template <typename I
>
464 void ImageDeleter
<I
>::handle_retry_timer() {
466 ceph_assert(ceph_mutex_is_locked(m_threads
->timer_lock
));
467 std::lock_guard locker
{m_lock
};
469 ceph_assert(m_timer_ctx
!= nullptr);
470 m_timer_ctx
= nullptr;
472 ceph_assert(m_running
);
473 ceph_assert(!m_retry_delete_queue
.empty());
475 // move all ready-to-ready items back to main queue
476 auto now
= clock_t::now();
477 while (!m_retry_delete_queue
.empty()) {
478 auto &delete_info
= m_retry_delete_queue
.front();
479 if (delete_info
->retry_time
> now
) {
483 m_delete_queue
.push_back(delete_info
);
484 m_retry_delete_queue
.pop_front();
487 // schedule wake up for any future retries
488 schedule_retry_timer();
490 // start (concurrent) removal of images
491 m_async_op_tracker
.start_op();
492 auto ctx
= new LambdaContext([this](int r
) {
494 m_async_op_tracker
.finish_op();
496 m_threads
->work_queue
->queue(ctx
, 0);
499 template <typename I
>
500 void ImageDeleter
<I
>::handle_trash_image(const std::string
& image_id
,
501 const ImageDeleter
<I
>::clock_t::time_point
& deferment_end_time
) {
502 std::scoped_lock locker
{m_threads
->timer_lock
, m_lock
};
504 auto del_info
= find_delete_info(image_id
);
505 if (del_info
!= nullptr) {
506 dout(20) << "image " << image_id
<< " "
507 << "was already scheduled for deletion" << dendl
;
511 dout(10) << "image_id=" << image_id
<< ", "
512 << "deferment_end_time=" << utime_t
{deferment_end_time
} << dendl
;
514 del_info
.reset(new DeleteInfo(image_id
));
515 del_info
->retry_time
= deferment_end_time
;
516 m_retry_delete_queue
.push_back(del_info
);
518 schedule_retry_timer();
521 template <typename I
>
522 void ImageDeleter
<I
>::notify_on_delete(const std::string
& image_id
,
524 dout(10) << "image_id=" << image_id
<< ", r=" << r
<< dendl
;
525 auto it
= m_on_delete_contexts
.find(image_id
);
526 if (it
== m_on_delete_contexts
.end()) {
530 it
->second
->complete(r
);
531 m_on_delete_contexts
.erase(it
);
534 template <typename I
>
535 void ImageDeleter
<I
>::DeleteInfo::print_status(Formatter
*f
,
536 bool print_failure_info
) {
537 f
->open_object_section("delete_info");
538 f
->dump_string("image_id", image_id
);
539 if (print_failure_info
) {
540 f
->dump_string("error_code", cpp_strerror(error_code
));
541 f
->dump_int("retries", retries
);
546 } // namespace mirror
549 template class rbd::mirror::ImageDeleter
<librbd::ImageCtx
>;