test/librbd/object_map/test_mock_SnapshotCreateRequest.cc \
test/librbd/object_map/test_mock_SnapshotRemoveRequest.cc \
test/librbd/object_map/test_mock_SnapshotRollbackRequest.cc \
- test/librbd/object_map/test_mock_UpdateRequest.cc
+ test/librbd/object_map/test_mock_UpdateRequest.cc \
+ test/librbd/operation/test_mock_SnapshotCreateRequest.cc \
+ test/librbd/operation/test_mock_SnapshotProtectRequest.cc \
+ test/librbd/operation/test_mock_SnapshotRemoveRequest.cc \
+ test/librbd/operation/test_mock_SnapshotUnprotectRequest.cc
unittest_librbd_CXXFLAGS = $(UNITTEST_CXXFLAGS) -DTEST_LIBRBD_INTERNALS
unittest_librbd_LDADD = \
librbd_test.la librbd_api.la librbd_internal.la $(LIBRBD_TYPES) \
test/librbd/test_fixture.h \
test/librbd/test_mock_fixture.h \
test/librbd/test_support.h \
+ test/librbd/mock/MockAioImageRequestWQ.h \
test/librbd/mock/MockContextWQ.h \
test/librbd/mock/MockImageCtx.h \
test/librbd/mock/MockImageWatcher.h \
+ test/librbd/mock/MockJournal.h \
test/librbd/mock/MockObjectMap.h
if LINUX
--- /dev/null
+// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_TEST_LIBRBD_MOCK_AIO_IMAGE_REQUEST_WQ_H
+#define CEPH_TEST_LIBRBD_MOCK_AIO_IMAGE_REQUEST_WQ_H
+
+#include "gmock/gmock.h"
+
+namespace librbd {
+
+struct MockAioImageRequestWQ {
+ MOCK_METHOD1(block_writes, void(Context *));
+ MOCK_METHOD0(unblock_writes, void());
+};
+
+} // namespace librbd
+
+#endif // CEPH_TEST_LIBRBD_MOCK_AIO_IMAGE_REQUEST_WQ_H
#ifndef CEPH_TEST_LIBRBD_MOCK_IMAGE_CTX_H
#define CEPH_TEST_LIBRBD_MOCK_IMAGE_CTX_H
+#include "test/librbd/mock/MockAioImageRequestWQ.h"
#include "test/librbd/mock/MockContextWQ.h"
#include "test/librbd/mock/MockImageWatcher.h"
+#include "test/librbd/mock/MockJournal.h"
#include "test/librbd/mock/MockObjectMap.h"
#include "common/RWLock.h"
#include "librbd/ImageCtx.h"
header_oid(image_ctx.header_oid),
id(image_ctx.id),
parent_md(image_ctx.parent_md),
- aio_work_queue(new MockContextWQ()),
+ aio_work_queue(new MockAioImageRequestWQ()),
op_work_queue(new MockContextWQ()),
- image_watcher(NULL),
+ image_watcher(NULL), journal(NULL),
concurrent_management_ops(image_ctx.concurrent_management_ops)
{
md_ctx.dup(image_ctx.md_ctx);
xlist<AsyncRequest<MockImageCtx>*> async_requests;
Cond async_requests_cond;
- MockContextWQ *aio_work_queue;
+ MockAioImageRequestWQ *aio_work_queue;
MockContextWQ *op_work_queue;
MockImageWatcher *image_watcher;
MockObjectMap object_map;
+ MockJournal *journal;
+
int concurrent_management_ops;
};
--- /dev/null
+// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_TEST_LIBRBD_MOCK_JOURNAL_H
+#define CEPH_TEST_LIBRBD_MOCK_JOURNAL_H
+
+#include "gmock/gmock.h"
+#include "librbd/Journal.h"
+
+namespace librbd {
+
+struct MockJournal {
+ MOCK_CONST_METHOD0(is_journal_ready, bool());
+ MOCK_CONST_METHOD0(is_journal_replaying, bool());
+
+ MOCK_METHOD1(wait_for_journal_ready, void(Context *));
+
+ MOCK_METHOD1(append_op_event, uint64_t(journal::EventEntry&));
+ MOCK_METHOD2(commit_op_event, void(uint64_t, int));
+};
+
+} // namespace librbd
+
+#endif // CEPH_TEST_LIBRBD_MOCK_JOURNAL_H
--- /dev/null
+// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "common/bit_vector.hpp"
+#include "librbd/internal.h"
+#include "librbd/operation/SnapshotCreateRequest.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+// template definitions
+#include "librbd/operation/SnapshotCreateRequest.cc"
+
+namespace librbd {
+namespace operation {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::DoDefault;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+using ::testing::WithArg;
+
+class TestMockOperationSnapshotCreateRequest : public TestMockFixture {
+public:
+ typedef SnapshotCreateRequest<MockImageCtx> MockSnapshotCreateRequest;
+
+ void expect_block_writes(MockImageCtx &mock_image_ctx) {
+ EXPECT_CALL(*mock_image_ctx.aio_work_queue, block_writes(_))
+ .WillRepeatedly(CompleteContext(0, NULL));
+ }
+
+ void expect_verify_lock_ownership(MockImageCtx &mock_image_ctx) {
+ EXPECT_CALL(*mock_image_ctx.image_watcher, is_lock_supported(_))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(*mock_image_ctx.image_watcher, is_lock_owner())
+ .WillRepeatedly(Return(true));
+ }
+
+ void expect_allocate_snap_id(MockImageCtx &mock_image_ctx, int r) {
+ auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ selfmanaged_snap_create(_));
+ if (r < 0 && r != -ESTALE) {
+ expect.WillOnce(Return(r));
+ } else {
+ expect.Times(r < 0 ? 2 : 1).WillRepeatedly(DoDefault());
+ }
+ }
+
+ void expect_release_snap_id(MockImageCtx &mock_image_ctx, int r) {
+ auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ selfmanaged_snap_remove(_));
+ if (r < 0) {
+ expect.WillOnce(Return(r));
+ } else {
+ expect.WillOnce(DoDefault());
+ }
+ }
+
+ void expect_snap_create(MockImageCtx &mock_image_ctx, int r) {
+ if (!mock_image_ctx.old_format) {
+ EXPECT_CALL(*mock_image_ctx.image_watcher, assert_header_locked(_))
+ .Times(r == -ESTALE ? 2 : 1);
+ }
+
+ auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(mock_image_ctx.header_oid, _, "rbd",
+ mock_image_ctx.old_format ? "snap_add" :
+ "snapshot_add",
+ _, _, _));
+ if (r == -ESTALE) {
+ expect.WillOnce(Return(r)).WillOnce(DoDefault());
+ } else if (r < 0) {
+ expect.WillOnce(Return(r));
+ } else {
+ expect.WillOnce(DoDefault());
+ }
+ }
+
+ void expect_object_map_snap_create(MockImageCtx &mock_image_ctx) {
+ bool enabled = mock_image_ctx.image_ctx->test_features(RBD_FEATURE_OBJECT_MAP);
+ EXPECT_CALL(mock_image_ctx.object_map, enabled(_))
+ .WillOnce(Return(enabled));
+ if (enabled) {
+ EXPECT_CALL(mock_image_ctx.object_map, snapshot_add(_, _))
+ .WillOnce(WithArg<1>(CompleteContext(
+ 0, mock_image_ctx.image_ctx->op_work_queue)));
+ }
+ }
+
+ void expect_update_snap_context(MockImageCtx &mock_image_ctx) {
+ // state machine checks to ensure a refresh hasn't already added the snap
+ EXPECT_CALL(mock_image_ctx, get_snap_info(_))
+ .WillOnce(Return(reinterpret_cast<const librbd::SnapInfo*>(NULL)));
+ EXPECT_CALL(mock_image_ctx, add_snap("snap1", _, _, _, _, _));
+ }
+
+ void expect_unblock_writes(MockImageCtx &mock_image_ctx) {
+ EXPECT_CALL(*mock_image_ctx.aio_work_queue, unblock_writes())
+ .Times(1);
+ }
+
+};
+
+TEST_F(TestMockOperationSnapshotCreateRequest, Success) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_verify_lock_ownership(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ ::testing::InSequence seq;
+ expect_block_writes(mock_image_ctx);
+ expect_allocate_snap_id(mock_image_ctx, 0);
+ expect_snap_create(mock_image_ctx, 0);
+ expect_update_snap_context(mock_image_ctx);
+ expect_object_map_snap_create(mock_image_ctx);
+ expect_unblock_writes(mock_image_ctx);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
+ mock_image_ctx, &cond_ctx, "snap1");
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(0, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotCreateRequest, AllocateSnapIdError) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_verify_lock_ownership(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ ::testing::InSequence seq;
+ expect_block_writes(mock_image_ctx);
+ expect_allocate_snap_id(mock_image_ctx, -EINVAL);
+ expect_unblock_writes(mock_image_ctx);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
+ mock_image_ctx, &cond_ctx, "snap1");
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(-EINVAL, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotCreateRequest, CreateSnapStale) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_verify_lock_ownership(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ expect_block_writes(mock_image_ctx);
+ expect_allocate_snap_id(mock_image_ctx, -ESTALE);
+ expect_snap_create(mock_image_ctx, -ESTALE);
+ expect_update_snap_context(mock_image_ctx);
+ expect_object_map_snap_create(mock_image_ctx);
+ expect_unblock_writes(mock_image_ctx);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
+ mock_image_ctx, &cond_ctx, "snap1");
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(0, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotCreateRequest, CreateSnapError) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_verify_lock_ownership(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ expect_block_writes(mock_image_ctx);
+ expect_allocate_snap_id(mock_image_ctx, 0);
+ expect_snap_create(mock_image_ctx, -EINVAL);
+ expect_release_snap_id(mock_image_ctx, 0);
+ expect_unblock_writes(mock_image_ctx);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
+ mock_image_ctx, &cond_ctx, "snap1");
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(-EINVAL, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotCreateRequest, ReleaseSnapIdError) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_verify_lock_ownership(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ expect_block_writes(mock_image_ctx);
+ expect_allocate_snap_id(mock_image_ctx, 0);
+ expect_snap_create(mock_image_ctx, -EINVAL);
+ expect_release_snap_id(mock_image_ctx, -ESTALE);
+ expect_unblock_writes(mock_image_ctx);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
+ mock_image_ctx, &cond_ctx, "snap1");
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(-EINVAL, cond_ctx.wait());
+}
+
+} // namespace operation
+} // namespace librbd
--- /dev/null
+// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "common/bit_vector.hpp"
+#include "librbd/internal.h"
+#include "librbd/operation/SnapshotProtectRequest.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+// template definitions
+#include "librbd/operation/SnapshotProtectRequest.cc"
+
+namespace librbd {
+namespace operation {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::DoDefault;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+using ::testing::WithArg;
+
+class TestMockOperationSnapshotProtectRequest : public TestMockFixture {
+public:
+ typedef SnapshotProtectRequest<MockImageCtx> MockSnapshotProtectRequest;
+
+ void expect_get_snap_id(MockImageCtx &mock_image_ctx, uint64_t snap_id) {
+ EXPECT_CALL(mock_image_ctx, get_snap_id(_))
+ .WillOnce(Return(snap_id));
+ }
+
+ void expect_is_snap_protected(MockImageCtx &mock_image_ctx, bool is_protected,
+ int r) {
+ auto &expect = EXPECT_CALL(mock_image_ctx, is_snap_protected(_, _));
+ if (r < 0) {
+ expect.WillOnce(Return(r));
+ } else {
+ expect.WillOnce(DoAll(SetArgPointee<1>(is_protected), Return(0)));
+ }
+ }
+
+ void expect_set_protection_status(MockImageCtx &mock_image_ctx, int r) {
+ auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(mock_image_ctx.header_oid, _, "rbd",
+ "set_protection_status", _, _, _));
+ if (r < 0) {
+ expect.WillOnce(Return(r));
+ } else {
+ expect.WillOnce(DoDefault());
+ }
+ }
+};
+
+TEST_F(TestMockOperationSnapshotProtectRequest, Success) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, librbd::snap_create(ictx, "snap1"));
+ ASSERT_EQ(0, librbd::ictx_check(ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ ::testing::InSequence seq;
+ expect_get_snap_id(mock_image_ctx, ictx->snap_info.rbegin()->first);
+ expect_is_snap_protected(mock_image_ctx, false, 0);
+ expect_set_protection_status(mock_image_ctx, 0);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotProtectRequest *req = new MockSnapshotProtectRequest(
+ mock_image_ctx, &cond_ctx, "snap1");
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(0, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotProtectRequest, GetSnapIdMissing) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, librbd::snap_create(ictx, "snap1"));
+ ASSERT_EQ(0, librbd::ictx_check(ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ ::testing::InSequence seq;
+ expect_get_snap_id(mock_image_ctx, CEPH_NOSNAP);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotProtectRequest *req = new MockSnapshotProtectRequest(
+ mock_image_ctx, &cond_ctx, "snap1");
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(-ENOENT, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotProtectRequest, IsSnapProtectedError) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, librbd::snap_create(ictx, "snap1"));
+ ASSERT_EQ(0, librbd::ictx_check(ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ ::testing::InSequence seq;
+ expect_get_snap_id(mock_image_ctx, ictx->snap_info.rbegin()->first);
+ expect_is_snap_protected(mock_image_ctx, false, -EINVAL);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotProtectRequest *req = new MockSnapshotProtectRequest(
+ mock_image_ctx, &cond_ctx, "snap1");
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(-EINVAL, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotProtectRequest, SnapAlreadyProtected) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, librbd::snap_create(ictx, "snap1"));
+ ASSERT_EQ(0, librbd::ictx_check(ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ ::testing::InSequence seq;
+ expect_get_snap_id(mock_image_ctx, ictx->snap_info.rbegin()->first);
+ expect_is_snap_protected(mock_image_ctx, true, 0);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotProtectRequest *req = new MockSnapshotProtectRequest(
+ mock_image_ctx, &cond_ctx, "snap1");
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(-EBUSY, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotProtectRequest, SetProtectionStateError) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, librbd::snap_create(ictx, "snap1"));
+ ASSERT_EQ(0, librbd::ictx_check(ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ ::testing::InSequence seq;
+ expect_get_snap_id(mock_image_ctx, ictx->snap_info.rbegin()->first);
+ expect_is_snap_protected(mock_image_ctx, false, 0);
+ expect_set_protection_status(mock_image_ctx, -EINVAL);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotProtectRequest *req = new MockSnapshotProtectRequest(
+ mock_image_ctx, &cond_ctx, "snap1");
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(-EINVAL, cond_ctx.wait());
+}
+
+} // namespace operation
+} // namespace librbd
--- /dev/null
+// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "common/bit_vector.hpp"
+#include "librbd/internal.h"
+#include "librbd/operation/SnapshotRemoveRequest.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+// template definitions
+#include "librbd/operation/SnapshotRemoveRequest.cc"
+
+namespace librbd {
+namespace operation {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::DoDefault;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+using ::testing::WithArg;
+
+class TestMockOperationSnapshotRemoveRequest : public TestMockFixture {
+public:
+ typedef SnapshotRemoveRequest<MockImageCtx> MockSnapshotRemoveRequest;
+
+ int create_snapshot(const char *snap_name) {
+ librbd::ImageCtx *ictx;
+ int r = open_image(m_image_name, &ictx);
+ if (r < 0) {
+ return r;
+ }
+
+ r = librbd::snap_create(ictx, snap_name);
+ if (r < 0) {
+ return r;
+ }
+
+ r = librbd::snap_protect(ictx, snap_name);
+ if (r < 0) {
+ return r;
+ }
+ close_image(ictx);
+ return 0;
+ }
+
+ void expect_object_map_snap_remove(MockImageCtx &mock_image_ctx, int r) {
+ bool enabled = mock_image_ctx.image_ctx->test_features(RBD_FEATURE_OBJECT_MAP);
+ EXPECT_CALL(mock_image_ctx.object_map, enabled(_))
+ .WillOnce(Return(enabled));
+ if (enabled) {
+ EXPECT_CALL(mock_image_ctx.object_map, snapshot_remove(_, _))
+ .WillOnce(WithArg<1>(CompleteContext(
+ r, mock_image_ctx.image_ctx->op_work_queue)));
+ }
+ }
+
+ void expect_get_parent_spec(MockImageCtx &mock_image_ctx, int r) {
+ auto &expect = EXPECT_CALL(mock_image_ctx, get_parent_spec(_, _));
+ if (r < 0) {
+ expect.WillOnce(Return(r));
+ } else {
+ parent_spec &parent_spec = mock_image_ctx.snap_info.rbegin()->second.parent.spec;
+ expect.WillOnce(DoAll(SetArgPointee<1>(parent_spec),
+ Return(0)));
+ }
+ }
+
+ void expect_remove_child(MockImageCtx &mock_image_ctx, int r) {
+ bool deep_flatten = mock_image_ctx.image_ctx->test_features(RBD_FEATURE_DEEP_FLATTEN);
+ auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(RBD_CHILDREN, _, "rbd", "remove_child",_,
+ _, _));
+ if (deep_flatten) {
+ expect.Times(0);
+ } else {
+ expect.WillOnce(Return(r));
+ }
+ }
+
+ void expect_verify_lock_ownership(MockImageCtx &mock_image_ctx) {
+ if (mock_image_ctx.old_format) {
+ return;
+ }
+
+ EXPECT_CALL(*mock_image_ctx.image_watcher, is_lock_owner())
+ .WillRepeatedly(Return(false));
+ }
+
+ void expect_snap_remove(MockImageCtx &mock_image_ctx, int r) {
+ auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(mock_image_ctx.header_oid, _, "rbd",
+ mock_image_ctx.old_format ? "snap_remove" :
+ "snapshot_remove",
+ _, _, _));
+ if (r < 0) {
+ expect.WillOnce(Return(r));
+ } else {
+ expect.WillOnce(DoDefault());
+ }
+ }
+
+ void expect_rm_snap(MockImageCtx &mock_image_ctx) {
+ EXPECT_CALL(mock_image_ctx, rm_snap(_, _)).Times(1);
+ }
+
+ void expect_release_snap_id(MockImageCtx &mock_image_ctx) {
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ selfmanaged_snap_remove(_))
+ .WillOnce(DoDefault());
+ }
+
+};
+
+TEST_F(TestMockOperationSnapshotRemoveRequest, Success) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, librbd::snap_create(ictx, "snap1"));
+ ASSERT_EQ(0, librbd::ictx_check(ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ ::testing::InSequence seq;
+ uint64_t snap_id = ictx->snap_info.rbegin()->first;
+ expect_object_map_snap_remove(mock_image_ctx, 0);
+ expect_get_parent_spec(mock_image_ctx, 0);
+ expect_verify_lock_ownership(mock_image_ctx);
+ expect_snap_remove(mock_image_ctx, 0);
+ expect_rm_snap(mock_image_ctx);
+ expect_release_snap_id(mock_image_ctx);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest(
+ mock_image_ctx, &cond_ctx, "snap1", snap_id);
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(0, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotRemoveRequest, FlattenedCloneRemovesChild) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ ASSERT_EQ(0, create_snapshot("snap1"));
+
+ int order = 22;
+ uint64_t features;
+ ASSERT_TRUE(::get_features(&features));
+ std::string clone_name = get_temp_image_name();
+ ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx,
+ clone_name.c_str(), features, &order, 0, 0));
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(clone_name, &ictx));
+ ASSERT_EQ(0, librbd::snap_create(ictx, "snap1"));
+
+ librbd::NoOpProgressContext prog_ctx;
+ ASSERT_EQ(0, librbd::flatten(ictx, prog_ctx));
+ ASSERT_EQ(0, librbd::ictx_check(ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ uint64_t snap_id = ictx->snap_info.rbegin()->first;
+ expect_object_map_snap_remove(mock_image_ctx, 0);
+ expect_get_parent_spec(mock_image_ctx, 0);
+ expect_remove_child(mock_image_ctx, -ENOENT);
+ expect_verify_lock_ownership(mock_image_ctx);
+ expect_snap_remove(mock_image_ctx, 0);
+ expect_rm_snap(mock_image_ctx);
+ expect_release_snap_id(mock_image_ctx);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest(
+ mock_image_ctx, &cond_ctx, "snap1", snap_id);
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(0, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotRemoveRequest, ObjectMapSnapRemoveError) {
+ REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, librbd::snap_create(ictx, "snap1"));
+ ASSERT_EQ(0, librbd::ictx_check(ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ ::testing::InSequence seq;
+ uint64_t snap_id = ictx->snap_info.rbegin()->first;
+ expect_object_map_snap_remove(mock_image_ctx, -EINVAL);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest(
+ mock_image_ctx, &cond_ctx, "snap1", snap_id);
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(-EINVAL, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotRemoveRequest, RemoveChildParentError) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, librbd::snap_create(ictx, "snap1"));
+ ASSERT_EQ(0, librbd::ictx_check(ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ ::testing::InSequence seq;
+ uint64_t snap_id = ictx->snap_info.rbegin()->first;
+ expect_object_map_snap_remove(mock_image_ctx, 0);
+ expect_get_parent_spec(mock_image_ctx, -ENOENT);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest(
+ mock_image_ctx, &cond_ctx, "snap1", snap_id);
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(-ENOENT, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotRemoveRequest, RemoveChildError) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ ASSERT_EQ(0, create_snapshot("snap1"));
+
+ int order = 22;
+ uint64_t features;
+ ASSERT_TRUE(::get_features(&features));
+ std::string clone_name = get_temp_image_name();
+ ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx,
+ clone_name.c_str(), features, &order, 0, 0));
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(clone_name, &ictx));
+ if (ictx->test_features(RBD_FEATURE_DEEP_FLATTEN)) {
+ std::cout << "SKIPPING" << std::endl;
+ return SUCCEED();
+ }
+
+ ASSERT_EQ(0, librbd::snap_create(ictx, "snap1"));
+
+ librbd::NoOpProgressContext prog_ctx;
+ ASSERT_EQ(0, librbd::flatten(ictx, prog_ctx));
+ ASSERT_EQ(0, librbd::ictx_check(ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ uint64_t snap_id = ictx->snap_info.rbegin()->first;
+ expect_object_map_snap_remove(mock_image_ctx, 0);
+ expect_get_parent_spec(mock_image_ctx, 0);
+ expect_remove_child(mock_image_ctx, -EINVAL);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest(
+ mock_image_ctx, &cond_ctx, "snap1", snap_id);
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(-EINVAL, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotRemoveRequest, RemoveSnapError) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, librbd::snap_create(ictx, "snap1"));
+ ASSERT_EQ(0, librbd::ictx_check(ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ ::testing::InSequence seq;
+ uint64_t snap_id = ictx->snap_info.rbegin()->first;
+ expect_object_map_snap_remove(mock_image_ctx, 0);
+ expect_get_parent_spec(mock_image_ctx, 0);
+ expect_verify_lock_ownership(mock_image_ctx);
+ expect_snap_remove(mock_image_ctx, -ENOENT);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest(
+ mock_image_ctx, &cond_ctx, "snap1", snap_id);
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(-ENOENT, cond_ctx.wait());
+}
+
+} // namespace operation
+} // namespace librbd
--- /dev/null
+// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librados_test_stub/MockTestMemRadosClient.h"
+#include "include/rados/librados.hpp"
+#include "common/bit_vector.hpp"
+#include "librbd/internal.h"
+#include "librbd/operation/SnapshotUnprotectRequest.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+// template definitions
+#include "librbd/operation/SnapshotUnprotectRequest.cc"
+
+namespace librbd {
+namespace operation {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::DoDefault;
+using ::testing::Return;
+using ::testing::SetArgReferee;
+using ::testing::SetArgPointee;
+using ::testing::WithArg;
+
+class TestMockOperationSnapshotUnprotectRequest : public TestMockFixture {
+public:
+ typedef SnapshotUnprotectRequest<MockImageCtx> MockSnapshotUnprotectRequest;
+
+ void expect_get_snap_id(MockImageCtx &mock_image_ctx, uint64_t snap_id) {
+ EXPECT_CALL(mock_image_ctx, get_snap_id(_))
+ .WillOnce(Return(snap_id));
+ }
+
+ void expect_is_snap_unprotected(MockImageCtx &mock_image_ctx,
+ bool is_unprotected, int r) {
+ auto &expect = EXPECT_CALL(mock_image_ctx, is_snap_unprotected(_, _));
+ if (r < 0) {
+ expect.WillOnce(Return(r));
+ } else {
+ expect.WillOnce(DoAll(SetArgPointee<1>(is_unprotected), Return(0)));
+ }
+ }
+
+ void expect_set_protection_status(MockImageCtx &mock_image_ctx,
+ uint64_t snap_id, uint8_t status, int r) {
+ bufferlist bl;
+ ::encode(snap_id, bl);
+ ::encode(status, bl);
+
+ auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(mock_image_ctx.header_oid, _, "rbd",
+ "set_protection_status", ContentsEqual(bl),
+ _, _));
+ if (r < 0) {
+ expect.WillOnce(Return(r));
+ } else {
+ expect.WillOnce(DoDefault());
+ }
+ }
+
+ size_t expect_create_pool_io_contexts(MockImageCtx &mock_image_ctx) {
+ librados::MockTestMemIoCtxImpl &io_ctx_impl =
+ get_mock_io_ctx(mock_image_ctx.md_ctx);
+ librados::MockTestMemRadosClient *rados_client =
+ io_ctx_impl.get_mock_rados_client();
+
+ std::list<std::pair<int64_t, std::string> > pools;
+ int r = rados_client->pool_list(pools);
+ if (r < 0) {
+ ADD_FAILURE() << "failed to list pools";
+ return 0;
+ }
+
+ EXPECT_CALL(*rados_client, create_ioctx(_, _))
+ .Times(pools.size()).WillRepeatedly(DoAll(
+ GetReference(&io_ctx_impl), Return(&io_ctx_impl)));
+ return pools.size();
+ }
+
+ void expect_get_children(MockImageCtx &mock_image_ctx, size_t pools, int r) {
+ bufferlist bl;
+ std::set<std::string> children;
+ ::encode(children, bl);
+
+ auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(RBD_CHILDREN, _, "rbd", "get_children", _,
+ _, _));
+ if (r < 0) {
+ expect.WillRepeatedly(Return(r));
+ } else {
+ expect.Times(pools).WillRepeatedly(DoAll(
+ SetArgPointee<5>(bl), Return(0)));
+ }
+ }
+};
+
+TEST_F(TestMockOperationSnapshotUnprotectRequest, Success) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, librbd::snap_create(ictx, "snap1"));
+ ASSERT_EQ(0, librbd::ictx_check(ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ ::testing::InSequence seq;
+ uint64_t snap_id = ictx->snap_info.rbegin()->first;
+ expect_get_snap_id(mock_image_ctx, snap_id);
+ expect_is_snap_unprotected(mock_image_ctx, false, 0);
+ expect_set_protection_status(mock_image_ctx, snap_id,
+ RBD_PROTECTION_STATUS_UNPROTECTING, 0);
+ size_t pools = expect_create_pool_io_contexts(mock_image_ctx);
+ expect_get_children(mock_image_ctx, pools, -ENOENT);
+ expect_set_protection_status(mock_image_ctx, snap_id,
+ RBD_PROTECTION_STATUS_UNPROTECTED, 0);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotUnprotectRequest *req = new MockSnapshotUnprotectRequest(
+ mock_image_ctx, &cond_ctx, "snap1");
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(0, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotUnprotectRequest, GetSnapIdMissing) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, librbd::snap_create(ictx, "snap1"));
+ ASSERT_EQ(0, librbd::ictx_check(ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ ::testing::InSequence seq;
+ expect_get_snap_id(mock_image_ctx, CEPH_NOSNAP);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotUnprotectRequest *req = new MockSnapshotUnprotectRequest(
+ mock_image_ctx, &cond_ctx, "snap1");
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(-ENOENT, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotUnprotectRequest, IsSnapUnprotectedError) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, librbd::snap_create(ictx, "snap1"));
+ ASSERT_EQ(0, librbd::ictx_check(ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ ::testing::InSequence seq;
+ expect_get_snap_id(mock_image_ctx, ictx->snap_info.rbegin()->first);
+ expect_is_snap_unprotected(mock_image_ctx, false, -EBADMSG);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotUnprotectRequest *req = new MockSnapshotUnprotectRequest(
+ mock_image_ctx, &cond_ctx, "snap1");
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(-EBADMSG, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotUnprotectRequest, SnapAlreadyUnprotected) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, librbd::snap_create(ictx, "snap1"));
+ ASSERT_EQ(0, librbd::ictx_check(ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ ::testing::InSequence seq;
+ expect_get_snap_id(mock_image_ctx, ictx->snap_info.rbegin()->first);
+ expect_is_snap_unprotected(mock_image_ctx, true, 0);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotUnprotectRequest *req = new MockSnapshotUnprotectRequest(
+ mock_image_ctx, &cond_ctx, "snap1");
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(-EINVAL, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotUnprotectRequest, SetProtectionStatusError) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, librbd::snap_create(ictx, "snap1"));
+ ASSERT_EQ(0, librbd::ictx_check(ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ ::testing::InSequence seq;
+ uint64_t snap_id = ictx->snap_info.rbegin()->first;
+ expect_get_snap_id(mock_image_ctx, snap_id);
+ expect_is_snap_unprotected(mock_image_ctx, false, 0);
+ expect_set_protection_status(mock_image_ctx, snap_id,
+ RBD_PROTECTION_STATUS_UNPROTECTING, -EINVAL);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotUnprotectRequest *req = new MockSnapshotUnprotectRequest(
+ mock_image_ctx, &cond_ctx, "snap1");
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(-EINVAL, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotUnprotectRequest, ChildrenExist) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, librbd::snap_create(ictx, "snap1"));
+ ASSERT_EQ(0, librbd::ictx_check(ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ ::testing::InSequence seq;
+ uint64_t snap_id = ictx->snap_info.rbegin()->first;
+ expect_get_snap_id(mock_image_ctx, snap_id);
+ expect_is_snap_unprotected(mock_image_ctx, false, 0);
+ expect_set_protection_status(mock_image_ctx, snap_id,
+ RBD_PROTECTION_STATUS_UNPROTECTING, 0);
+ size_t pools = expect_create_pool_io_contexts(mock_image_ctx);
+ expect_get_children(mock_image_ctx, pools, 0);
+ expect_set_protection_status(mock_image_ctx, snap_id,
+ RBD_PROTECTION_STATUS_PROTECTED, 0);
+
+ C_SaferCond cond_ctx;
+ MockSnapshotUnprotectRequest *req = new MockSnapshotUnprotectRequest(
+ mock_image_ctx, &cond_ctx, "snap1");
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ req->send();
+ }
+ ASSERT_EQ(-EBUSY, cond_ctx.wait());
+}
+
+} // namespace operation
+} // namespace librbd
// template definitions
#include "librbd/AsyncRequest.cc"
#include "librbd/AsyncObjectThrottle.cc"
+#include "librbd/operation/Request.cc"
template class librbd::AsyncRequest<librbd::MockImageCtx>;
template class librbd::AsyncObjectThrottle<librbd::MockImageCtx>;
+template class librbd::operation::Request<librbd::MockImageCtx>;
using ::testing::_;
using ::testing::DoDefault;