rbd-mirror: extract journal local image creation to new state machine

This state machine simplifies the existing bootstrap state machine steps
in that it will always unregister as a first step (might not be registered).
It also take advantage of the previous fix for REPLAYING vs SYNCING state
so that it always uses an initial SYNCING state.

Signed-off-by: Jason Dillaman <dillaman@redhat.com>
This commit is contained in:
Jason Dillaman 2019-12-20 10:43:48 -05:00
parent 8c33391a62
commit 638be259f2
5 changed files with 680 additions and 0 deletions

View File

@ -38,6 +38,7 @@ add_executable(unittest_rbd_mirror
image_replayer/test_mock_GetMirrorImageIdRequest.cc image_replayer/test_mock_GetMirrorImageIdRequest.cc
image_replayer/test_mock_PrepareLocalImageRequest.cc image_replayer/test_mock_PrepareLocalImageRequest.cc
image_replayer/test_mock_PrepareRemoteImageRequest.cc image_replayer/test_mock_PrepareRemoteImageRequest.cc
image_replayer/journal/test_mock_CreateLocalImageRequest.cc
image_replayer/journal/test_mock_EventPreprocessor.cc image_replayer/journal/test_mock_EventPreprocessor.cc
image_replayer/journal/test_mock_Replayer.cc image_replayer/journal/test_mock_Replayer.cc
image_sync/test_mock_SyncPointCreateRequest.cc image_sync/test_mock_SyncPointCreateRequest.cc

View File

@ -0,0 +1,362 @@
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
#include "test/rbd_mirror/test_mock_fixture.h"
#include "librbd/journal/Types.h"
#include "librbd/journal/TypeTraits.h"
#include "tools/rbd_mirror/Threads.h"
#include "tools/rbd_mirror/image_replayer/CreateImageRequest.h"
#include "tools/rbd_mirror/image_replayer/journal/CreateLocalImageRequest.h"
#include "test/journal/mock/MockJournaler.h"
#include "test/librbd/mock/MockImageCtx.h"
#include "test/rbd_mirror/mock/MockContextWQ.h"
#include "test/rbd_mirror/mock/MockSafeTimer.h"
#include <boost/intrusive_ptr.hpp>
namespace librbd {
namespace {
struct MockTestImageCtx : public librbd::MockImageCtx {
explicit MockTestImageCtx(librbd::ImageCtx &image_ctx)
: librbd::MockImageCtx(image_ctx) {
}
};
} // anonymous namespace
namespace journal {
template <>
struct TypeTraits<librbd::MockTestImageCtx> {
typedef ::journal::MockJournaler Journaler;
};
} // namespace journal
namespace util {
static std::string s_image_id;
template <>
std::string generate_image_id<MockTestImageCtx>(librados::IoCtx&) {
ceph_assert(!s_image_id.empty());
return s_image_id;
}
} // namespace util
} // namespace librbd
namespace rbd {
namespace mirror {
template <>
struct Threads<librbd::MockTestImageCtx> {
};
namespace image_replayer {
template<>
struct CreateImageRequest<librbd::MockTestImageCtx> {
static CreateImageRequest* s_instance;
Context *on_finish = nullptr;
static CreateImageRequest* create(Threads<librbd::MockTestImageCtx>* threads,
librados::IoCtx &local_io_ctx,
const std::string &global_image_id,
const std::string &remote_mirror_uuid,
const std::string &local_image_name,
const std::string &local_image_id,
librbd::MockTestImageCtx *remote_image_ctx,
Context *on_finish) {
ceph_assert(s_instance != nullptr);
s_instance->on_finish = on_finish;
s_instance->construct(local_image_id);
return s_instance;
}
CreateImageRequest() {
ceph_assert(s_instance == nullptr);
s_instance = this;
}
~CreateImageRequest() {
s_instance = nullptr;
}
MOCK_METHOD1(construct, void(const std::string&));
MOCK_METHOD0(send, void());
};
CreateImageRequest<librbd::MockTestImageCtx>*
CreateImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
} // namespace image_replayer
} // namespace mirror
} // namespace rbd
#include "tools/rbd_mirror/image_replayer/journal/CreateLocalImageRequest.cc"
using ::testing::_;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::WithArg;
namespace rbd {
namespace mirror {
namespace image_replayer {
namespace journal {
class TestMockImageReplayerJournalCreateLocalImageRequest : public TestMockFixture {
public:
typedef CreateLocalImageRequest<librbd::MockTestImageCtx> MockCreateLocalImageRequest;
typedef Threads<librbd::MockTestImageCtx> MockThreads;
typedef CreateImageRequest<librbd::MockTestImageCtx> MockCreateImageRequest;
void SetUp() override {
TestMockFixture::SetUp();
librbd::RBD rbd;
ASSERT_EQ(0, create_image(rbd, m_remote_io_ctx, m_image_name, m_image_size));
ASSERT_EQ(0, open_image(m_remote_io_ctx, m_image_name, &m_remote_image_ctx));
m_mock_remote_image_ctx = new librbd::MockTestImageCtx(*m_remote_image_ctx);
}
void TearDown() override {
delete m_mock_remote_image_ctx;
TestMockFixture::TearDown();
}
void expect_journaler_register_client(
::journal::MockJournaler& mock_journaler,
const librbd::journal::ClientData& client_data, int r) {
bufferlist bl;
encode(client_data, bl);
EXPECT_CALL(mock_journaler, register_client(ContentsEqual(bl), _))
.WillOnce(WithArg<1>(Invoke([this, r](Context *on_finish) {
m_threads->work_queue->queue(on_finish, r);
})));
}
void expect_journaler_unregister_client(
::journal::MockJournaler& mock_journaler, int r) {
EXPECT_CALL(mock_journaler, unregister_client(_))
.WillOnce(Invoke([this, r](Context *on_finish) {
m_threads->work_queue->queue(on_finish, r);
}));
}
void expect_journaler_update_client(
::journal::MockJournaler& mock_journaler,
const librbd::journal::ClientData& client_data, int r) {
bufferlist bl;
encode(client_data, bl);
EXPECT_CALL(mock_journaler, update_client(ContentsEqual(bl), _))
.WillOnce(WithArg<1>(Invoke([this, r](Context *on_finish) {
m_threads->work_queue->queue(on_finish, r);
})));
}
void expect_create_image(MockCreateImageRequest& mock_create_image_request,
const std::string& image_id, int r) {
EXPECT_CALL(mock_create_image_request, construct(image_id));
EXPECT_CALL(mock_create_image_request, send())
.WillOnce(Invoke([this, &mock_create_image_request, r]() {
m_threads->work_queue->queue(mock_create_image_request.on_finish, r);
}));
}
MockCreateLocalImageRequest* create_request(
MockThreads& mock_threads,
::journal::MockJournaler& mock_journaler,
const std::string& global_image_id,
const std::string& remote_mirror_uuid,
librbd::journal::MirrorPeerClientMeta* mirror_peer_client_meta,
std::string* local_image_id, Context* on_finish) {
return new MockCreateLocalImageRequest(
&mock_threads, m_local_io_ctx, m_mock_remote_image_ctx,
&mock_journaler, global_image_id, remote_mirror_uuid,
mirror_peer_client_meta, nullptr, local_image_id, on_finish);
}
librbd::ImageCtx *m_remote_image_ctx;
librbd::MockTestImageCtx *m_mock_remote_image_ctx = nullptr;
};
TEST_F(TestMockImageReplayerJournalCreateLocalImageRequest, Success) {
InSequence seq;
// re-register the client
::journal::MockJournaler mock_journaler;
expect_journaler_unregister_client(mock_journaler, 0);
librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
librbd::util::s_image_id = "local image id";
mirror_peer_client_meta.image_id = "local image id";
mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_SYNCING;
librbd::journal::ClientData client_data;
client_data.client_meta = mirror_peer_client_meta;
expect_journaler_register_client(mock_journaler, client_data, 0);
// create the missing local image
MockCreateImageRequest mock_create_image_request;
expect_create_image(mock_create_image_request, "local image id", 0);
C_SaferCond ctx;
MockThreads mock_threads;
std::string local_image_id;
auto request = create_request(
mock_threads, mock_journaler, "global image id", "remote mirror uuid",
&mirror_peer_client_meta, &local_image_id, &ctx);
request->send();
ASSERT_EQ(0, ctx.wait());
ASSERT_EQ("local image id", local_image_id);
ASSERT_EQ("local image id", mirror_peer_client_meta.image_id);
ASSERT_EQ(librbd::journal::MIRROR_PEER_STATE_SYNCING,
mirror_peer_client_meta.state);
}
TEST_F(TestMockImageReplayerJournalCreateLocalImageRequest, UnregisterError) {
InSequence seq;
// re-register the client
::journal::MockJournaler mock_journaler;
expect_journaler_unregister_client(mock_journaler, -EINVAL);
C_SaferCond ctx;
MockThreads mock_threads;
librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
std::string local_image_id;
auto request = create_request(
mock_threads, mock_journaler, "global image id", "remote mirror uuid",
&mirror_peer_client_meta, &local_image_id, &ctx);
request->send();
ASSERT_EQ(-EINVAL, ctx.wait());
}
TEST_F(TestMockImageReplayerJournalCreateLocalImageRequest, RegisterError) {
InSequence seq;
// re-register the client
::journal::MockJournaler mock_journaler;
expect_journaler_unregister_client(mock_journaler, 0);
librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
librbd::util::s_image_id = "local image id";
mirror_peer_client_meta.image_id = "local image id";
mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_SYNCING;
librbd::journal::ClientData client_data;
client_data.client_meta = mirror_peer_client_meta;
expect_journaler_register_client(mock_journaler, client_data, -EINVAL);
C_SaferCond ctx;
MockThreads mock_threads;
std::string local_image_id;
auto request = create_request(
mock_threads, mock_journaler, "global image id", "remote mirror uuid",
&mirror_peer_client_meta, &local_image_id, &ctx);
request->send();
ASSERT_EQ(-EINVAL, ctx.wait());
}
TEST_F(TestMockImageReplayerJournalCreateLocalImageRequest, CreateImageError) {
InSequence seq;
// re-register the client
::journal::MockJournaler mock_journaler;
expect_journaler_unregister_client(mock_journaler, 0);
librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
librbd::util::s_image_id = "local image id";
mirror_peer_client_meta.image_id = "local image id";
mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_SYNCING;
librbd::journal::ClientData client_data;
client_data.client_meta = mirror_peer_client_meta;
expect_journaler_register_client(mock_journaler, client_data, 0);
// create the missing local image
MockCreateImageRequest mock_create_image_request;
expect_create_image(mock_create_image_request, "local image id", -EINVAL);
C_SaferCond ctx;
MockThreads mock_threads;
std::string local_image_id;
auto request = create_request(
mock_threads, mock_journaler, "global image id", "remote mirror uuid",
&mirror_peer_client_meta, &local_image_id, &ctx);
request->send();
ASSERT_EQ(-EINVAL, ctx.wait());
}
TEST_F(TestMockImageReplayerJournalCreateLocalImageRequest, CreateImageDuplicate) {
InSequence seq;
// re-register the client
::journal::MockJournaler mock_journaler;
expect_journaler_unregister_client(mock_journaler, 0);
librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
librbd::util::s_image_id = "local image id";
mirror_peer_client_meta.image_id = "local image id";
mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_SYNCING;
librbd::journal::ClientData client_data;
client_data.client_meta = mirror_peer_client_meta;
expect_journaler_register_client(mock_journaler, client_data, 0);
// create the missing local image
MockCreateImageRequest mock_create_image_request;
expect_create_image(mock_create_image_request, "local image id", -EBADF);
// update image id
expect_journaler_update_client(mock_journaler, client_data, 0);
// re-create the local image
expect_create_image(mock_create_image_request, "local image id", 0);
C_SaferCond ctx;
MockThreads mock_threads;
std::string local_image_id;
auto request = create_request(
mock_threads, mock_journaler, "global image id", "remote mirror uuid",
&mirror_peer_client_meta, &local_image_id, &ctx);
request->send();
ASSERT_EQ(0, ctx.wait());
}
TEST_F(TestMockImageReplayerJournalCreateLocalImageRequest, UpdateClientImageError) {
InSequence seq;
// re-register the client
::journal::MockJournaler mock_journaler;
expect_journaler_unregister_client(mock_journaler, 0);
librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
librbd::util::s_image_id = "local image id";
mirror_peer_client_meta.image_id = "local image id";
mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_SYNCING;
librbd::journal::ClientData client_data;
client_data.client_meta = mirror_peer_client_meta;
expect_journaler_register_client(mock_journaler, client_data, 0);
// create the missing local image
MockCreateImageRequest mock_create_image_request;
expect_create_image(mock_create_image_request, "local image id", -EBADF);
// update image id
expect_journaler_update_client(mock_journaler, client_data, -EINVAL);
C_SaferCond ctx;
MockThreads mock_threads;
std::string local_image_id;
auto request = create_request(
mock_threads, mock_journaler, "global image id", "remote mirror uuid",
&mirror_peer_client_meta, &local_image_id, &ctx);
request->send();
ASSERT_EQ(-EINVAL, ctx.wait());
}
} // namespace journal
} // namespace image_replayer
} // namespace mirror
} // namespace rbd

View File

@ -41,6 +41,7 @@ set(rbd_mirror_internal
image_replayer/PrepareLocalImageRequest.cc image_replayer/PrepareLocalImageRequest.cc
image_replayer/PrepareRemoteImageRequest.cc image_replayer/PrepareRemoteImageRequest.cc
image_replayer/Utils.cc image_replayer/Utils.cc
image_replayer/journal/CreateLocalImageRequest.cc
image_replayer/journal/EventPreprocessor.cc image_replayer/journal/EventPreprocessor.cc
image_replayer/journal/Replayer.cc image_replayer/journal/Replayer.cc
image_replayer/journal/ReplayStatusFormatter.cc image_replayer/journal/ReplayStatusFormatter.cc

View File

@ -0,0 +1,194 @@
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
#include "CreateLocalImageRequest.h"
#include "include/rados/librados.hpp"
#include "common/debug.h"
#include "common/dout.h"
#include "common/errno.h"
#include "journal/Journaler.h"
#include "librbd/ImageCtx.h"
#include "librbd/Utils.h"
#include "tools/rbd_mirror/ProgressContext.h"
#include "tools/rbd_mirror/image_replayer/CreateImageRequest.h"
#define dout_context g_ceph_context
#define dout_subsys ceph_subsys_rbd_mirror
#undef dout_prefix
#define dout_prefix *_dout << "rbd::mirror::image_replayer::journal::" \
<< "CreateLocalImageRequest: " << this << " " \
<< __func__ << ": "
namespace rbd {
namespace mirror {
namespace image_replayer {
namespace journal {
using librbd::util::create_context_callback;
template <typename I>
void CreateLocalImageRequest<I>::send() {
*m_local_image_id = "";
unregister_client();
}
template <typename I>
void CreateLocalImageRequest<I>::unregister_client() {
dout(10) << dendl;
update_progress("UNREGISTER_CLIENT");
auto ctx = create_context_callback<
CreateLocalImageRequest<I>,
&CreateLocalImageRequest<I>::handle_unregister_client>(this);
m_remote_journaler->unregister_client(ctx);
}
template <typename I>
void CreateLocalImageRequest<I>::handle_unregister_client(int r) {
dout(10) << "r=" << r << dendl;
if (r < 0 && r != -ENOENT) {
derr << "failed to unregister with remote journal: " << cpp_strerror(r)
<< dendl;
finish(r);
return;
}
*m_client_meta = librbd::journal::MirrorPeerClientMeta{""};
register_client();
}
template <typename I>
void CreateLocalImageRequest<I>::register_client() {
ceph_assert(m_local_image_id->empty());
*m_local_image_id = librbd::util::generate_image_id<I>(m_local_io_ctx);
dout(10) << "local_image_id=" << *m_local_image_id << dendl;
update_progress("REGISTER_CLIENT");
librbd::journal::MirrorPeerClientMeta client_meta{*m_local_image_id};
client_meta.state = librbd::journal::MIRROR_PEER_STATE_SYNCING;
librbd::journal::ClientData client_data{client_meta};
bufferlist client_data_bl;
encode(client_data, client_data_bl);
auto ctx = create_context_callback<
CreateLocalImageRequest<I>,
&CreateLocalImageRequest<I>::handle_register_client>(this);
m_remote_journaler->register_client(client_data_bl, ctx);
}
template <typename I>
void CreateLocalImageRequest<I>::handle_register_client(int r) {
dout(10) << "r=" << r << dendl;
if (r < 0) {
derr << "failed to register with remote journal: " << cpp_strerror(r)
<< dendl;
finish(r);
return;
}
*m_client_meta = librbd::journal::MirrorPeerClientMeta{*m_local_image_id};
m_client_meta->state = librbd::journal::MIRROR_PEER_STATE_SYNCING;
create_local_image();
}
template <typename I>
void CreateLocalImageRequest<I>::create_local_image() {
dout(10) << "local_image_id=" << *m_local_image_id << dendl;
update_progress("CREATE_LOCAL_IMAGE");
m_remote_image_ctx->image_lock.lock_shared();
std::string image_name = m_remote_image_ctx->name;
m_remote_image_ctx->image_lock.unlock_shared();
auto ctx = create_context_callback<
CreateLocalImageRequest<I>,
&CreateLocalImageRequest<I>::handle_create_local_image>(this);
auto request = CreateImageRequest<I>::create(
m_threads, m_local_io_ctx, m_global_image_id, m_remote_mirror_uuid,
image_name, *m_local_image_id, m_remote_image_ctx, ctx);
request->send();
}
template <typename I>
void CreateLocalImageRequest<I>::handle_create_local_image(int r) {
dout(10) << "r=" << r << dendl;
if (r == -EBADF) {
dout(5) << "image id " << *m_local_image_id << " already in-use" << dendl;
*m_local_image_id = "";
update_client_image();
return;
} else if (r < 0) {
if (r == -ENOENT) {
dout(10) << "parent image does not exist" << dendl;
} else {
derr << "failed to create local image: " << cpp_strerror(r) << dendl;
}
finish(r);
return;
}
finish(0);
}
template <typename I>
void CreateLocalImageRequest<I>::update_client_image() {
ceph_assert(m_local_image_id->empty());
*m_local_image_id = librbd::util::generate_image_id<I>(m_local_io_ctx);
dout(10) << "local_image_id=" << *m_local_image_id << dendl;
update_progress("UPDATE_CLIENT_IMAGE");
librbd::journal::MirrorPeerClientMeta client_meta{*m_local_image_id};
client_meta.state = librbd::journal::MIRROR_PEER_STATE_SYNCING;
librbd::journal::ClientData client_data(client_meta);
bufferlist data_bl;
encode(client_data, data_bl);
auto ctx = create_context_callback<
CreateLocalImageRequest<I>,
&CreateLocalImageRequest<I>::handle_update_client_image>(this);
m_remote_journaler->update_client(data_bl, ctx);
}
template <typename I>
void CreateLocalImageRequest<I>::handle_update_client_image(int r) {
dout(10) << "r=" << r << dendl;
if (r < 0) {
derr << "failed to update client: " << cpp_strerror(r) << dendl;
finish(r);
return;
}
*m_client_meta = librbd::journal::MirrorPeerClientMeta{*m_local_image_id};
m_client_meta->state = librbd::journal::MIRROR_PEER_STATE_SYNCING;
create_local_image();
}
template <typename I>
void CreateLocalImageRequest<I>::finish(int r) {
dout(10) << "r=" << r << dendl;
m_on_finish->complete(r);
delete this;
}
template <typename I>
void CreateLocalImageRequest<I>::update_progress(
const std::string& description) {
dout(15) << description << dendl;
if (m_progress_ctx != nullptr) {
m_progress_ctx->update_progress(description);
}
}
} // namespace journal
} // namespace image_replayer
} // namespace mirror
} // namespace rbd
template class rbd::mirror::image_replayer::journal::CreateLocalImageRequest<librbd::ImageCtx>;

View File

@ -0,0 +1,122 @@
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
#ifndef RBD_MIRROR_IMAGE_REPLAYER_JOURNAL_CREATE_LOCAL_IMAGE_REQUEST_H
#define RBD_MIRROR_IMAGE_REPLAYER_JOURNAL_CREATE_LOCAL_IMAGE_REQUEST_H
#include "include/rados/librados_fwd.hpp"
#include "librbd/journal/Types.h"
#include "librbd/journal/TypeTraits.h"
#include <string>
struct Context;
namespace librbd { class ImageCtx; }
namespace rbd {
namespace mirror {
class ProgressContext;
template <typename> struct Threads;
namespace image_replayer {
namespace journal {
template <typename ImageCtxT>
class CreateLocalImageRequest {
public:
typedef librbd::journal::MirrorPeerClientMeta MirrorPeerClientMeta;
typedef librbd::journal::TypeTraits<ImageCtxT> TypeTraits;
typedef typename TypeTraits::Journaler Journaler;
typedef rbd::mirror::ProgressContext ProgressContext;
static CreateLocalImageRequest* create(
Threads<ImageCtxT>* threads, librados::IoCtx& local_io_ctx,
ImageCtxT* remote_image_ctx, Journaler* remote_journaler,
const std::string& global_image_id,
const std::string& remote_mirror_uuid,
MirrorPeerClientMeta* client_meta, ProgressContext* progress_ctx,
std::string* local_image_id, Context* on_finish) {
return new CreateLocalImageRequest(threads, local_io_ctx, remote_image_ctx,
remote_journaler, global_image_id,
remote_mirror_uuid, client_meta,
progress_ctx, local_image_id, on_finish);
}
CreateLocalImageRequest(
Threads<ImageCtxT>* threads, librados::IoCtx& local_io_ctx,
ImageCtxT* remote_image_ctx, Journaler* remote_journaler,
const std::string& global_image_id,
const std::string& remote_mirror_uuid,
MirrorPeerClientMeta* client_meta, ProgressContext* progress_ctx,
std::string* local_image_id, Context* on_finish)
: m_threads(threads), m_local_io_ctx(local_io_ctx),
m_remote_image_ctx(remote_image_ctx),
m_remote_journaler(remote_journaler),
m_global_image_id(global_image_id),
m_remote_mirror_uuid(remote_mirror_uuid), m_client_meta(client_meta),
m_progress_ctx(progress_ctx), m_local_image_id(local_image_id),
m_on_finish(on_finish) {
}
void send();
private:
/**
* @verbatim
*
* <start>
* |
* v
* UNREGISTER_CLIENT
* |
* v
* REGISTER_CLIENT
* |
* | . . . . . . . . . UPDATE_CLIENT_IMAGE
* | . ^
* v v (id exists) *
* CREATE_LOCAL_IMAGE * * * * * * * * *
* |
* v
* <finish>
*
* @endverbatim
*/
Threads<ImageCtxT>* m_threads;
librados::IoCtx& m_local_io_ctx;
ImageCtxT* m_remote_image_ctx;
Journaler* m_remote_journaler;
std::string m_global_image_id;
std::string m_remote_mirror_uuid;
MirrorPeerClientMeta* m_client_meta;
ProgressContext* m_progress_ctx;
std::string* m_local_image_id;
Context* m_on_finish;
void unregister_client();
void handle_unregister_client(int r);
void register_client();
void handle_register_client(int r);
void create_local_image();
void handle_create_local_image(int r);
void update_client_image();
void handle_update_client_image(int r);
void finish(int r);
void update_progress(const std::string& description);
};
} // namespace journal
} // namespace image_replayer
} // namespace mirror
} // namespace rbd
extern template class rbd::mirror::image_replayer::journal::CreateLocalImageRequest<librbd::ImageCtx>;
#endif // RBD_MIRROR_IMAGE_REPLAYER_JOURNAL_CREATE_LOCAL_IMAGE_REQUEST_H