]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
tests: initial set of test cases for op state machines 6703/head
authorJason Dillaman <dillaman@redhat.com>
Wed, 19 Aug 2015 17:32:42 +0000 (13:32 -0400)
committerJason Dillaman <dillaman@redhat.com>
Wed, 2 Dec 2015 16:11:12 +0000 (11:11 -0500)
Signed-off-by: Jason Dillaman <dillaman@redhat.com>
src/test/Makefile-client.am
src/test/librbd/mock/MockAioImageRequestWQ.h [new file with mode: 0644]
src/test/librbd/mock/MockImageCtx.h
src/test/librbd/mock/MockJournal.h [new file with mode: 0644]
src/test/librbd/operation/test_mock_SnapshotCreateRequest.cc [new file with mode: 0644]
src/test/librbd/operation/test_mock_SnapshotProtectRequest.cc [new file with mode: 0644]
src/test/librbd/operation/test_mock_SnapshotRemoveRequest.cc [new file with mode: 0644]
src/test/librbd/operation/test_mock_SnapshotUnprotectRequest.cc [new file with mode: 0644]
src/test/librbd/test_mock_fixture.cc

index 76cc13adf4a401479bce10fce4f6049f9e0e2a83..00764b4ac41fd616467d13fcc0c14bad0d8853c7 100644 (file)
@@ -358,7 +358,11 @@ unittest_librbd_SOURCES = \
        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) \
@@ -394,9 +398,11 @@ noinst_HEADERS += \
        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
diff --git a/src/test/librbd/mock/MockAioImageRequestWQ.h b/src/test/librbd/mock/MockAioImageRequestWQ.h
new file mode 100644 (file)
index 0000000..cd34807
--- /dev/null
@@ -0,0 +1,18 @@
+// -*- 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
index 53d6fd0d01e1d7907189ffb1b0f1a704cbcb2e9a..9ded05dd16c6849b78510f39b7fc6e49fbfd49a9 100644 (file)
@@ -4,8 +4,10 @@
 #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"
@@ -33,9 +35,9 @@ struct MockImageCtx {
       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);
@@ -98,12 +100,14 @@ struct MockImageCtx {
   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;
 };
 
diff --git a/src/test/librbd/mock/MockJournal.h b/src/test/librbd/mock/MockJournal.h
new file mode 100644 (file)
index 0000000..0535d0a
--- /dev/null
@@ -0,0 +1,24 @@
+// -*- 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
diff --git a/src/test/librbd/operation/test_mock_SnapshotCreateRequest.cc b/src/test/librbd/operation/test_mock_SnapshotCreateRequest.cc
new file mode 100644 (file)
index 0000000..8ae12d0
--- /dev/null
@@ -0,0 +1,236 @@
+// -*- 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
diff --git a/src/test/librbd/operation/test_mock_SnapshotProtectRequest.cc b/src/test/librbd/operation/test_mock_SnapshotProtectRequest.cc
new file mode 100644 (file)
index 0000000..e77ceb2
--- /dev/null
@@ -0,0 +1,190 @@
+// -*- 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
diff --git a/src/test/librbd/operation/test_mock_SnapshotRemoveRequest.cc b/src/test/librbd/operation/test_mock_SnapshotRemoveRequest.cc
new file mode 100644 (file)
index 0000000..323f95b
--- /dev/null
@@ -0,0 +1,314 @@
+// -*- 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
diff --git a/src/test/librbd/operation/test_mock_SnapshotUnprotectRequest.cc b/src/test/librbd/operation/test_mock_SnapshotUnprotectRequest.cc
new file mode 100644 (file)
index 0000000..7ec65b4
--- /dev/null
@@ -0,0 +1,275 @@
+// -*- 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
index 1839b9150eca626560a26053f285b0349a9a9092..d181914f3717960142afcf2d6a8fb599c9aa05a1 100644 (file)
@@ -9,9 +9,11 @@
 // 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;