]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd/migration: re-use RawSnapshot for RawFormat's HEAD revision
authorJason Dillaman <dillaman@redhat.com>
Tue, 1 Dec 2020 17:34:34 +0000 (12:34 -0500)
committerJason Dillaman <dillaman@redhat.com>
Tue, 1 Dec 2020 17:34:34 +0000 (12:34 -0500)
Signed-off-by: Jason Dillaman <dillaman@redhat.com>
src/librbd/migration/RawFormat.cc
src/librbd/migration/RawFormat.h
src/test/librbd/migration/test_mock_RawFormat.cc
src/test/librbd/mock/migration/MockSnapshotInterface.h [new file with mode: 0644]

index 87f8b21c4199190387242f586f95f0401fa10689..8e8ecd1b03b41ebf383e49443e96478445ba919e 100644 (file)
 #include "librbd/Utils.h"
 #include "librbd/io/AioCompletion.h"
 #include "librbd/io/ReadResult.h"
-#include "librbd/migration/FileStream.h"
+#include "librbd/migration/SnapshotInterface.h"
 #include "librbd/migration/SourceSpecBuilder.h"
-#include "librbd/migration/StreamInterface.h"
-
-#define dout_subsys ceph_subsys_rbd
-#undef dout_prefix
-#define dout_prefix *_dout << "librbd::migration::RawFormat: " << this \
-                           << " " << __func__ << ": "
 
 namespace librbd {
 namespace migration {
 
 #define dout_subsys ceph_subsys_rbd
-#undef dout_prefix
-#define dout_prefix *_dout << "librbd::migration::RawFormat::OpenRequest " \
-                           << this << " " << __func__ << ": "
-
-template <typename I>
-struct RawFormat<I>::OpenRequest {
-  RawFormat* raw_format;
-  Context* on_finish;
-
-  uint64_t image_size = 0;
-
-  OpenRequest(RawFormat* raw_format, Context* on_finish)
-    : raw_format(raw_format), on_finish(on_finish) {
-  }
-
-  void send() {
-    open_stream();
-  }
-
-  void open_stream() {
-    auto cct = raw_format->m_image_ctx->cct;
-    ldout(cct, 10) << dendl;
-
-    auto ctx = util::create_context_callback<
-      OpenRequest, &OpenRequest::handle_open_stream>(this);
-    raw_format->m_stream->open(ctx);
-  }
-
-  void handle_open_stream(int r) {
-    auto cct = raw_format->m_image_ctx->cct;
-    ldout(cct, 10) << "r=" << r << dendl;
-
-    if (r < 0) {
-      lderr(cct) << "failed to open stream: " << cpp_strerror(r) << dendl;
-      finish(r);
-      return;
-    }
-
-    get_image_size();
-  }
-
-  void get_image_size() {
-    auto cct = raw_format->m_image_ctx->cct;
-    ldout(cct, 10) << dendl;
-
-    auto ctx = util::create_context_callback<
-      OpenRequest, &OpenRequest::handle_get_image_size>(this);
-    raw_format->get_image_size(CEPH_NOSNAP, &image_size, ctx);
-  }
-
-  void handle_get_image_size(int r) {
-    auto cct = raw_format->m_image_ctx->cct;
-    ldout(cct, 10) << "r=" << r << dendl;
-
-    if (r < 0) {
-      lderr(cct) << "failed to open stream: " << cpp_strerror(r) << dendl;
-      finish(r);
-      return;
-    }
-
-    raw_format->m_image_ctx->image_lock.lock();
-    raw_format->m_image_ctx->size = image_size;
-    raw_format->m_image_ctx->image_lock.unlock();
-
-    finish(0);
-  }
-
-  void finish(int r) {
-    auto cct = raw_format->m_image_ctx->cct;
-    ldout(cct, 10) << "r=" << r << dendl;
-
-    if (r < 0) {
-      raw_format->m_image_ctx->state->close(new LambdaContext(
-        [r, on_finish=on_finish](int _) { on_finish->complete(r); }));
-    } else {
-      on_finish->complete(0);
-    }
-
-    delete this;
-  }
-};
-
 #undef dout_prefix
 #define dout_prefix *_dout << "librbd::migration::RawFormat: " << this \
                            << " " << __func__ << ": "
@@ -121,17 +33,48 @@ void RawFormat<I>::open(Context* on_finish) {
   auto cct = m_image_ctx->cct;
   ldout(cct, 10) << dendl;
 
-  int r = m_source_spec_builder->build_stream(m_json_object, &m_stream);
+  on_finish = new LambdaContext([this, on_finish](int r) {
+    handle_open(r, on_finish); });
+
+  // treat the base image as a HEAD-revision snapshot
+  int r = m_source_spec_builder->build_snapshot(m_json_object, CEPH_NOSNAP,
+                                                &m_snapshots[CEPH_NOSNAP]);
+  if (r < 0) {
+    lderr(cct) << "failed to build HEAD revision handler: " << cpp_strerror(r)
+               << dendl;
+    on_finish->complete(r);
+    return;
+  }
+
+  auto gather_ctx = new C_Gather(cct, on_finish);
+  SnapshotInterface* previous_snapshot = nullptr;
+  for (auto& [_, snapshot] : m_snapshots) {
+    snapshot->open(previous_snapshot, gather_ctx->new_sub());
+    previous_snapshot = snapshot.get();
+  }
+  gather_ctx->activate();
+}
+
+template <typename I>
+void RawFormat<I>::handle_open(int r, Context* on_finish) {
+  auto cct = m_image_ctx->cct;
+  ldout(cct, 10) << "r=" << r << dendl;
+
   if (r < 0) {
-    lderr(cct) << "failed to build migration stream handler" << cpp_strerror(r)
+    lderr(cct) << "failed to open raw image: " << cpp_strerror(r)
                << dendl;
-    m_image_ctx->state->close(
-      new LambdaContext([r, on_finish](int _) { on_finish->complete(r); }));
+    m_image_ctx->state->close(new LambdaContext(
+      [r, on_finish=on_finish](int _) { on_finish->complete(r); }));
     return;
   }
 
-  auto req = new OpenRequest(this, on_finish);
-  req->send();
+  auto head_snapshot = m_snapshots[CEPH_NOSNAP];
+  ceph_assert(head_snapshot);
+
+  m_image_ctx->image_lock.lock();
+  m_image_ctx->size = head_snapshot->get_snap_info().size;
+  m_image_ctx->image_lock.unlock();
+  on_finish->complete(0);
 }
 
 template <typename I>
@@ -139,12 +82,12 @@ void RawFormat<I>::close(Context* on_finish) {
   auto cct = m_image_ctx->cct;
   ldout(cct, 10) << dendl;
 
-  if (!m_stream) {
-    on_finish->complete(0);
-    return;
+  auto gather_ctx = new C_Gather(cct, on_finish);
+  for (auto& [snap_id, snapshot] : m_snapshots) {
+    snapshot->close(gather_ctx->new_sub());
   }
 
-  m_stream->close(on_finish);
+  gather_ctx->activate();
 }
 
 template <typename I>
@@ -153,6 +96,12 @@ void RawFormat<I>::get_snapshots(SnapInfos* snap_infos, Context* on_finish) {
   ldout(cct, 10) << dendl;
 
   snap_infos->clear();
+  for (auto& [snap_id, snapshot] : m_snapshots) {
+    if (snap_id == CEPH_NOSNAP) {
+      continue;
+    }
+    snap_infos->emplace(snap_id, snapshot->get_snap_info());
+  }
   on_finish->complete(0);
 }
 
@@ -162,12 +111,14 @@ void RawFormat<I>::get_image_size(uint64_t snap_id, uint64_t* size,
   auto cct = m_image_ctx->cct;
   ldout(cct, 10) << dendl;
 
-  if (snap_id != CEPH_NOSNAP) {
-    on_finish->complete(-EINVAL);
+  auto snapshot_it = m_snapshots.find(snap_id);
+  if (snapshot_it == m_snapshots.end()) {
+    on_finish->complete(-ENOENT);
     return;
   }
 
-  m_stream->get_size(size, on_finish);
+  *size = snapshot_it->second->get_snap_info().size;
+  on_finish->complete(0);
 }
 
 template <typename I>
@@ -178,20 +129,15 @@ bool RawFormat<I>::read(
   auto cct = m_image_ctx->cct;
   ldout(cct, 20) << "image_extents=" << image_extents << dendl;
 
-  if (snap_id != CEPH_NOSNAP) {
-    aio_comp->fail(-EINVAL);
+  auto snapshot_it = m_snapshots.find(snap_id);
+  if (snapshot_it == m_snapshots.end()) {
+    aio_comp->fail(-ENOENT);
     return true;
   }
 
-  aio_comp->read_result = std::move(read_result);
-  aio_comp->read_result.set_image_extents(image_extents);
-
-  aio_comp->set_request_count(1);
-  auto ctx = new io::ReadResult::C_ImageReadRequest(aio_comp,
-                                                    0, image_extents);
-
-  // raw directly maps the image-extent IO down to a byte IO extent
-  m_stream->read(std::move(image_extents), &ctx->bl, ctx);
+  snapshot_it->second->read(aio_comp, std::move(image_extents),
+                            std::move(read_result), op_flags, read_flags,
+                            parent_trace);
   return true;
 }
 
index 450fd7d44064a1c520a2b72d9ecd6d8561b7de6f..0febfe3fb1d365bbdf0ff28b129c95ee5214928a 100644 (file)
@@ -8,6 +8,7 @@
 #include "librbd/Types.h"
 #include "librbd/migration/FormatInterface.h"
 #include "json_spirit/json_spirit.h"
+#include <map>
 #include <memory>
 
 struct Context;
@@ -20,7 +21,7 @@ struct ImageCtx;
 namespace migration {
 
 template <typename> struct SourceSpecBuilder;
-struct StreamInterface;
+struct SnapshotInterface;
 
 template <typename ImageCtxT>
 class RawFormat : public FormatInterface {
@@ -54,14 +55,16 @@ public:
                   Context* on_finish) override;
 
 private:
-  struct OpenRequest;
+  typedef std::shared_ptr<SnapshotInterface> Snapshot;
+  typedef std::map<uint64_t, Snapshot> Snapshots;
 
   ImageCtxT* m_image_ctx;
   json_spirit::mObject m_json_object;
   const SourceSpecBuilder<ImageCtxT>* m_source_spec_builder;
 
-  std::shared_ptr<StreamInterface> m_stream;
+  Snapshots m_snapshots;
 
+  void handle_open(int r, Context* on_finish);
 };
 
 } // namespace migration
index 023a50b93f4337a13480f482065e894ade037b01..0adf6b9395cf66dda05a28c791d315c9109821b8 100644 (file)
@@ -3,10 +3,9 @@
 
 #include "test/librbd/test_mock_fixture.h"
 #include "test/librbd/test_support.h"
-#include "test/librbd/mock/migration/MockStreamInterface.h"
+#include "test/librbd/mock/migration/MockSnapshotInterface.h"
 #include "include/rbd_types.h"
 #include "common/ceph_mutex.h"
-#include "librbd/migration/FileStream.h"
 #include "librbd/migration/RawFormat.h"
 #include "librbd/migration/SourceSpecBuilder.h"
 #include "gtest/gtest.h"
@@ -28,8 +27,8 @@ namespace migration {
 template<>
 struct SourceSpecBuilder<librbd::MockTestImageCtx> {
 
-  MOCK_CONST_METHOD2(build_stream, int(const json_spirit::mObject&,
-                                       std::shared_ptr<StreamInterface>*));
+  MOCK_CONST_METHOD3(build_snapshot, int(const json_spirit::mObject&, uint64_t,
+                                         std::shared_ptr<SnapshotInterface>*));
 
 };
 
@@ -41,6 +40,8 @@ struct SourceSpecBuilder<librbd::MockTestImageCtx> {
 using ::testing::_;
 using ::testing::InSequence;
 using ::testing::Invoke;
+using ::testing::ReturnRef;
+using ::testing::WithArg;
 using ::testing::WithArgs;
 
 namespace librbd {
@@ -65,42 +66,48 @@ public:
     json_object["stream"] = stream_obj;
   }
 
-  void expect_build_stream(MockSourceSpecBuilder& mock_source_spec_builder,
-                           MockStreamInterface* mock_stream_interface, int r) {
-    EXPECT_CALL(mock_source_spec_builder, build_stream(_, _))
-      .WillOnce(WithArgs<1>(Invoke([mock_stream_interface, r]
-        (std::shared_ptr<StreamInterface>* ptr) {
-          ptr->reset(mock_stream_interface);
+  void expect_build_snapshot(MockSourceSpecBuilder& mock_source_spec_builder,
+                             uint64_t index,
+                             MockSnapshotInterface* mock_snapshot_interface,
+                             int r) {
+    EXPECT_CALL(mock_source_spec_builder, build_snapshot(_, index, _))
+      .WillOnce(WithArgs<2>(Invoke([mock_snapshot_interface, r]
+        (std::shared_ptr<SnapshotInterface>* ptr) {
+          ptr->reset(mock_snapshot_interface);
           return r;
         })));
   }
 
-  void expect_stream_open(MockStreamInterface& mock_stream_interface, int r) {
-    EXPECT_CALL(mock_stream_interface, open(_))
-      .WillOnce(Invoke([r](Context* ctx) { ctx->complete(r); }));
+  void expect_snapshot_open(MockSnapshotInterface& mock_snapshot_interface,
+                            int r) {
+    EXPECT_CALL(mock_snapshot_interface, open(_, _))
+      .WillOnce(WithArg<1>(Invoke([r](Context* ctx) { ctx->complete(r); })));
   }
 
-  void expect_stream_close(MockStreamInterface& mock_stream_interface, int r) {
-    EXPECT_CALL(mock_stream_interface, close(_))
+  void expect_snapshot_close(MockSnapshotInterface& mock_snapshot_interface,
+                             int r) {
+    EXPECT_CALL(mock_snapshot_interface, close(_))
       .WillOnce(Invoke([r](Context* ctx) { ctx->complete(r); }));
   }
 
-  void expect_stream_get_size(MockStreamInterface& mock_stream_interface,
-                              uint64_t size, int r) {
-    EXPECT_CALL(mock_stream_interface, get_size(_, _))
-      .WillOnce(Invoke([size, r](uint64_t* out_size, Context* ctx) {
-          *out_size = size;
-          ctx->complete(r);
-        }));
+  void expect_snapshot_get_info(MockSnapshotInterface& mock_snapshot_interface,
+                                const SnapInfo& snap_info) {
+    EXPECT_CALL(mock_snapshot_interface, get_snap_info())
+      .WillOnce(ReturnRef(snap_info));
   }
 
-  void expect_stream_read(MockStreamInterface& mock_stream_interface,
-                          const io::Extents& byte_extents,
-                          const bufferlist& bl, int r) {
-    EXPECT_CALL(mock_stream_interface, read(byte_extents, _, _))
-      .WillOnce(WithArgs<1, 2>(Invoke([bl, r]
-        (bufferlist* out_bl, Context* ctx) {
-          *out_bl = bl;
+  void expect_snapshot_read(MockSnapshotInterface& mock_snapshot_interface,
+                            const io::Extents& image_extents,
+                            const bufferlist& bl, int r) {
+    EXPECT_CALL(mock_snapshot_interface, read(_, image_extents, _))
+      .WillOnce(WithArgs<0, 2>(Invoke([bl, image_extents, r]
+        (io::AioCompletion* aio_comp, io::ReadResult& read_result) {
+          aio_comp->read_result = std::move(read_result);
+          aio_comp->read_result.set_image_extents(image_extents);
+          aio_comp->set_request_count(1);
+          auto ctx = new io::ReadResult::C_ImageReadRequest(aio_comp, 0,
+                                                            image_extents);
+          ctx->bl = std::move(bl);
           ctx->complete(r);
         })));
   }
@@ -121,13 +128,15 @@ TEST_F(TestMockMigrationRawFormat, OpenClose) {
   InSequence seq;
   MockSourceSpecBuilder mock_source_spec_builder;
 
-  auto mock_stream_interface = new MockStreamInterface();
-  expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+  auto mock_snapshot_interface = new MockSnapshotInterface();
+  expect_build_snapshot(mock_source_spec_builder, CEPH_NOSNAP,
+                        mock_snapshot_interface, 0);
 
-  expect_stream_open(*mock_stream_interface, 0);
-  expect_stream_get_size(*mock_stream_interface, 0, 0);
+  expect_snapshot_open(*mock_snapshot_interface, 0);
+  SnapInfo snap_info{{}, {}, 123, {}, 0, 0, {}};
+  expect_snapshot_get_info(*mock_snapshot_interface, snap_info);
 
-  expect_stream_close(*mock_stream_interface, 0);
+  expect_snapshot_close(*mock_snapshot_interface, 0);
 
   MockRawFormat mock_raw_format(&mock_image_ctx, json_object,
                                 &mock_source_spec_builder);
@@ -147,10 +156,11 @@ TEST_F(TestMockMigrationRawFormat, OpenError) {
   InSequence seq;
   MockSourceSpecBuilder mock_source_spec_builder;
 
-  auto mock_stream_interface = new MockStreamInterface();
-  expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+  auto mock_snapshot_interface = new MockSnapshotInterface();
+  expect_build_snapshot(mock_source_spec_builder, CEPH_NOSNAP,
+                        mock_snapshot_interface, 0);
 
-  expect_stream_open(*mock_stream_interface, -ENOENT);
+  expect_snapshot_open(*mock_snapshot_interface, -ENOENT);
 
   expect_close(mock_image_ctx, 0);
 
@@ -168,13 +178,15 @@ TEST_F(TestMockMigrationRawFormat, GetSnapshots) {
   InSequence seq;
   MockSourceSpecBuilder mock_source_spec_builder;
 
-  auto mock_stream_interface = new MockStreamInterface();
-  expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+  auto mock_snapshot_interface = new MockSnapshotInterface();
+  expect_build_snapshot(mock_source_spec_builder, CEPH_NOSNAP,
+                        mock_snapshot_interface, 0);
 
-  expect_stream_open(*mock_stream_interface, 0);
-  expect_stream_get_size(*mock_stream_interface, 0, 0);
+  expect_snapshot_open(*mock_snapshot_interface, 0);
+  SnapInfo snap_info{{}, {}, 123, {}, 0, 0, {}};
+  expect_snapshot_get_info(*mock_snapshot_interface, snap_info);
 
-  expect_stream_close(*mock_stream_interface, 0);
+  expect_snapshot_close(*mock_snapshot_interface, 0);
 
   MockRawFormat mock_raw_format(&mock_image_ctx, json_object,
                                 &mock_source_spec_builder);
@@ -200,15 +212,17 @@ TEST_F(TestMockMigrationRawFormat, GetImageSize) {
   InSequence seq;
   MockSourceSpecBuilder mock_source_spec_builder;
 
-  auto mock_stream_interface = new MockStreamInterface();
-  expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+  auto mock_snapshot_interface = new MockSnapshotInterface();
+  expect_build_snapshot(mock_source_spec_builder, CEPH_NOSNAP,
+                        mock_snapshot_interface, 0);
 
-  expect_stream_open(*mock_stream_interface, 0);
-  expect_stream_get_size(*mock_stream_interface, 0, 0);
+  expect_snapshot_open(*mock_snapshot_interface, 0);
+  SnapInfo snap_info{{}, {}, 123, {}, 0, 0, {}};
+  expect_snapshot_get_info(*mock_snapshot_interface, snap_info);
 
-  expect_stream_get_size(*mock_stream_interface, 123, 0);
+  expect_snapshot_get_info(*mock_snapshot_interface, snap_info);
 
-  expect_stream_close(*mock_stream_interface, 0);
+  expect_snapshot_close(*mock_snapshot_interface, 0);
 
   MockRawFormat mock_raw_format(&mock_image_ctx, json_object,
                                 &mock_source_spec_builder);
@@ -228,19 +242,21 @@ TEST_F(TestMockMigrationRawFormat, GetImageSize) {
   ASSERT_EQ(0, ctx3.wait());
 }
 
-TEST_F(TestMockMigrationRawFormat, GetImageSizeSnapshot) {
+TEST_F(TestMockMigrationRawFormat, GetImageSizeSnapshotDNE) {
   MockTestImageCtx mock_image_ctx(*m_image_ctx);
 
   InSequence seq;
   MockSourceSpecBuilder mock_source_spec_builder;
 
-  auto mock_stream_interface = new MockStreamInterface();
-  expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+  auto mock_snapshot_interface = new MockSnapshotInterface();
+  expect_build_snapshot(mock_source_spec_builder, CEPH_NOSNAP,
+                        mock_snapshot_interface, 0);
 
-  expect_stream_open(*mock_stream_interface, 0);
-  expect_stream_get_size(*mock_stream_interface, 0, 0);
+  expect_snapshot_open(*mock_snapshot_interface, 0);
+  SnapInfo snap_info{{}, {}, 123, {}, 0, 0, {}};
+  expect_snapshot_get_info(*mock_snapshot_interface, snap_info);
 
-  expect_stream_close(*mock_stream_interface, 0);
+  expect_snapshot_close(*mock_snapshot_interface, 0);
 
   MockRawFormat mock_raw_format(&mock_image_ctx, json_object,
                                 &mock_source_spec_builder);
@@ -252,7 +268,7 @@ TEST_F(TestMockMigrationRawFormat, GetImageSizeSnapshot) {
   C_SaferCond ctx2;
   uint64_t size;
   mock_raw_format.get_image_size(0, &size, &ctx2);
-  ASSERT_EQ(-EINVAL, ctx2.wait());
+  ASSERT_EQ(-ENOENT, ctx2.wait());
 
   C_SaferCond ctx3;
   mock_raw_format.close(&ctx3);
@@ -265,17 +281,19 @@ TEST_F(TestMockMigrationRawFormat, Read) {
   InSequence seq;
   MockSourceSpecBuilder mock_source_spec_builder;
 
-  auto mock_stream_interface = new MockStreamInterface();
-  expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+  auto mock_snapshot_interface = new MockSnapshotInterface();
+  expect_build_snapshot(mock_source_spec_builder, CEPH_NOSNAP,
+                        mock_snapshot_interface, 0);
 
-  expect_stream_open(*mock_stream_interface, 0);
-  expect_stream_get_size(*mock_stream_interface, 0, 0);
+  expect_snapshot_open(*mock_snapshot_interface, 0);
+  SnapInfo snap_info{{}, {}, 123, {}, 0, 0, {}};
+  expect_snapshot_get_info(*mock_snapshot_interface, snap_info);
 
   bufferlist expect_bl;
   expect_bl.append(std::string(123, '1'));
-  expect_stream_read(*mock_stream_interface, {{123, 123}}, expect_bl, 0);
+  expect_snapshot_read(*mock_snapshot_interface, {{123, 123}}, expect_bl, 0);
 
-  expect_stream_close(*mock_stream_interface, 0);
+  expect_snapshot_close(*mock_snapshot_interface, 0);
 
   MockRawFormat mock_raw_format(&mock_image_ctx, json_object,
                                 &mock_source_spec_builder);
@@ -305,13 +323,15 @@ TEST_F(TestMockMigrationRawFormat, ListSnaps) {
   InSequence seq;
   MockSourceSpecBuilder mock_source_spec_builder;
 
-  auto mock_stream_interface = new MockStreamInterface();
-  expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+  auto mock_snapshot_interface = new MockSnapshotInterface();
+  expect_build_snapshot(mock_source_spec_builder, CEPH_NOSNAP,
+                        mock_snapshot_interface, 0);
 
-  expect_stream_open(*mock_stream_interface, 0);
-  expect_stream_get_size(*mock_stream_interface, 0, 0);
+  expect_snapshot_open(*mock_snapshot_interface, 0);
+  SnapInfo snap_info{{}, {}, 123, {}, 0, 0, {}};
+  expect_snapshot_get_info(*mock_snapshot_interface, snap_info);
 
-  expect_stream_close(*mock_stream_interface, 0);
+  expect_snapshot_close(*mock_snapshot_interface, 0);
 
   MockRawFormat mock_raw_format(&mock_image_ctx, json_object,
                                 &mock_source_spec_builder);
diff --git a/src/test/librbd/mock/migration/MockSnapshotInterface.h b/src/test/librbd/mock/migration/MockSnapshotInterface.h
new file mode 100644 (file)
index 0000000..abb6d1a
--- /dev/null
@@ -0,0 +1,44 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_TEST_LIBRBD_MOCK_MIGRATION_MOCK_SNAPSHOT_INTERFACE_H
+#define CEPH_TEST_LIBRBD_MOCK_MIGRATION_MOCK_SNAPSHOT_INTERFACE_H
+
+#include "include/buffer.h"
+#include "gmock/gmock.h"
+#include "librbd/io/AioCompletion.h"
+#include "librbd/io/ReadResult.h"
+#include "librbd/io/Types.h"
+#include "librbd/migration/SnapshotInterface.h"
+
+namespace librbd {
+namespace migration {
+
+struct MockSnapshotInterface : public SnapshotInterface {
+  MOCK_METHOD2(open, void(SnapshotInterface*, Context*));
+  MOCK_METHOD1(close, void(Context*));
+
+  MOCK_CONST_METHOD0(get_snap_info, const SnapInfo&());
+
+  MOCK_METHOD3(read, void(io::AioCompletion*, const io::Extents&,
+                          io::ReadResult&));
+  void read(io::AioCompletion* aio_comp, io::Extents&& image_extents,
+            io::ReadResult&& read_result, int op_flags, int read_flags,
+            const ZTracer::Trace &parent_trace) override {
+    read(aio_comp, image_extents, read_result);
+  }
+
+  MOCK_METHOD3(list_snap, void(const io::Extents&, io::SparseExtents*,
+                               Context*));
+  void list_snap(io::Extents&& image_extents, int list_snaps_flags,
+                 io::SparseExtents* sparse_extents,
+                 const ZTracer::Trace &parent_trace,
+                 Context* on_finish) override {
+    list_snap(image_extents, sparse_extents, on_finish);
+  }
+};
+
+} // namespace migration
+} // namespace librbd
+
+#endif // CEPH_TEST_LIBRBD_MOCK_MIGRATION_MOCK_SNAPSHOT_INTERFACE_H