start_mirror ${CLUSTER1}
image=test
create_image ${CLUSTER2} ${POOL} ${image}
+set_image_meta ${CLUSTER2} ${POOL} ${image} "key1" "value1"
+set_image_meta ${CLUSTER2} ${POOL} ${image} "key2" "value2"
wait_for_image_replay_started ${CLUSTER1} ${POOL} ${image}
write_image ${CLUSTER2} ${POOL} ${image} 100
wait_for_replay_complete ${CLUSTER1} ${CLUSTER2} ${POOL} ${image}
wait_for_status_in_pool_dir ${CLUSTER2} ${POOL} ${image} 'down+unknown'
fi
compare_images ${POOL} ${image}
+compare_image_meta ${CLUSTER1} ${POOL} ${image} "key1" "value1"
+compare_image_meta ${CLUSTER1} ${POOL} ${image} "key2" "value2"
testlog "TEST: stop mirror, add image, start mirror and test replay"
stop_mirror ${CLUSTER1}
rbd --cluster ${cluster} -p ${pool} image-meta set ${image} $key $val
}
+compare_image_meta()
+{
+ local cluster=$1
+ local pool=$2
+ local image=$3
+ local key=$4
+ local value=$5
+
+ test `rbd --cluster ${cluster} -p ${pool} image-meta get ${image} ${key}` = "${value}"
+}
+
rename_image()
{
local cluster=$1
image_replayer/test_mock_PrepareLocalImageRequest.cc
image_replayer/test_mock_PrepareRemoteImageRequest.cc
image_sync/test_mock_ImageCopyRequest.cc
+ image_sync/test_mock_MetadataCopyRequest.cc
image_sync/test_mock_ObjectCopyRequest.cc
image_sync/test_mock_SnapshotCopyRequest.cc
image_sync/test_mock_SnapshotCreateRequest.cc
--- /dev/null
+// -*- 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 "include/rbd/librbd.hpp"
+#include "include/stringify.h"
+#include "librbd/ImageCtx.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "tools/rbd_mirror/image_sync/MetadataCopyRequest.h"
+#include <map>
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public librbd::MockImageCtx {
+ MockTestImageCtx(librbd::ImageCtx &image_ctx)
+ : librbd::MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+} // namespace librbd
+
+// template definitions
+#include "tools/rbd_mirror/image_sync/MetadataCopyRequest.cc"
+
+namespace rbd {
+namespace mirror {
+namespace image_sync {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+class TestMockImageSyncMetadataCopyRequest : public TestMockFixture {
+public:
+ typedef MetadataCopyRequest<librbd::MockTestImageCtx> MockMetadataCopyRequest;
+ typedef std::map<std::string, bufferlist> Metadata;
+
+ 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));
+
+ ASSERT_EQ(0, create_image(rbd, m_local_io_ctx, m_image_name, m_image_size));
+ ASSERT_EQ(0, open_image(m_local_io_ctx, m_image_name, &m_local_image_ctx));
+ }
+
+ void expect_metadata_list(librbd::MockTestImageCtx &mock_image_ctx,
+ const Metadata& metadata, int r) {
+ bufferlist out_bl;
+ ::encode(metadata, out_bl);
+
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(mock_image_ctx.header_oid, _, StrEq("rbd"),
+ StrEq("metadata_list"), _, _, _))
+ .WillOnce(DoAll(WithArg<5>(CopyInBufferlist(out_bl)),
+ Return(r)));
+ }
+
+ void expect_metadata_set(librbd::MockTestImageCtx &mock_image_ctx,
+ const Metadata& metadata, int r) {
+ bufferlist in_bl;
+ ::encode(metadata, in_bl);
+
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(mock_image_ctx.header_oid, _, StrEq("rbd"),
+ StrEq("metadata_set"), ContentsEqual(in_bl), _, _))
+ .WillOnce(Return(r));
+ }
+
+ librbd::ImageCtx *m_remote_image_ctx;
+ librbd::ImageCtx *m_local_image_ctx;
+};
+
+TEST_F(TestMockImageSyncMetadataCopyRequest, Success) {
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+ size_t idx = 1;
+ Metadata key_values_1;
+ for (; idx <= 128; ++idx) {
+ bufferlist bl;
+ bl.append("value" + stringify(idx));
+ key_values_1.emplace("key" + stringify(idx), bl);
+ }
+
+ Metadata key_values_2;
+ for (; idx <= 255; ++idx) {
+ bufferlist bl;
+ bl.append("value" + stringify(idx));
+ key_values_2.emplace("key" + stringify(idx), bl);
+ }
+
+ InSequence seq;
+ expect_metadata_list(mock_remote_image_ctx, key_values_1, 0);
+ expect_metadata_set(mock_local_image_ctx, key_values_1, 0);
+ expect_metadata_list(mock_remote_image_ctx, key_values_2, 0);
+ expect_metadata_set(mock_local_image_ctx, key_values_2, 0);
+
+ C_SaferCond ctx;
+ auto request = MockMetadataCopyRequest::create(&mock_local_image_ctx,
+ &mock_remote_image_ctx,
+ &ctx);
+ request->send();
+
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageSyncMetadataCopyRequest, Empty) {
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+ Metadata key_values;
+
+ InSequence seq;
+ expect_metadata_list(mock_remote_image_ctx, key_values, 0);
+
+ C_SaferCond ctx;
+ auto request = MockMetadataCopyRequest::create(&mock_local_image_ctx,
+ &mock_remote_image_ctx,
+ &ctx);
+ request->send();
+
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageSyncMetadataCopyRequest, MetadataListError) {
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+ Metadata key_values;
+
+ InSequence seq;
+ expect_metadata_list(mock_remote_image_ctx, key_values, -EINVAL);
+
+ C_SaferCond ctx;
+ auto request = MockMetadataCopyRequest::create(&mock_local_image_ctx,
+ &mock_remote_image_ctx,
+ &ctx);
+ request->send();
+
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageSyncMetadataCopyRequest, MetadataSetError) {
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+ Metadata key_values;
+ bufferlist bl;
+ bl.append("value");
+ key_values.emplace("key", bl);
+
+ InSequence seq;
+ expect_metadata_list(mock_remote_image_ctx, key_values, 0);
+ expect_metadata_set(mock_local_image_ctx, key_values, -EINVAL);
+
+ C_SaferCond ctx;
+ auto request = MockMetadataCopyRequest::create(&mock_local_image_ctx,
+ &mock_remote_image_ctx,
+ &ctx);
+ request->send();
+
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace image_sync
+} // namespace mirror
+} // namespace rbd
#include "tools/rbd_mirror/ImageSync.h"
#include "tools/rbd_mirror/Threads.h"
#include "tools/rbd_mirror/image_sync/ImageCopyRequest.h"
+#include "tools/rbd_mirror/image_sync/MetadataCopyRequest.h"
#include "tools/rbd_mirror/image_sync/SnapshotCopyRequest.h"
#include "tools/rbd_mirror/image_sync/SyncPointCreateRequest.h"
#include "tools/rbd_mirror/image_sync/SyncPointPruneRequest.h"
MOCK_METHOD0(send, void());
};
+template <>
+class MetadataCopyRequest<librbd::MockTestImageCtx> {
+public:
+ static MetadataCopyRequest* s_instance;
+ Context *on_finish;
+
+ static MetadataCopyRequest* create(librbd::MockTestImageCtx *local_image_ctx,
+ librbd::MockTestImageCtx *remote_image_ctx,
+ Context *on_finish) {
+ assert(s_instance != nullptr);
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ MetadataCopyRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
template <>
class SnapshotCopyRequest<librbd::MockTestImageCtx> {
public:
};
ImageCopyRequest<librbd::MockTestImageCtx>* ImageCopyRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+MetadataCopyRequest<librbd::MockTestImageCtx>* MetadataCopyRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
SnapshotCopyRequest<librbd::MockTestImageCtx>* SnapshotCopyRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
SyncPointCreateRequest<librbd::MockTestImageCtx>* SyncPointCreateRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
SyncPointPruneRequest<librbd::MockTestImageCtx>* SyncPointPruneRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
typedef ImageSync<librbd::MockTestImageCtx> MockImageSync;
typedef InstanceWatcher<librbd::MockTestImageCtx> MockInstanceWatcher;
typedef image_sync::ImageCopyRequest<librbd::MockTestImageCtx> MockImageCopyRequest;
+ typedef image_sync::MetadataCopyRequest<librbd::MockTestImageCtx> MockMetadataCopyRequest;
typedef image_sync::SnapshotCopyRequest<librbd::MockTestImageCtx> MockSnapshotCopyRequest;
typedef image_sync::SyncPointCreateRequest<librbd::MockTestImageCtx> MockSyncPointCreateRequest;
typedef image_sync::SyncPointPruneRequest<librbd::MockTestImageCtx> MockSyncPointPruneRequest;
}));
}
+ void expect_copy_metadata(MockMetadataCopyRequest &mock_metadata_copy_request,
+ int r) {
+ EXPECT_CALL(mock_metadata_copy_request, send())
+ .WillOnce(Invoke([this, &mock_metadata_copy_request, r]() {
+ m_threads->work_queue->queue(mock_metadata_copy_request.on_finish, r);
+ }));
+ }
+
void expect_rollback_object_map(librbd::MockObjectMap &mock_object_map, int r) {
if ((m_local_image_ctx->features & RBD_FEATURE_OBJECT_MAP) != 0) {
EXPECT_CALL(mock_object_map, rollback(_, _))
MockSnapshotCopyRequest mock_snapshot_copy_request;
MockSyncPointCreateRequest mock_sync_point_create_request;
MockSyncPointPruneRequest mock_sync_point_prune_request;
+ MockMetadataCopyRequest mock_metadata_copy_request;
librbd::MockExclusiveLock mock_exclusive_lock;
mock_local_image_ctx.exclusive_lock = &mock_exclusive_lock;
expect_create_object_map(mock_local_image_ctx, mock_object_map);
expect_open_object_map(mock_local_image_ctx, *mock_object_map);
expect_prune_sync_point(mock_sync_point_prune_request, true, 0);
+ expect_copy_metadata(mock_metadata_copy_request, 0);
expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
C_SaferCond ctx;
MockSnapshotCopyRequest mock_snapshot_copy_request;
MockSyncPointCreateRequest mock_sync_point_create_request;
MockSyncPointPruneRequest mock_sync_point_prune_request;
+ MockMetadataCopyRequest mock_metadata_copy_request;
m_client_meta.sync_points = {{cls::rbd::UserSnapshotNamespace(), "snap1", boost::none},
{cls::rbd::UserSnapshotNamespace(), "snap2", "snap1", boost::none}};
expect_create_object_map(mock_local_image_ctx, mock_object_map);
expect_open_object_map(mock_local_image_ctx, *mock_object_map);
expect_prune_sync_point(mock_sync_point_prune_request, true, 0);
+ expect_copy_metadata(mock_metadata_copy_request, 0);
expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
C_SaferCond ctx;
class MockImageCtx;
}
+ACTION_P(CopyInBufferlist, str) {
+ arg0->append(str);
+}
+
ACTION_P(CompleteContext, r) {
arg0->complete(r);
}
image_replayer/PrepareRemoteImageRequest.cc
image_replayer/ReplayStatusFormatter.cc
image_sync/ImageCopyRequest.cc
+ image_sync/MetadataCopyRequest.cc
image_sync/ObjectCopyRequest.cc
image_sync/SnapshotCopyRequest.cc
image_sync/SnapshotCreateRequest.cc
#include "librbd/Utils.h"
#include "librbd/journal/Types.h"
#include "tools/rbd_mirror/image_sync/ImageCopyRequest.h"
+#include "tools/rbd_mirror/image_sync/MetadataCopyRequest.h"
#include "tools/rbd_mirror/image_sync/SnapshotCopyRequest.h"
#include "tools/rbd_mirror/image_sync/SyncPointCreateRequest.h"
#include "tools/rbd_mirror/image_sync/SyncPointPruneRequest.h"
return;
}
+ send_copy_metadata();
+}
+
+template <typename I>
+void ImageSync<I>::send_copy_metadata() {
+ dout(20) << dendl;
+ update_progress("COPY_METADATA");
+
+ Context *ctx = create_context_callback<
+ ImageSync<I>, &ImageSync<I>::handle_copy_metadata>(this);
+ auto request = MetadataCopyRequest<I>::create(
+ m_local_image_ctx, m_remote_image_ctx, ctx);
+ request->send();
+}
+
+template <typename I>
+void ImageSync<I>::handle_copy_metadata(int r) {
+ dout(20) << ": r=" << r << dendl;
+ if (r < 0) {
+ derr << ": failed to copy metadata: " << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ }
+
finish(0);
}
* v .
* REFRESH_OBJECT_MAP (skip if object .
* | map disabled) .
- * v
+ * v .
* PRUNE_SYNC_POINTS . (image sync canceled)
* | .
* v .
+ * COPY_METADATA .
+ * | .
+ * v .
* <finish> < . . . . . . . . . . . . . .
*
* @endverbatim
void send_prune_sync_points();
void handle_prune_sync_points(int r);
+ void send_copy_metadata();
+ void handle_copy_metadata(int r);
+
void update_progress(const std::string &description);
};
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "tools/rbd_mirror/image_sync/MetadataCopyRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "librbd/Utils.h"
+
+#define dout_context g_ceph_context
+#define dout_subsys ceph_subsys_rbd_mirror
+#undef dout_prefix
+#define dout_prefix *_dout << "rbd::mirror::image_sync::MetadataCopyRequest: " \
+ << this << " " << __func__ << ": "
+
+namespace rbd {
+namespace mirror {
+namespace image_sync {
+
+namespace {
+
+const uint64_t MAX_METADATA_ITEMS = 128;
+
+} // anonymous namespace
+
+using librbd::util::create_rados_callback;
+
+template <typename I>
+void MetadataCopyRequest<I>::send() {
+ list_remote_metadata();
+}
+
+template <typename I>
+void MetadataCopyRequest<I>::list_remote_metadata() {
+ dout(20) << "start_key=" << m_last_metadata_key << dendl;
+
+ librados::ObjectReadOperation op;
+ librbd::cls_client::metadata_list_start(&op, m_last_metadata_key, MAX_METADATA_ITEMS);
+
+ librados::AioCompletion *aio_comp = create_rados_callback<
+ MetadataCopyRequest<I>,
+ &MetadataCopyRequest<I>::handle_list_remote_data>(this);
+ m_out_bl.clear();
+ m_remote_image_ctx->md_ctx.aio_operate(m_remote_image_ctx->header_oid,
+ aio_comp, &op, &m_out_bl);
+ aio_comp->release();
+}
+
+template <typename I>
+void MetadataCopyRequest<I>::handle_list_remote_data(int r) {
+ dout(20) << "r=" << r << dendl;
+
+ Metadata metadata;
+ if (r == 0) {
+ bufferlist::iterator it = m_out_bl.begin();
+ r = librbd::cls_client::metadata_list_finish(&it, &metadata);
+ }
+
+ if (r < 0) {
+ derr << "failed to retrieve metadata: " << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ }
+
+ if (metadata.empty()) {
+ finish(0);
+ return;
+ }
+
+ m_last_metadata_key = metadata.rbegin()->first;
+ m_more_metadata = (metadata.size() >= MAX_METADATA_ITEMS);
+ set_local_metadata(metadata);
+}
+
+template <typename I>
+void MetadataCopyRequest<I>::set_local_metadata(const Metadata& metadata) {
+ dout(20) << "count=" << metadata.size() << dendl;
+
+ librados::ObjectWriteOperation op;
+ librbd::cls_client::metadata_set(&op, metadata);
+
+ librados::AioCompletion *aio_comp = create_rados_callback<
+ MetadataCopyRequest<I>,
+ &MetadataCopyRequest<I>::handle_set_local_metadata>(this);
+ m_local_image_ctx->md_ctx.aio_operate(m_local_image_ctx->header_oid, aio_comp,
+ &op);
+ aio_comp->release();
+}
+
+template <typename I>
+void MetadataCopyRequest<I>::handle_set_local_metadata(int r) {
+ dout(20) << "r=" << r << dendl;
+
+ if (r < 0) {
+ derr << "failed to set metadata: " << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ }
+
+ if (m_more_metadata) {
+ list_remote_metadata();
+ return;
+ }
+
+ finish(0);
+}
+
+template <typename I>
+void MetadataCopyRequest<I>::finish(int r) {
+ dout(20) << "r=" << r << dendl;
+ m_on_finish->complete(r);
+ delete this;
+}
+
+} // namespace image_sync
+} // namespace mirror
+} // namespace rbd
+
+template class rbd::mirror::image_sync::MetadataCopyRequest<librbd::ImageCtx>;
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef RBD_MIRROR_IMAGE_SYNC_METADATA_COPY_REQUEST_H
+#define RBD_MIRROR_IMAGE_SYNC_METADATA_COPY_REQUEST_H
+
+#include "include/int_types.h"
+#include "include/buffer.h"
+#include "include/rados/librados.hpp"
+#include "librbd/ImageCtx.h"
+#include <map>
+#include <string>
+
+class Context;
+
+namespace rbd {
+namespace mirror {
+namespace image_sync {
+
+template <typename ImageCtxT = librbd::ImageCtx>
+class MetadataCopyRequest {
+public:
+ static MetadataCopyRequest* create(ImageCtxT *local_image_ctx,
+ ImageCtxT *remote_image_ctx,
+ Context *on_finish) {
+ return new MetadataCopyRequest(local_image_ctx, remote_image_ctx,
+ on_finish);
+ }
+
+ MetadataCopyRequest(ImageCtxT *local_image_ctx, ImageCtxT *remote_image_ctx,
+ Context *on_finish)
+ : m_local_image_ctx(local_image_ctx), m_remote_image_ctx(remote_image_ctx),
+ m_on_finish(on_finish) {
+ }
+
+ void send();
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start>
+ * |
+ * v
+ * LIST_REMOTE_METADATA <-----\
+ * | | (repeat if additional
+ * v | metadata)
+ * SET_LOCAL_METADATA --------/
+ * |
+ * v
+ * <finish>
+ *
+ * @endverbatim
+ */
+ typedef std::map<std::string, bufferlist> Metadata;
+
+ ImageCtxT *m_local_image_ctx;
+ ImageCtxT *m_remote_image_ctx;
+ Context *m_on_finish;
+
+ bufferlist m_out_bl;
+
+ std::string m_last_metadata_key;
+ bool m_more_metadata = false;
+
+ void list_remote_metadata();
+ void handle_list_remote_data(int r);
+
+ void set_local_metadata(const Metadata& metadata);
+ void handle_set_local_metadata(int r);
+
+ void finish(int r);
+
+};
+
+} // namespace image_sync
+} // namespace mirror
+} // namespace rbd
+
+extern template class rbd::mirror::image_sync::MetadataCopyRequest<librbd::ImageCtx>;
+
+#endif // RBD_MIRROR_IMAGE_SYNC_METADATA_COPY_REQUEST_H