1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 #include "librbd/operation/TrimRequest.h"
5 #include "librbd/AsyncObjectThrottle.h"
6 #include "librbd/ExclusiveLock.h"
7 #include "librbd/ImageCtx.h"
8 #include "librbd/internal.h"
9 #include "librbd/ObjectMap.h"
10 #include "librbd/Utils.h"
11 #include "librbd/io/ObjectDispatchSpec.h"
12 #include "librbd/io/ObjectDispatcherInterface.h"
13 #include "common/ContextCompletion.h"
14 #include "common/dout.h"
15 #include "common/errno.h"
16 #include "osdc/Striper.h"
18 #include <boost/lambda/bind.hpp>
19 #include <boost/lambda/construct.hpp>
20 #include <boost/scope_exit.hpp>
22 #define dout_subsys ceph_subsys_rbd
24 #define dout_prefix *_dout << "librbd::TrimRequest: "
30 class C_CopyupObject
: public C_AsyncObjectThrottle
<I
> {
32 C_CopyupObject(AsyncObjectThrottle
<I
> &throttle
, I
*image_ctx
,
33 IOContext io_context
, uint64_t object_no
)
34 : C_AsyncObjectThrottle
<I
>(throttle
, *image_ctx
), m_io_context(io_context
),
35 m_object_no(object_no
)
40 I
&image_ctx
= this->m_image_ctx
;
41 ceph_assert(ceph_mutex_is_locked(image_ctx
.owner_lock
));
42 ceph_assert(image_ctx
.exclusive_lock
== nullptr ||
43 image_ctx
.exclusive_lock
->is_lock_owner());
45 std::string oid
= image_ctx
.get_object_name(m_object_no
);
46 ldout(image_ctx
.cct
, 10) << "removing (with copyup) " << oid
<< dendl
;
48 auto object_dispatch_spec
= io::ObjectDispatchSpec::create_discard(
49 &image_ctx
, io::OBJECT_DISPATCH_LAYER_NONE
, m_object_no
, 0,
50 image_ctx
.layout
.object_size
, m_io_context
,
51 io::OBJECT_DISCARD_FLAG_DISABLE_OBJECT_MAP_UPDATE
, 0, {}, this);
52 object_dispatch_spec
->send();
56 IOContext m_io_context
;
61 class C_RemoveObject
: public C_AsyncObjectThrottle
<I
> {
63 C_RemoveObject(AsyncObjectThrottle
<I
> &throttle
, I
*image_ctx
,
65 : C_AsyncObjectThrottle
<I
>(throttle
, *image_ctx
), m_object_no(object_no
)
70 I
&image_ctx
= this->m_image_ctx
;
71 ceph_assert(ceph_mutex_is_locked(image_ctx
.owner_lock
));
72 ceph_assert(image_ctx
.exclusive_lock
== nullptr ||
73 image_ctx
.exclusive_lock
->is_lock_owner());
76 std::shared_lock image_locker
{image_ctx
.image_lock
};
77 if (image_ctx
.object_map
!= nullptr &&
78 !image_ctx
.object_map
->object_may_exist(m_object_no
)) {
83 std::string oid
= image_ctx
.get_object_name(m_object_no
);
84 ldout(image_ctx
.cct
, 10) << "removing " << oid
<< dendl
;
86 librados::AioCompletion
*rados_completion
=
87 util::create_rados_callback(this);
88 int r
= image_ctx
.data_ctx
.aio_remove(oid
, rados_completion
);
90 rados_completion
->release();
99 TrimRequest
<I
>::TrimRequest(I
&image_ctx
, Context
*on_finish
,
100 uint64_t original_size
, uint64_t new_size
,
101 ProgressContext
&prog_ctx
)
102 : AsyncRequest
<I
>(image_ctx
, on_finish
), m_new_size(new_size
),
105 uint64_t period
= image_ctx
.get_stripe_period();
106 uint64_t new_num_periods
= ((m_new_size
+ period
- 1) / period
);
107 m_delete_off
= std::min(new_num_periods
* period
, original_size
);
108 // first object we can delete free and clear
109 m_delete_start
= new_num_periods
* image_ctx
.get_stripe_count();
110 m_delete_start_min
= m_delete_start
;
111 m_num_objects
= Striper::get_num_objects(image_ctx
.layout
, original_size
);
113 CephContext
*cct
= image_ctx
.cct
;
114 ldout(cct
, 10) << this << " trim image " << original_size
<< " -> "
115 << m_new_size
<< " periods " << new_num_periods
116 << " discard to offset " << m_delete_off
117 << " delete objects " << m_delete_start
118 << " to " << m_num_objects
<< dendl
;
121 template <typename I
>
122 bool TrimRequest
<I
>::should_complete(int r
)
124 I
&image_ctx
= this->m_image_ctx
;
125 CephContext
*cct
= image_ctx
.cct
;
126 ldout(cct
, 5) << this << " should_complete: r=" << r
<< dendl
;
127 if (r
== -ERESTART
) {
128 ldout(cct
, 5) << "trim operation interrupted" << dendl
;
131 lderr(cct
) << "trim encountered an error: " << cpp_strerror(r
) << dendl
;
135 std::shared_lock owner_lock
{image_ctx
.owner_lock
};
138 ldout(cct
, 5) << " PRE_TRIM" << dendl
;
139 send_copyup_objects();
142 case STATE_COPYUP_OBJECTS
:
143 ldout(cct
, 5) << " COPYUP_OBJECTS" << dendl
;
144 send_remove_objects();
147 case STATE_REMOVE_OBJECTS
:
148 ldout(cct
, 5) << " REMOVE_OBJECTS" << dendl
;
152 case STATE_POST_TRIM
:
153 ldout(cct
, 5) << " POST_TRIM" << dendl
;
154 send_clean_boundary();
157 case STATE_CLEAN_BOUNDARY
:
158 ldout(cct
, 5) << "CLEAN_BOUNDARY" << dendl
;
163 ldout(cct
, 5) << "FINISHED" << dendl
;
167 lderr(cct
) << "invalid state: " << m_state
<< dendl
;
174 template <typename I
>
175 void TrimRequest
<I
>::send() {
176 I
&image_ctx
= this->m_image_ctx
;
177 CephContext
*cct
= image_ctx
.cct
;
179 if (!image_ctx
.data_ctx
.is_valid()) {
180 lderr(cct
) << "missing data pool" << dendl
;
181 send_finish(-ENODEV
);
189 void TrimRequest
<I
>::send_pre_trim() {
190 I
&image_ctx
= this->m_image_ctx
;
191 ceph_assert(ceph_mutex_is_locked(image_ctx
.owner_lock
));
193 if (m_delete_start
>= m_num_objects
) {
194 send_clean_boundary();
199 std::shared_lock image_locker
{image_ctx
.image_lock
};
200 if (image_ctx
.object_map
!= nullptr) {
201 ldout(image_ctx
.cct
, 5) << this << " send_pre_trim: "
202 << " delete_start_min=" << m_delete_start_min
203 << " num_objects=" << m_num_objects
<< dendl
;
204 m_state
= STATE_PRE_TRIM
;
206 ceph_assert(image_ctx
.exclusive_lock
->is_lock_owner());
208 if (image_ctx
.object_map
->template aio_update
<AsyncRequest
<I
> >(
209 CEPH_NOSNAP
, m_delete_start_min
, m_num_objects
, OBJECT_PENDING
,
210 OBJECT_EXISTS
, {}, false, this)) {
216 send_copyup_objects();
220 void TrimRequest
<I
>::send_copyup_objects() {
221 I
&image_ctx
= this->m_image_ctx
;
222 ceph_assert(ceph_mutex_is_locked(image_ctx
.owner_lock
));
224 IOContext io_context
;
226 uint64_t parent_overlap
;
228 std::shared_lock image_locker
{image_ctx
.image_lock
};
230 io_context
= image_ctx
.get_data_io_context();
231 has_snapshots
= !image_ctx
.snaps
.empty();
232 int r
= image_ctx
.get_parent_overlap(CEPH_NOSNAP
, &parent_overlap
);
236 // copyup is only required for portion of image that overlaps parent
237 uint64_t copyup_end
= Striper::get_num_objects(image_ctx
.layout
,
240 // TODO: protect against concurrent shrink and snap create?
241 // skip to remove if no copyup is required.
242 if (copyup_end
<= m_delete_start
|| !has_snapshots
) {
243 send_remove_objects();
247 uint64_t copyup_start
= m_delete_start
;
248 m_delete_start
= copyup_end
;
250 ldout(image_ctx
.cct
, 5) << this << " send_copyup_objects: "
251 << " start object=" << copyup_start
<< ", "
252 << " end object=" << copyup_end
<< dendl
;
253 m_state
= STATE_COPYUP_OBJECTS
;
255 Context
*ctx
= this->create_callback_context();
256 typename AsyncObjectThrottle
<I
>::ContextFactory
context_factory(
257 boost::lambda::bind(boost::lambda::new_ptr
<C_CopyupObject
<I
> >(),
258 boost::lambda::_1
, &image_ctx
, io_context
, boost::lambda::_2
));
259 AsyncObjectThrottle
<I
> *throttle
= new AsyncObjectThrottle
<I
>(
260 this, image_ctx
, context_factory
, ctx
, &m_prog_ctx
, copyup_start
,
263 image_ctx
.config
.template get_val
<uint64_t>("rbd_concurrent_management_ops"));
266 template <typename I
>
267 void TrimRequest
<I
>::send_remove_objects() {
268 I
&image_ctx
= this->m_image_ctx
;
269 ceph_assert(ceph_mutex_is_locked(image_ctx
.owner_lock
));
271 ldout(image_ctx
.cct
, 5) << this << " send_remove_objects: "
272 << " delete_start=" << m_delete_start
273 << " num_objects=" << m_num_objects
<< dendl
;
274 m_state
= STATE_REMOVE_OBJECTS
;
276 Context
*ctx
= this->create_callback_context();
277 typename AsyncObjectThrottle
<I
>::ContextFactory
context_factory(
278 boost::lambda::bind(boost::lambda::new_ptr
<C_RemoveObject
<I
> >(),
279 boost::lambda::_1
, &image_ctx
, boost::lambda::_2
));
280 AsyncObjectThrottle
<I
> *throttle
= new AsyncObjectThrottle
<I
>(
281 this, image_ctx
, context_factory
, ctx
, &m_prog_ctx
, m_delete_start
,
284 image_ctx
.config
.template get_val
<uint64_t>("rbd_concurrent_management_ops"));
288 void TrimRequest
<I
>::send_post_trim() {
289 I
&image_ctx
= this->m_image_ctx
;
290 ceph_assert(ceph_mutex_is_locked(image_ctx
.owner_lock
));
293 std::shared_lock image_locker
{image_ctx
.image_lock
};
294 if (image_ctx
.object_map
!= nullptr) {
295 ldout(image_ctx
.cct
, 5) << this << " send_post_trim:"
296 << " delete_start_min=" << m_delete_start_min
297 << " num_objects=" << m_num_objects
<< dendl
;
298 m_state
= STATE_POST_TRIM
;
300 ceph_assert(image_ctx
.exclusive_lock
->is_lock_owner());
302 if (image_ctx
.object_map
->template aio_update
<AsyncRequest
<I
> >(
303 CEPH_NOSNAP
, m_delete_start_min
, m_num_objects
, OBJECT_NONEXISTENT
,
304 OBJECT_PENDING
, {}, false, this)) {
310 send_clean_boundary();
313 template <typename I
>
314 void TrimRequest
<I
>::send_clean_boundary() {
315 I
&image_ctx
= this->m_image_ctx
;
316 ceph_assert(ceph_mutex_is_locked(image_ctx
.owner_lock
));
317 CephContext
*cct
= image_ctx
.cct
;
318 if (m_delete_off
<= m_new_size
) {
323 // should have been canceled prior to releasing lock
324 ceph_assert(image_ctx
.exclusive_lock
== nullptr ||
325 image_ctx
.exclusive_lock
->is_lock_owner());
326 uint64_t delete_len
= m_delete_off
- m_new_size
;
327 ldout(image_ctx
.cct
, 5) << this << " send_clean_boundary: "
328 << " delete_off=" << m_delete_off
329 << " length=" << delete_len
<< dendl
;
330 m_state
= STATE_CLEAN_BOUNDARY
;
332 IOContext io_context
;
334 std::shared_lock image_locker
{image_ctx
.image_lock
};
335 io_context
= image_ctx
.get_data_io_context();
338 // discard the weird boundary
339 std::vector
<ObjectExtent
> extents
;
340 Striper::file_to_extents(cct
, image_ctx
.format_string
,
341 &image_ctx
.layout
, m_new_size
, delete_len
, 0,
344 ContextCompletion
*completion
=
345 new ContextCompletion(this->create_async_callback_context(), true);
346 for (auto& extent
: extents
) {
347 ldout(cct
, 20) << " ex " << extent
<< dendl
;
348 Context
*req_comp
= new C_ContextCompletion(*completion
);
350 if (extent
.offset
== 0) {
351 // treat as a full object delete on the boundary
352 extent
.length
= image_ctx
.layout
.object_size
;
355 auto object_dispatch_spec
= io::ObjectDispatchSpec::create_discard(
356 &image_ctx
, io::OBJECT_DISPATCH_LAYER_NONE
, extent
.objectno
, extent
.offset
,
357 extent
.length
, io_context
, 0, 0, {}, req_comp
);
358 object_dispatch_spec
->send();
360 completion
->finish_adding_requests();
363 template <typename I
>
364 void TrimRequest
<I
>::send_finish(int r
) {
365 m_state
= STATE_FINISHED
;
366 this->async_complete(r
);
369 } // namespace operation
370 } // namespace librbd
372 template class librbd::operation::TrimRequest
<librbd::ImageCtx
>;