return 0;
}
+void metadata_get_start(librados::ObjectReadOperation* op,
+ const std::string &key) {
+ bufferlist bl;
+ encode(key, bl);
+
+ op->exec("rbd", "metadata_get", bl);
+}
+
+int metadata_get_finish(bufferlist::const_iterator *it,
+ std::string* value) {
+ try {
+ decode(*value, *it);
+ } catch (const buffer::error &err) {
+ return -EBADMSG;
+ }
+ return 0;
+}
+
int metadata_get(librados::IoCtx *ioctx, const std::string &oid,
const std::string &key, string *s)
{
ceph_assert(s);
- bufferlist in, out;
- encode(key, in);
- int r = ioctx->exec(oid, "rbd", "metadata_get", in, out);
- if (r < 0)
- return r;
+ librados::ObjectReadOperation op;
+ metadata_get_start(&op, key);
- auto iter = out.cbegin();
- try {
- decode(*s, iter);
- } catch (const buffer::error &err) {
- return -EBADMSG;
+ bufferlist out_bl;
+ int r = ioctx->operate(oid, &op, &out_bl);
+ if (r < 0) {
+ return r;
}
+ auto it = out_bl.cbegin();
+ r = metadata_get_finish(&it, s);
+ if (r < 0) {
+ return r;
+ }
return 0;
}
const std::string &key);
int metadata_remove(librados::IoCtx *ioctx, const std::string &oid,
const std::string &key);
+void metadata_get_start(librados::ObjectReadOperation* op,
+ const std::string &key);
+int metadata_get_finish(bufferlist::const_iterator *it,
+ std::string* value);
int metadata_get(librados::IoCtx *ioctx, const std::string &oid,
const std::string &key, string *v);
mirror/snapshot/CreatePrimaryRequest.cc
mirror/snapshot/DemoteRequest.cc
mirror/snapshot/GetImageStateRequest.cc
+ mirror/snapshot/ImageMeta.cc
mirror/snapshot/PromoteRequest.cc
mirror/snapshot/RemoveImageStateRequest.cc
mirror/snapshot/SetImageStateRequest.cc
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/mirror/snapshot/ImageMeta.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "json_spirit/json_spirit.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/Utils.h"
+#include "librbd/WatchNotifyTypes.h"
+#include "librbd/mirror/snapshot/Utils.h"
+#include "librbd/watcher/Notifier.h"
+
+#define dout_subsys ceph_subsys_rbd
+
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::mirror::snapshot::ImageMeta: " \
+ << this << " " << __func__ << ": "
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+using librbd::util::create_rados_callback;
+using librbd::mirror::snapshot::util::get_image_meta_key;
+
+template <typename I>
+ImageMeta<I>::ImageMeta(I* image_ctx, const std::string& mirror_uuid)
+ : m_image_ctx(image_ctx), m_mirror_uuid(mirror_uuid) {
+}
+
+template <typename I>
+void ImageMeta<I>::load(Context* on_finish) {
+ ldout(m_image_ctx->cct, 15) << "oid=" << m_image_ctx->header_oid << ", "
+ << "key=" << get_image_meta_key(m_mirror_uuid)
+ << dendl;
+
+ librados::ObjectReadOperation op;
+ cls_client::metadata_get_start(&op, get_image_meta_key(m_mirror_uuid));
+
+ m_out_bl.clear();
+ auto ctx = new LambdaContext([this, on_finish](int r) {
+ handle_load(on_finish, r);
+ });
+ auto aio_comp = create_rados_callback(ctx);
+ int r = m_image_ctx->md_ctx.aio_operate(m_image_ctx->header_oid, aio_comp,
+ &op, &m_out_bl);
+ ceph_assert(r == 0);
+ aio_comp->release();
+}
+
+template <typename I>
+void ImageMeta<I>::handle_load(Context* on_finish, int r) {
+ ldout(m_image_ctx->cct, 15) << "r=" << r << dendl;
+
+ std::string data;
+ if (r == 0) {
+ auto it = m_out_bl.cbegin();
+ r = cls_client::metadata_get_finish(&it, &data);
+ }
+
+ if (r == -ENOENT) {
+ ldout(m_image_ctx->cct, 15) << "no snapshot-based mirroring image-meta: "
+ << cpp_strerror(r) << dendl;
+ on_finish->complete(r);
+ return;
+ } else if (r < 0) {
+ lderr(m_image_ctx->cct) << "failed to load snapshot-based mirroring "
+ << "image-meta: " << cpp_strerror(r) << dendl;
+ on_finish->complete(r);
+ return;
+ }
+
+ bool json_valid = false;
+ json_spirit::mValue json_root;
+ if (json_spirit::read(data, json_root)) {
+ try {
+ auto& json_obj = json_root.get_obj();
+ resync_requested = json_obj["resync_requested"].get_bool();
+ json_valid = true;
+ } catch (std::runtime_error&) {
+ }
+ }
+
+ if (!json_valid) {
+ lderr(m_image_ctx->cct) << "invalid image-meta JSON received" << dendl;
+ on_finish->complete(-EBADMSG);
+ return;
+ }
+
+ on_finish->complete(0);
+}
+
+template <typename I>
+void ImageMeta<I>::save(Context* on_finish) {
+ ldout(m_image_ctx->cct, 15) << "oid=" << m_image_ctx->header_oid << ", "
+ << "key=" << get_image_meta_key(m_mirror_uuid)
+ << dendl;
+
+ // simple implementation for now
+ std::string json = "{\"resync_requested\": " +
+ std::string(resync_requested ? "true" : "false") + "}";
+
+ bufferlist bl;
+ bl.append(json);
+
+ // avoid using built-in metadata_set operation since that would require
+ // opening the non-primary image in read/write mode which isn't supported
+ librados::ObjectWriteOperation op;
+ cls_client::metadata_set(&op, {{get_image_meta_key(m_mirror_uuid), bl}});
+
+ auto ctx = new LambdaContext([this, on_finish](int r) {
+ handle_save(on_finish, r);
+ });
+ auto aio_comp = create_rados_callback(ctx);
+ int r = m_image_ctx->md_ctx.aio_operate(m_image_ctx->header_oid, aio_comp,
+ &op);
+ ceph_assert(r == 0);
+ aio_comp->release();
+}
+
+template <typename I>
+void ImageMeta<I>::handle_save(Context* on_finish, int r) {
+ ldout(m_image_ctx->cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_image_ctx->cct) << "failed to save snapshot-based mirroring "
+ << "image-meta: " << cpp_strerror(r) << dendl;
+ on_finish->complete(r);
+ return;
+ }
+
+ notify_update(on_finish);
+}
+
+template <typename I>
+void ImageMeta<I>::notify_update(Context* on_finish) {
+ ldout(m_image_ctx->cct, 15) << dendl;
+
+ // directly send header notification on image since you cannot
+ // open a non-primary image read/write and therefore cannot re-use
+ // the ImageWatcher to send the notification
+ bufferlist bl;
+ encode(watch_notify::NotifyMessage(watch_notify::HeaderUpdatePayload()), bl);
+
+ m_out_bl.clear();
+ auto ctx = new LambdaContext([this, on_finish](int r) {
+ handle_notify_update(on_finish, r);
+ });
+ auto aio_comp = create_rados_callback(ctx);
+ int r = m_image_ctx->md_ctx.aio_notify(
+ m_image_ctx->header_oid, aio_comp, bl, watcher::Notifier::NOTIFY_TIMEOUT,
+ &m_out_bl);
+ ceph_assert(r == 0);
+ aio_comp->release();
+}
+
+template <typename I>
+void ImageMeta<I>::handle_notify_update(Context* on_finish, int r) {
+ ldout(m_image_ctx->cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_image_ctx->cct) << "failed to notify image update: "
+ << cpp_strerror(r) << dendl;
+ }
+ on_finish->complete(r);
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+template class librbd::mirror::snapshot::ImageMeta<librbd::ImageCtx>;
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_MIRROR_SNAPSHOT_IMAGE_META_H
+#define CEPH_LIBRBD_MIRROR_SNAPSHOT_IMAGE_META_H
+
+#include "include/rados/librados.hpp"
+#include <string>
+
+struct Context;
+
+namespace librbd {
+
+struct ImageCtx;
+
+namespace mirror {
+namespace snapshot {
+
+template <typename ImageCtxT>
+class ImageMeta {
+public:
+ static ImageMeta* create(ImageCtxT* image_ctx,
+ const std::string& mirror_uuid) {
+ return new ImageMeta(image_ctx, mirror_uuid);
+ }
+
+ ImageMeta(ImageCtxT* image_ctx, const std::string& mirror_uuid);
+
+ void load(Context* on_finish);
+ void save(Context* on_finish);
+
+ bool resync_requested = false;
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start>
+ * |
+ * v
+ * METADATA_GET
+ * |
+ * v
+ * <idle>
+ * |
+ * v
+ * METADATA_SET
+ * |
+ * v
+ * NOTIFY_UPDATE
+ * |
+ * v
+ * <finish>
+ *
+ * @endverbatim
+ */
+
+ ImageCtxT* m_image_ctx;
+ std::string m_mirror_uuid;
+
+ bufferlist m_out_bl;
+
+ void handle_load(Context* on_finish, int r);
+
+ void handle_save(Context* on_finish, int r);
+
+ void notify_update(Context* on_finish);
+ void handle_notify_update(Context* on_finish, int r);
+
+};
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+extern template class librbd::mirror::snapshot::ImageMeta<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_MIRROR_SNAPSHOT_IMAGE_META_H
} // anonymous namespace
+std::string get_image_meta_key(const std::string& mirror_uuid) {
+ return ".rbd_mirror." + mirror_uuid;
+}
+
template <typename I>
bool can_create_primary_snapshot(I *image_ctx, bool demoted, bool force,
uint64_t *rollback_snap_id) {
#ifndef CEPH_LIBRBD_MIRROR_SNAPSHOT_UTILS_H
#define CEPH_LIBRBD_MIRROR_SNAPSHOT_UTILS_H
+#include "include/int_types.h"
#include "include/stringify.h"
+#include <string>
namespace librbd {
namespace snapshot {
namespace util {
+std::string get_image_meta_key(const std::string& mirror_uuid);
+
template <typename ImageCtxT = librbd::ImageCtx>
bool can_create_primary_snapshot(ImageCtxT *image_ctx, bool demoted, bool force,
uint64_t *rollback_snap_id);
managed_lock/test_mock_ReleaseRequest.cc
mirror/snapshot/test_mock_CreateNonPrimaryRequest.cc
mirror/snapshot/test_mock_CreatePrimaryRequest.cc
+ mirror/snapshot/test_mock_ImageMeta.cc
mirror/snapshot/test_mock_PromoteRequest.cc
mirror/snapshot/test_mock_UnlinkPeerRequest.cc
mirror/snapshot/test_mock_Utils.cc
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "librbd/ImageState.h"
+#include "librbd/mirror/snapshot/ImageMeta.h"
+#include "librbd/mirror/snapshot/Utils.h"
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+} // namespace librbd
+
+#include "librbd/mirror/snapshot/ImageMeta.cc"
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+class TestMockMirrorSnapshotImageMeta : public TestMockFixture {
+public:
+ typedef ImageMeta<MockTestImageCtx> MockImageMeta;
+
+ void expect_metadata_get(MockTestImageCtx& mock_image_ctx,
+ const std::string& mirror_uuid,
+ const std::string& value, int r) {
+ bufferlist in_bl;
+ ceph::encode(util::get_image_meta_key(mirror_uuid), in_bl);
+
+ bufferlist out_bl;
+ ceph::encode(value, out_bl);
+
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(mock_image_ctx.header_oid, _, StrEq("rbd"),
+ StrEq("metadata_get"), ContentsEqual(in_bl), _, _))
+ .WillOnce(DoAll(WithArg<5>(CopyInBufferlist(out_bl)),
+ Return(r)));
+ }
+
+ void expect_metadata_set(MockTestImageCtx& mock_image_ctx,
+ const std::string& mirror_uuid,
+ const std::string& value, int r) {
+ bufferlist value_bl;
+ value_bl.append(value);
+
+ bufferlist in_bl;
+ ceph::encode(
+ std::map<std::string, bufferlist>{
+ {util::get_image_meta_key(mirror_uuid), value_bl}},
+ 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));
+ }
+};
+
+TEST_F(TestMockMirrorSnapshotImageMeta, Load) {
+ librbd::ImageCtx* image_ctx;
+ ASSERT_EQ(0, open_image(m_image_name, &image_ctx));
+ MockTestImageCtx mock_image_ctx(*image_ctx);
+
+ InSequence seq;
+ expect_metadata_get(mock_image_ctx, "mirror uuid",
+ "{\"resync_requested\": true}", 0);
+
+ MockImageMeta mock_image_meta(&mock_image_ctx, "mirror uuid");
+ C_SaferCond ctx;
+ mock_image_meta.load(&ctx);
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotImageMeta, LoadError) {
+ librbd::ImageCtx* image_ctx;
+ ASSERT_EQ(0, open_image(m_image_name, &image_ctx));
+ MockTestImageCtx mock_image_ctx(*image_ctx);
+
+ InSequence seq;
+ expect_metadata_get(mock_image_ctx, "mirror uuid",
+ "{\"resync_requested\": true}", -EINVAL);
+
+ MockImageMeta mock_image_meta(&mock_image_ctx, "mirror uuid");
+ C_SaferCond ctx;
+ mock_image_meta.load(&ctx);
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotImageMeta, LoadCorrupt) {
+ librbd::ImageCtx* image_ctx;
+ ASSERT_EQ(0, open_image(m_image_name, &image_ctx));
+ MockTestImageCtx mock_image_ctx(*image_ctx);
+
+ InSequence seq;
+ expect_metadata_get(mock_image_ctx, "mirror uuid",
+ "\"resync_requested\": true}", 0);
+
+ MockImageMeta mock_image_meta(&mock_image_ctx, "mirror uuid");
+ C_SaferCond ctx;
+ mock_image_meta.load(&ctx);
+ ASSERT_EQ(-EBADMSG, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotImageMeta, Save) {
+ librbd::ImageCtx* image_ctx;
+ ASSERT_EQ(0, open_image(m_image_name, &image_ctx));
+ MockTestImageCtx mock_image_ctx(*image_ctx);
+
+ InSequence seq;
+ expect_metadata_set(mock_image_ctx, "mirror uuid",
+ "{\"resync_requested\": true}", 0);
+
+ MockImageMeta mock_image_meta(&mock_image_ctx, "mirror uuid");
+ mock_image_meta.resync_requested = true;
+
+ C_SaferCond ctx;
+ mock_image_meta.save(&ctx);
+ ASSERT_EQ(0, ctx.wait());
+
+ // should have sent image-update notification
+ ASSERT_TRUE(image_ctx->state->is_refresh_required());
+}
+
+TEST_F(TestMockMirrorSnapshotImageMeta, SaveError) {
+ librbd::ImageCtx* image_ctx;
+ ASSERT_EQ(0, open_image(m_image_name, &image_ctx));
+ MockTestImageCtx mock_image_ctx(*image_ctx);
+
+ InSequence seq;
+ expect_metadata_set(mock_image_ctx, "mirror uuid",
+ "{\"resync_requested\": false}", -EINVAL);
+
+ MockImageMeta mock_image_meta(&mock_image_ctx, "mirror uuid");
+
+ C_SaferCond ctx;
+ mock_image_meta.save(&ctx);
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd