1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 #include "test/librbd/test_mock_fixture.h"
5 #include "test/librbd/test_support.h"
6 #include "test/librbd/mock/MockImageCtx.h"
7 #include "test/librbd/mock/MockContextWQ.h"
8 #include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
9 #include "test/librados_test_stub/MockTestMemRadosClient.h"
10 #include "librbd/image/TypeTraits.h"
11 #include "librbd/image/DetachChildRequest.h"
12 #include "librbd/trash/RemoveRequest.h"
13 #include "gmock/gmock.h"
14 #include "gtest/gtest.h"
19 struct MockTestImageCtx
: public MockImageCtx
{
20 static MockTestImageCtx
* s_instance
;
21 static MockTestImageCtx
* create(const std::string
&image_name
,
22 const std::string
&image_id
,
23 const char *snap
, librados::IoCtx
& p
,
25 ceph_assert(s_instance
!= nullptr);
29 MockTestImageCtx(ImageCtx
&image_ctx
) : MockImageCtx(image_ctx
) {
34 MockTestImageCtx
* MockTestImageCtx::s_instance
= nullptr;
36 } // anonymous namespace
41 struct TypeTraits
<MockTestImageCtx
> {
42 typedef librbd::MockContextWQ ContextWQ
;
50 class RemoveRequest
<MockTestImageCtx
> {
52 typedef ::librbd::image::TypeTraits
<MockTestImageCtx
> TypeTraits
;
53 typedef typename
TypeTraits::ContextWQ ContextWQ
;
55 static RemoveRequest
*s_instance
;
56 static RemoveRequest
*create(librados::IoCtx
&ioctx
,
57 MockTestImageCtx
*image_ctx
,
58 ContextWQ
*op_work_queue
, bool force
,
59 ProgressContext
&prog_ctx
, Context
*on_finish
) {
60 ceph_assert(s_instance
!= nullptr);
61 s_instance
->on_finish
= on_finish
;
65 Context
*on_finish
= nullptr;
71 MOCK_METHOD0(send
, void());
74 RemoveRequest
<MockTestImageCtx
> *RemoveRequest
<MockTestImageCtx
>::s_instance
;
79 // template definitions
80 #include "librbd/image/DetachChildRequest.cc"
86 using ::testing::DoAll
;
87 using ::testing::DoDefault
;
88 using ::testing::InSequence
;
89 using ::testing::Invoke
;
90 using ::testing::Return
;
91 using ::testing::StrEq
;
92 using ::testing::WithArg
;
94 class TestMockImageDetachChildRequest
: public TestMockFixture
{
96 typedef DetachChildRequest
<MockTestImageCtx
> MockDetachChildRequest
;
97 typedef trash::RemoveRequest
<MockTestImageCtx
> MockTrashRemoveRequest
;
99 void SetUp() override
{
100 TestMockFixture::SetUp();
102 ASSERT_EQ(0, open_image(m_image_name
, &image_ctx
));
105 void expect_test_op_features(MockTestImageCtx
& mock_image_ctx
, bool enabled
) {
106 EXPECT_CALL(mock_image_ctx
,
107 test_op_features(RBD_OPERATION_FEATURE_CLONE_CHILD
))
108 .WillOnce(Return(enabled
));
111 void expect_create_ioctx(MockImageCtx
&mock_image_ctx
,
112 librados::MockTestMemIoCtxImpl
**io_ctx_impl
) {
113 *io_ctx_impl
= &get_mock_io_ctx(mock_image_ctx
.md_ctx
);
114 auto rados_client
= (*io_ctx_impl
)->get_mock_rados_client();
116 EXPECT_CALL(*rados_client
, create_ioctx(_
, _
))
117 .WillOnce(DoAll(GetReference(*io_ctx_impl
), Return(*io_ctx_impl
)));
120 void expect_child_detach(MockImageCtx
&mock_image_ctx
,
121 librados::MockTestMemIoCtxImpl
&mock_io_ctx_impl
,
123 auto& parent_spec
= mock_image_ctx
.parent_md
.spec
;
126 encode(parent_spec
.snap_id
, bl
);
127 encode(cls::rbd::ChildImageSpec
{mock_image_ctx
.md_ctx
.get_id(), "",
128 mock_image_ctx
.id
}, bl
);
130 EXPECT_CALL(mock_io_ctx_impl
,
131 exec(util::header_name(parent_spec
.image_id
),
132 _
, StrEq("rbd"), StrEq("child_detach"), ContentsEqual(bl
),
134 .WillOnce(Return(r
));
137 void expect_remove_child(MockImageCtx
&mock_image_ctx
, int r
) {
138 EXPECT_CALL(get_mock_io_ctx(mock_image_ctx
.md_ctx
),
139 exec(RBD_CHILDREN
, _
, StrEq("rbd"), StrEq("remove_child"), _
,
141 .WillOnce(Return(r
));
144 void expect_snapshot_get(MockImageCtx
&mock_image_ctx
,
145 librados::MockTestMemIoCtxImpl
&mock_io_ctx_impl
,
146 const std::string
& parent_header_name
,
147 const cls::rbd::SnapshotInfo
& snap_info
, int r
) {
150 EXPECT_CALL(mock_io_ctx_impl
,
151 exec(parent_header_name
, _
, StrEq("rbd"),
152 StrEq("snapshot_get"), _
, _
, _
, _
))
153 .WillOnce(WithArg
<5>(Invoke([snap_info
, r
](bufferlist
* bl
) {
154 encode(snap_info
, *bl
);
159 void expect_open(MockImageCtx
&mock_image_ctx
, int r
) {
160 EXPECT_CALL(*mock_image_ctx
.state
, open(true, _
))
161 .WillOnce(WithArg
<1>(Invoke([this, &mock_image_ctx
, r
](Context
* ctx
) {
162 EXPECT_EQ(0U, mock_image_ctx
.read_only_mask
&
163 IMAGE_READ_ONLY_FLAG_NON_PRIMARY
);
164 image_ctx
->op_work_queue
->queue(ctx
, r
);
167 EXPECT_CALL(mock_image_ctx
, test_features(_
))
168 .WillOnce(Return(false));
172 void expect_close(MockImageCtx
&mock_image_ctx
, int r
) {
173 EXPECT_CALL(*mock_image_ctx
.state
, close(_
))
174 .WillOnce(Invoke([this, r
](Context
* ctx
) {
175 image_ctx
->op_work_queue
->queue(ctx
, r
);
179 void expect_snap_remove(MockImageCtx
&mock_image_ctx
,
180 const std::string
&snap_name
, int r
) {
181 EXPECT_CALL(*mock_image_ctx
.operations
,
182 snap_remove({cls::rbd::TrashSnapshotNamespace
{}},
183 StrEq(snap_name
), _
))
184 .WillOnce(WithArg
<2>(Invoke([this, r
](Context
*ctx
) {
185 image_ctx
->op_work_queue
->queue(ctx
, r
);
189 void expect_trash_get(MockImageCtx
&mock_image_ctx
,
190 librados::MockTestMemIoCtxImpl
&mock_io_ctx_impl
,
191 const cls::rbd::TrashImageSpec
& trash_spec
,
194 EXPECT_CALL(mock_io_ctx_impl
,
195 exec(RBD_TRASH
, _
, StrEq("rbd"),
196 StrEq("trash_get"), _
, _
, _
, _
))
197 .WillOnce(WithArg
<5>(Invoke([trash_spec
, r
](bufferlist
* bl
) {
198 encode(trash_spec
, *bl
);
203 void expect_trash_remove(MockTrashRemoveRequest
& mock_trash_remove_request
,
205 EXPECT_CALL(mock_trash_remove_request
, send())
206 .WillOnce(Invoke([&mock_trash_remove_request
, r
]() {
207 mock_trash_remove_request
.on_finish
->complete(r
);
211 librbd::ImageCtx
*image_ctx
;
214 TEST_F(TestMockImageDetachChildRequest
, SuccessV1
) {
215 REQUIRE_FEATURE(RBD_FEATURE_LAYERING
);
217 MockTestImageCtx
mock_image_ctx(*image_ctx
);
218 mock_image_ctx
.parent_md
.spec
= {m_ioctx
.get_id(), "", "parent id", 234};
221 expect_test_op_features(mock_image_ctx
, false);
222 expect_remove_child(mock_image_ctx
, 0);
225 auto req
= MockDetachChildRequest::create(mock_image_ctx
, &ctx
);
227 ASSERT_EQ(0, ctx
.wait());
230 TEST_F(TestMockImageDetachChildRequest
, SuccessV2
) {
231 REQUIRE_FEATURE(RBD_FEATURE_LAYERING
);
233 MockTestImageCtx
mock_image_ctx(*image_ctx
);
234 mock_image_ctx
.parent_md
.spec
= {m_ioctx
.get_id(), "", "parent id", 234};
237 expect_test_op_features(mock_image_ctx
, true);
239 librados::MockTestMemIoCtxImpl
*mock_io_ctx_impl
;
240 expect_create_ioctx(mock_image_ctx
, &mock_io_ctx_impl
);
241 expect_child_detach(mock_image_ctx
, *mock_io_ctx_impl
, 0);
242 expect_snapshot_get(mock_image_ctx
, *mock_io_ctx_impl
,
243 "rbd_header.parent id",
244 {234, {cls::rbd::UserSnapshotNamespace
{}},
245 "snap1", 123, {}, 0}, 0);
248 auto req
= MockDetachChildRequest::create(mock_image_ctx
, &ctx
);
250 ASSERT_EQ(0, ctx
.wait());
253 TEST_F(TestMockImageDetachChildRequest
, TrashedSnapshotSuccess
) {
254 REQUIRE_FEATURE(RBD_FEATURE_LAYERING
);
256 MockTestImageCtx
mock_image_ctx(*image_ctx
);
257 mock_image_ctx
.parent_md
.spec
= {m_ioctx
.get_id(), "", "parent id", 234};
260 expect_test_op_features(mock_image_ctx
, true);
262 librados::MockTestMemIoCtxImpl
*mock_io_ctx_impl
;
263 expect_create_ioctx(mock_image_ctx
, &mock_io_ctx_impl
);
264 expect_child_detach(mock_image_ctx
, *mock_io_ctx_impl
, 0);
265 expect_snapshot_get(mock_image_ctx
, *mock_io_ctx_impl
,
266 "rbd_header.parent id",
267 {234, {cls::rbd::TrashSnapshotNamespace
{}},
268 "snap1", 123, {}, 0}, 0);
269 expect_open(mock_image_ctx
, 0);
270 expect_snap_remove(mock_image_ctx
, "snap1", 0);
271 const cls::rbd::TrashImageSpec trash_spec
;
272 expect_trash_get(mock_image_ctx
, *mock_io_ctx_impl
, trash_spec
, -ENOENT
);
273 expect_close(mock_image_ctx
, 0);
276 auto req
= MockDetachChildRequest::create(mock_image_ctx
, &ctx
);
278 ASSERT_EQ(0, ctx
.wait());
281 TEST_F(TestMockImageDetachChildRequest
, ParentAutoRemove
) {
282 REQUIRE_FEATURE(RBD_FEATURE_LAYERING
);
284 MockTestImageCtx
mock_image_ctx(*image_ctx
);
285 mock_image_ctx
.parent_md
.spec
= {m_ioctx
.get_id(), "", "parent id", 234};
288 expect_test_op_features(mock_image_ctx
, true);
290 librados::MockTestMemIoCtxImpl
*mock_io_ctx_impl
;
291 expect_create_ioctx(mock_image_ctx
, &mock_io_ctx_impl
);
292 expect_child_detach(mock_image_ctx
, *mock_io_ctx_impl
, 0);
293 expect_snapshot_get(mock_image_ctx
, *mock_io_ctx_impl
,
294 "rbd_header.parent id",
295 {234, {cls::rbd::TrashSnapshotNamespace
{}},
296 "snap1", 123, {}, 0}, 0);
297 expect_open(mock_image_ctx
, 0);
298 expect_snap_remove(mock_image_ctx
, "snap1", 0);
299 const cls::rbd::TrashImageSpec trash_spec
=
300 {cls::rbd::TRASH_IMAGE_SOURCE_USER_PARENT
, "parent", {}, {}};
302 expect_trash_get(mock_image_ctx
, *mock_io_ctx_impl
, trash_spec
, 0);
303 MockTrashRemoveRequest mock_trash_remove_request
;
304 expect_trash_remove(mock_trash_remove_request
, 0);
307 auto req
= MockDetachChildRequest::create(mock_image_ctx
, &ctx
);
309 ASSERT_EQ(0, ctx
.wait());
312 TEST_F(TestMockImageDetachChildRequest
, TrashedSnapshotInUse
) {
313 REQUIRE_FEATURE(RBD_FEATURE_LAYERING
);
315 MockTestImageCtx
mock_image_ctx(*image_ctx
);
316 mock_image_ctx
.parent_md
.spec
= {m_ioctx
.get_id(), "", "parent id", 234};
319 expect_test_op_features(mock_image_ctx
, true);
321 librados::MockTestMemIoCtxImpl
*mock_io_ctx_impl
;
322 expect_create_ioctx(mock_image_ctx
, &mock_io_ctx_impl
);
323 expect_child_detach(mock_image_ctx
, *mock_io_ctx_impl
, 0);
324 expect_snapshot_get(mock_image_ctx
, *mock_io_ctx_impl
,
325 "rbd_header.parent id",
326 {234, {cls::rbd::TrashSnapshotNamespace
{}},
327 "snap1", 123, {}, 1}, 0);
330 auto req
= MockDetachChildRequest::create(mock_image_ctx
, &ctx
);
332 ASSERT_EQ(0, ctx
.wait());
335 TEST_F(TestMockImageDetachChildRequest
, TrashedSnapshotSnapshotGetError
) {
336 REQUIRE_FEATURE(RBD_FEATURE_LAYERING
);
338 MockTestImageCtx
mock_image_ctx(*image_ctx
);
339 mock_image_ctx
.parent_md
.spec
= {m_ioctx
.get_id(), "", "parent id", 234};
342 expect_test_op_features(mock_image_ctx
, true);
344 librados::MockTestMemIoCtxImpl
*mock_io_ctx_impl
;
345 expect_create_ioctx(mock_image_ctx
, &mock_io_ctx_impl
);
346 expect_child_detach(mock_image_ctx
, *mock_io_ctx_impl
, 0);
347 expect_snapshot_get(mock_image_ctx
, *mock_io_ctx_impl
,
348 "rbd_header.parent id",
349 {234, {cls::rbd::TrashSnapshotNamespace
{}},
350 "snap1", 123, {}, 0}, -EINVAL
);
353 auto req
= MockDetachChildRequest::create(mock_image_ctx
, &ctx
);
355 ASSERT_EQ(0, ctx
.wait());
358 TEST_F(TestMockImageDetachChildRequest
, TrashedSnapshotOpenParentError
) {
359 REQUIRE_FEATURE(RBD_FEATURE_LAYERING
);
361 MockTestImageCtx
mock_image_ctx(*image_ctx
);
362 mock_image_ctx
.parent_md
.spec
= {m_ioctx
.get_id(), "", "parent id", 234};
365 expect_test_op_features(mock_image_ctx
, true);
367 librados::MockTestMemIoCtxImpl
*mock_io_ctx_impl
;
368 expect_create_ioctx(mock_image_ctx
, &mock_io_ctx_impl
);
369 expect_child_detach(mock_image_ctx
, *mock_io_ctx_impl
, 0);
370 expect_snapshot_get(mock_image_ctx
, *mock_io_ctx_impl
,
371 "rbd_header.parent id",
372 {234, {cls::rbd::TrashSnapshotNamespace
{}},
373 "snap1", 123, {}, 0}, 0);
374 expect_open(mock_image_ctx
, -EPERM
);
377 auto req
= MockDetachChildRequest::create(mock_image_ctx
, &ctx
);
379 ASSERT_EQ(0, ctx
.wait());
382 TEST_F(TestMockImageDetachChildRequest
, TrashedSnapshotRemoveError
) {
383 REQUIRE_FEATURE(RBD_FEATURE_LAYERING
);
385 MockTestImageCtx
mock_image_ctx(*image_ctx
);
386 mock_image_ctx
.parent_md
.spec
= {m_ioctx
.get_id(), "", "parent id", 234};
389 expect_test_op_features(mock_image_ctx
, true);
391 librados::MockTestMemIoCtxImpl
*mock_io_ctx_impl
;
392 expect_create_ioctx(mock_image_ctx
, &mock_io_ctx_impl
);
393 expect_child_detach(mock_image_ctx
, *mock_io_ctx_impl
, 0);
394 expect_snapshot_get(mock_image_ctx
, *mock_io_ctx_impl
,
395 "rbd_header.parent id",
396 {234, {cls::rbd::TrashSnapshotNamespace
{}},
397 "snap1", 123, {}, 0}, 0);
398 expect_open(mock_image_ctx
, 0);
399 expect_snap_remove(mock_image_ctx
, "snap1", -EPERM
);
400 expect_close(mock_image_ctx
, -EPERM
);
403 auto req
= MockDetachChildRequest::create(mock_image_ctx
, &ctx
);
405 ASSERT_EQ(0, ctx
.wait());
408 TEST_F(TestMockImageDetachChildRequest
, ParentDNE
) {
409 MockTestImageCtx
mock_image_ctx(*image_ctx
);
410 expect_op_work_queue(mock_image_ctx
);
413 auto req
= MockDetachChildRequest::create(mock_image_ctx
, &ctx
);
415 ASSERT_EQ(0, ctx
.wait());
418 TEST_F(TestMockImageDetachChildRequest
, ChildDetachError
) {
419 REQUIRE_FEATURE(RBD_FEATURE_LAYERING
);
421 MockTestImageCtx
mock_image_ctx(*image_ctx
);
422 mock_image_ctx
.parent_md
.spec
= {m_ioctx
.get_id(), "", "parent id", 234};
425 expect_test_op_features(mock_image_ctx
, true);
427 librados::MockTestMemIoCtxImpl
*mock_io_ctx_impl
;
428 expect_create_ioctx(mock_image_ctx
, &mock_io_ctx_impl
);
429 expect_child_detach(mock_image_ctx
, *mock_io_ctx_impl
, -EPERM
);
432 auto req
= MockDetachChildRequest::create(mock_image_ctx
, &ctx
);
434 ASSERT_EQ(-EPERM
, ctx
.wait());
437 TEST_F(TestMockImageDetachChildRequest
, RemoveChildError
) {
438 REQUIRE_FEATURE(RBD_FEATURE_LAYERING
);
440 MockTestImageCtx
mock_image_ctx(*image_ctx
);
441 mock_image_ctx
.parent_md
.spec
= {m_ioctx
.get_id(), "", "parent id", 234};
444 expect_test_op_features(mock_image_ctx
, false);
445 expect_remove_child(mock_image_ctx
, -EINVAL
);
448 auto req
= MockDetachChildRequest::create(mock_image_ctx
, &ctx
);
450 ASSERT_EQ(-EINVAL
, ctx
.wait());
454 } // namespace librbd