]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
librbd: requests for mirror snapshot image state
authorMykola Golub <mgolub@suse.com>
Thu, 21 Nov 2019 14:25:46 +0000 (14:25 +0000)
committerMykola Golub <mgolub@suse.com>
Tue, 10 Dec 2019 15:45:30 +0000 (15:45 +0000)
Signed-off-by: Mykola Golub <mgolub@suse.com>
14 files changed:
src/cls/rbd/cls_rbd_types.h
src/librbd/CMakeLists.txt
src/librbd/mirror/snapshot/GetImageStateRequest.cc [new file with mode: 0644]
src/librbd/mirror/snapshot/GetImageStateRequest.h [new file with mode: 0644]
src/librbd/mirror/snapshot/RemoveImageStateRequest.cc [new file with mode: 0644]
src/librbd/mirror/snapshot/RemoveImageStateRequest.h [new file with mode: 0644]
src/librbd/mirror/snapshot/SetImageStateRequest.cc [new file with mode: 0644]
src/librbd/mirror/snapshot/SetImageStateRequest.h [new file with mode: 0644]
src/librbd/mirror/snapshot/Types.cc [new file with mode: 0644]
src/librbd/mirror/snapshot/Types.h [new file with mode: 0644]
src/librbd/mirror/snapshot/Utils.h [new file with mode: 0644]
src/librbd/mirror/snapshot/WriteImageStateRequest.cc [new file with mode: 0644]
src/librbd/mirror/snapshot/WriteImageStateRequest.h [new file with mode: 0644]
src/test/librbd/test_mirroring.cc

index 8f7ab37295ba611b5a1c61ae507f2a763074a066..eede25c9b916944611ac6ba6d036d1545e7a5744 100644 (file)
@@ -637,6 +637,9 @@ struct SnapshotNamespace : public SnapshotNamespaceVariant {
   inline bool operator<(const SnapshotNamespaceVariant& sn) const {
     return static_cast<const SnapshotNamespaceVariant&>(*this) < sn;
   }
+  inline bool operator!=(const SnapshotNamespaceVariant& sn) const {
+    return !(*this == sn);
+  }
 };
 WRITE_CLASS_ENCODER(SnapshotNamespace);
 
index 8de8dbe0b579b19d84b4fddd6c720c6b1bf73fde..f17765b99fb6b6a40288e7be3d43b673d643652e 100644 (file)
@@ -107,8 +107,13 @@ set(librbd_internal_srcs
   mirror/snapshot/CreateNonPrimaryRequest.cc
   mirror/snapshot/CreatePrimaryRequest.cc
   mirror/snapshot/DemoteRequest.cc
+  mirror/snapshot/GetImageStateRequest.cc
   mirror/snapshot/PromoteRequest.cc
+  mirror/snapshot/RemoveImageStateRequest.cc
+  mirror/snapshot/SetImageStateRequest.cc
+  mirror/snapshot/Types.cc
   mirror/snapshot/UnlinkPeerRequest.cc
+  mirror/snapshot/WriteImageStateRequest.cc
   object_map/CreateRequest.cc
   object_map/InvalidateRequest.cc
   object_map/LockRequest.cc
diff --git a/src/librbd/mirror/snapshot/GetImageStateRequest.cc b/src/librbd/mirror/snapshot/GetImageStateRequest.cc
new file mode 100644 (file)
index 0000000..e7db57b
--- /dev/null
@@ -0,0 +1,114 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/mirror/snapshot/GetImageStateRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/Utils.h"
+#include "librbd/mirror/snapshot/Types.h"
+#include "librbd/mirror/snapshot/Utils.h"
+
+#define dout_subsys ceph_subsys_rbd
+
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::mirror::snapshot::GetImageStateRequest: " \
+                           << this << " " << __func__ << ": "
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+using librbd::util::create_rados_callback;
+
+template <typename I>
+void GetImageStateRequest<I>::send() {
+  read_object();
+}
+
+
+template <typename I>
+void GetImageStateRequest<I>::read_object() {
+  CephContext *cct = m_image_ctx->cct;
+
+  auto oid = util::image_state_object_name(m_image_ctx, m_snap_id,
+                                           m_object_index);
+  ldout(cct, 20) << oid << dendl;
+
+  librados::ObjectReadOperation op;
+  m_bl.clear();
+  op.read(0, 0, &m_bl, nullptr);
+
+  librados::AioCompletion *comp = create_rados_callback<
+    GetImageStateRequest<I>,
+    &GetImageStateRequest<I>::handle_read_object>(this);
+  int r = m_image_ctx->md_ctx.aio_operate(oid, comp, &op, nullptr);
+  ceph_assert(r == 0);
+  comp->release();
+}
+
+template <typename I>
+void GetImageStateRequest<I>::handle_read_object(int r) {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(cct) << "failed to read image state object: " << cpp_strerror(r)
+               << dendl;
+    finish(r);
+    return;
+  }
+
+  auto iter = m_bl.cbegin();
+
+  if (m_object_index == 0) {
+    ImageStateHeader header;
+    try {
+      using ceph::decode;
+      decode(header, iter);
+    } catch (const buffer::error &err) {
+      lderr(cct) << "failed to decode image state object header" << dendl;
+      finish(-EBADMSG);
+      return;
+    }
+    m_object_count = header.object_count;
+  }
+
+  bufferlist bl;
+  bl.substr_of(m_bl, iter.get_off(), m_bl.length() - iter.get_off());
+  m_state_bl.claim_append(bl);
+
+  m_object_index++;
+
+  if (m_object_index >= m_object_count) {
+    finish(0);
+    return;
+  }
+
+  read_object();
+}
+
+template <typename I>
+void GetImageStateRequest<I>::finish(int r) {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << "r=" << r << dendl;
+
+  if (r == 0) {
+    try {
+      using ceph::decode;
+      decode(*m_image_state, m_state_bl);
+    } catch (const buffer::error &err) {
+      lderr(cct) << "failed to decode image state" << dendl;
+      r = -EBADMSG;
+    }
+  }
+
+  m_on_finish->complete(r);
+  delete this;
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+template class librbd::mirror::snapshot::GetImageStateRequest<librbd::ImageCtx>;
diff --git a/src/librbd/mirror/snapshot/GetImageStateRequest.h b/src/librbd/mirror/snapshot/GetImageStateRequest.h
new file mode 100644 (file)
index 0000000..483e3a2
--- /dev/null
@@ -0,0 +1,76 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_MIRROR_SNAPSHOT_GET_IMAGE_STATE_REQUEST_H
+#define CEPH_LIBRBD_MIRROR_SNAPSHOT_GET_IMAGE_STATE_REQUEST_H
+
+#include "include/buffer.h"
+#include "include/types.h"
+
+struct Context;
+
+namespace librbd {
+
+struct ImageCtx;
+
+namespace mirror {
+namespace snapshot {
+
+struct ImageState;
+
+template <typename ImageCtxT = librbd::ImageCtx>
+class GetImageStateRequest {
+public:
+  static GetImageStateRequest *create(ImageCtxT *image_ctx, uint64_t snap_id,
+                                      ImageState *image_state,
+                                      Context *on_finish) {
+    return new GetImageStateRequest(image_ctx, snap_id, image_state, on_finish);
+  }
+
+  GetImageStateRequest(ImageCtxT *image_ctx, uint64_t snap_id,
+                       ImageState *image_state, Context *on_finish)
+    : m_image_ctx(image_ctx), m_snap_id(snap_id), m_image_state(image_state),
+      m_on_finish(on_finish) {
+  }
+
+  void send();
+
+private:
+  /**
+   * @verbatim
+   *
+   * <start>
+   *    |
+   *    v
+   * READ_OBJECT (repeat for
+   *    |         every object)
+   *    v
+   * <finish>
+   *
+   * @endverbatim
+   */
+
+  ImageCtxT *m_image_ctx;
+  uint64_t m_snap_id;
+  ImageState *m_image_state;
+  Context *m_on_finish;
+
+  bufferlist m_bl;
+  bufferlist m_state_bl;
+
+  size_t m_object_count = 0;
+  size_t m_object_index = 0;
+
+  void read_object();
+  void handle_read_object(int r);
+
+  void finish(int r);
+};
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+extern template class librbd::mirror::snapshot::GetImageStateRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_MIRROR_SNAPSHOT_GET_IMAGE_STATE_REQUEST_H
diff --git a/src/librbd/mirror/snapshot/RemoveImageStateRequest.cc b/src/librbd/mirror/snapshot/RemoveImageStateRequest.cc
new file mode 100644 (file)
index 0000000..05920c2
--- /dev/null
@@ -0,0 +1,131 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/mirror/snapshot/RemoveImageStateRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/Utils.h"
+#include "librbd/mirror/snapshot/Types.h"
+#include "librbd/mirror/snapshot/Utils.h"
+
+#define dout_subsys ceph_subsys_rbd
+
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::mirror::snapshot::RemoveImageStateRequest: " \
+                           << this << " " << __func__ << ": "
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+using librbd::util::create_rados_callback;
+
+template <typename I>
+void RemoveImageStateRequest<I>::send() {
+  get_object_count();
+}
+
+
+template <typename I>
+void RemoveImageStateRequest<I>::get_object_count() {
+  CephContext *cct = m_image_ctx->cct;
+
+  auto oid = util::image_state_object_name(m_image_ctx, m_snap_id, 0);
+  ldout(cct, 20) << oid << dendl;
+
+  librados::ObjectReadOperation op;
+  op.read(0, 0, &m_bl, nullptr);
+
+  librados::AioCompletion *comp = create_rados_callback<
+    RemoveImageStateRequest<I>,
+    &RemoveImageStateRequest<I>::handle_get_object_count>(this);
+  int r = m_image_ctx->md_ctx.aio_operate(oid, comp, &op, nullptr);
+  ceph_assert(r == 0);
+  comp->release();
+}
+
+template <typename I>
+void RemoveImageStateRequest<I>::handle_get_object_count(int r) {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(cct) << "failed to read image state object: " << cpp_strerror(r)
+               << dendl;
+    finish(r);
+    return;
+  }
+
+  ImageStateHeader header(1);
+  auto iter = m_bl.cbegin();
+  try {
+    using ceph::decode;
+    
+    decode(header, iter);
+  } catch (const buffer::error &err) {
+    lderr(cct) << "failed to decode image state object header" << dendl;
+    // still try to remove it
+  }
+
+  m_object_count = header.object_count > 0 ? header.object_count : 1;
+
+  remove_object();
+}
+
+template <typename I>
+void RemoveImageStateRequest<I>::remove_object() {
+  CephContext *cct = m_image_ctx->cct;
+
+  ceph_assert(m_object_count > 0);
+  m_object_count--;
+
+  auto oid = util::image_state_object_name(m_image_ctx, m_snap_id,
+                                           m_object_count);
+  ldout(cct, 20) << oid << dendl;
+
+  librados::ObjectWriteOperation op;
+  op.remove();
+
+  librados::AioCompletion *comp = create_rados_callback<
+    RemoveImageStateRequest<I>,
+    &RemoveImageStateRequest<I>::handle_remove_object>(this);
+  int r = m_image_ctx->md_ctx.aio_operate(oid, comp, &op);
+  ceph_assert(r == 0);
+  comp->release();
+}
+
+template <typename I>
+void RemoveImageStateRequest<I>::handle_remove_object(int r) {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << "r=" << r << dendl;
+
+  if (r < 0 && r != -ENOENT) {
+    lderr(cct) << "failed to remove image state object: " << cpp_strerror(r)
+               << dendl;
+    finish(r);
+    return;
+  }
+
+  if (m_object_count == 0) {
+    finish(0);
+    return;
+  }
+
+  remove_object();
+}
+
+template <typename I>
+void RemoveImageStateRequest<I>::finish(int r) {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << "r=" << r << dendl;
+
+  m_on_finish->complete(r);
+  delete this;
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+template class librbd::mirror::snapshot::RemoveImageStateRequest<librbd::ImageCtx>;
diff --git a/src/librbd/mirror/snapshot/RemoveImageStateRequest.h b/src/librbd/mirror/snapshot/RemoveImageStateRequest.h
new file mode 100644 (file)
index 0000000..be7dad8
--- /dev/null
@@ -0,0 +1,75 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_MIRROR_SNAPSHOT_REMOVE_IMAGE_STATE_REQUEST_H
+#define CEPH_LIBRBD_MIRROR_SNAPSHOT_REMOVE_IMAGE_STATE_REQUEST_H
+
+#include "include/buffer.h"
+#include "include/types.h"
+
+struct Context;
+
+namespace librbd {
+
+struct ImageCtx;
+
+namespace mirror {
+namespace snapshot {
+
+template <typename ImageCtxT = librbd::ImageCtx>
+class RemoveImageStateRequest {
+public:
+  static RemoveImageStateRequest *create(ImageCtxT *image_ctx, uint64_t snap_id,
+                                         Context *on_finish) {
+      return new RemoveImageStateRequest(image_ctx, snap_id, on_finish);
+  }
+
+  RemoveImageStateRequest(ImageCtxT *image_ctx, uint64_t snap_id,
+                          Context *on_finish)
+    : m_image_ctx(image_ctx), m_snap_id(snap_id), m_on_finish(on_finish) {
+  }
+
+  void send();
+
+private:
+  /**
+   * @verbatim
+   *
+   * <start>
+   *    |
+   *    v
+   * GET_OBJECT_COUNT
+   *    |
+   *    v
+   * REMOVE_OBJECT (repeat for
+   *    |           every object)
+   *    v
+   * <finish>
+   *
+   * @endverbatim
+   */
+
+  ImageCtxT *m_image_ctx;
+  uint64_t m_snap_id;
+  Context *m_on_finish;
+
+  bufferlist m_bl;
+
+  size_t m_object_count = 0;
+
+  void get_object_count();
+  void handle_get_object_count(int r);
+
+  void remove_object();
+  void handle_remove_object(int r);
+
+  void finish(int r);
+};
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+extern template class librbd::mirror::snapshot::RemoveImageStateRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_MIRROR_SNAPSHOT_REMOVE_IMAGE_STATE_REQUEST_H
diff --git a/src/librbd/mirror/snapshot/SetImageStateRequest.cc b/src/librbd/mirror/snapshot/SetImageStateRequest.cc
new file mode 100644 (file)
index 0000000..d238664
--- /dev/null
@@ -0,0 +1,185 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/mirror/snapshot/SetImageStateRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/Utils.h"
+#include "librbd/mirror/snapshot/WriteImageStateRequest.h"
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#define dout_subsys ceph_subsys_rbd
+
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::mirror_snapshot::SetImageStateRequest: " \
+                           << this << " " << __func__ << ": "
+
+namespace {
+
+const uint64_t MAX_METADATA_ITEMS = 128;
+
+}
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+using librbd::util::create_context_callback;
+using librbd::util::create_rados_callback;
+
+template <typename I>
+void SetImageStateRequest<I>::send() {
+  get_snap_limit();
+}
+
+template <typename I>
+void SetImageStateRequest<I>::get_snap_limit() {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << dendl;
+
+  librados::ObjectReadOperation op;
+  cls_client::snapshot_get_limit_start(&op);
+
+  librados::AioCompletion *comp = create_rados_callback<
+    SetImageStateRequest<I>,
+    &SetImageStateRequest<I>::handle_get_snap_limit>(this);
+  m_bl.clear();
+  int r = m_image_ctx->md_ctx.aio_operate(m_image_ctx->header_oid, comp, &op,
+                                          &m_bl);
+  ceph_assert(r == 0);
+  comp->release();
+}
+
+template <typename I>
+void SetImageStateRequest<I>::handle_get_snap_limit(int r) {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << "r=" << r << dendl;
+
+  if (r == 0) {
+    auto it = m_bl.cbegin();
+    r = cls_client::snapshot_get_limit_finish(&it, &m_image_state.snap_limit);
+  }
+
+  if (r < 0) {
+    lderr(cct) << "failed to retrieve snapshot limit: " << cpp_strerror(r)
+               << dendl;
+    finish(r);
+    return;
+  }
+
+  get_metadata();
+}
+
+template <typename I>
+void SetImageStateRequest<I>::get_metadata() {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << "start_key=" << m_last_metadata_key << dendl;
+
+  librados::ObjectReadOperation op;
+  cls_client::metadata_list_start(&op, m_last_metadata_key, MAX_METADATA_ITEMS);
+
+  librados::AioCompletion *comp = create_rados_callback<
+    SetImageStateRequest<I>,
+    &SetImageStateRequest<I>::handle_get_metadata>(this);
+  m_bl.clear();
+  int r = m_image_ctx->md_ctx.aio_operate(m_image_ctx->header_oid, comp, &op,
+                                          &m_bl);
+  ceph_assert(r == 0);
+  comp->release();
+}
+
+template <typename I>
+void SetImageStateRequest<I>::handle_get_metadata(int r) {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << "r=" << r << dendl;
+
+  std::map<std::string, bufferlist> metadata;
+  if (r == 0) {
+    auto it = m_bl.cbegin();
+    r = cls_client::metadata_list_finish(&it, &metadata);
+  }
+
+  if (r < 0) {
+    lderr(cct) << "failed to retrieve metadata: " << cpp_strerror(r)
+               << dendl;
+    finish(r);
+    return;
+  }
+
+  if (!metadata.empty()) {
+    m_image_state.metadata.insert(metadata.begin(), metadata.end());
+    m_last_metadata_key = metadata.rbegin()->first;
+    if (boost::starts_with(m_last_metadata_key,
+                           ImageCtx::METADATA_CONF_PREFIX)) {
+      get_metadata();
+      return;
+    }
+  }
+
+  {
+    std::shared_lock image_locker{m_image_ctx->image_lock};
+
+    m_image_state.name = m_image_ctx->name;
+    m_image_state.features = m_image_ctx->features;
+
+    for (auto &[snap_id, snap_info] : m_image_ctx->snap_info) {
+      auto type = cls::rbd::get_snap_namespace_type(snap_info.snap_namespace);
+      if (type == cls::rbd::SNAPSHOT_NAMESPACE_TYPE_MIRROR_PRIMARY ||
+          type == cls::rbd::SNAPSHOT_NAMESPACE_TYPE_MIRROR_NON_PRIMARY) {
+        continue;
+      }
+      m_image_state.snapshots[snap_id] = {snap_id, snap_info.snap_namespace,
+                                          snap_info.name,
+                                          snap_info.protection_status};
+    }
+  }
+
+  write_image_state();
+}
+
+template <typename I>
+void SetImageStateRequest<I>::write_image_state() {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << dendl;
+
+  auto ctx = create_context_callback<
+    SetImageStateRequest<I>,
+    &SetImageStateRequest<I>::handle_write_image_state>(this);
+
+  auto req = WriteImageStateRequest<I>::create(m_image_ctx, m_snap_id,
+                                               m_image_state, ctx);
+  req->send();
+}
+
+template <typename I>
+void SetImageStateRequest<I>::handle_write_image_state(int r) {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(cct) << "failed to write image state: " << cpp_strerror(r)
+               << dendl;
+    finish(r);
+    return;
+  }
+
+  finish(0);
+}
+
+template <typename I>
+void SetImageStateRequest<I>::finish(int r) {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << "r=" << r << dendl;
+
+  m_on_finish->complete(r);
+  delete this;
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+template class librbd::mirror::snapshot::SetImageStateRequest<librbd::ImageCtx>;
diff --git a/src/librbd/mirror/snapshot/SetImageStateRequest.h b/src/librbd/mirror/snapshot/SetImageStateRequest.h
new file mode 100644 (file)
index 0000000..a16ba74
--- /dev/null
@@ -0,0 +1,85 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_MIRROR_SNAPSHOT_SET_IMAGE_STATE_REQUEST_H
+#define CEPH_LIBRBD_MIRROR_SNAPSHOT_SET_IMAGE_STATE_REQUEST_H
+
+#include "librbd/mirror/snapshot/Types.h"
+
+#include <map>
+#include <string>
+
+struct Context;
+
+namespace librbd {
+
+struct ImageCtx;
+
+namespace mirror {
+namespace snapshot {
+
+template <typename ImageCtxT = librbd::ImageCtx>
+class SetImageStateRequest {
+public:
+  static SetImageStateRequest *create(ImageCtxT *image_ctx, uint64_t snap_id,
+                                      Context *on_finish) {
+    return new SetImageStateRequest(image_ctx, snap_id, on_finish);
+  }
+
+  SetImageStateRequest(ImageCtxT *image_ctx, uint64_t snap_id,
+                       Context *on_finish)
+    : m_image_ctx(image_ctx), m_snap_id(snap_id), m_on_finish(on_finish) {
+  }
+
+  void send();
+
+private:
+  /**
+   * @verbatim
+   *
+   * <start>
+   *    |
+   *    v
+   * GET_SNAP_LIMIT
+   *    |
+   *    v
+   * GET_METADATA (repeat until
+   *    |          all metadata read)
+   *    v
+   * WRITE_IMAGE_STATE
+   *    |
+   *    v
+   * <finish>
+   *
+   * @endverbatim
+   */
+
+  ImageCtxT *m_image_ctx;
+  uint64_t m_snap_id;
+  Context *m_on_finish;
+
+  ImageState m_image_state;
+
+  bufferlist m_bl;
+  bufferlist m_state_bl;
+  std::string m_last_metadata_key;
+
+  void get_snap_limit();
+  void handle_get_snap_limit(int r);
+
+  void get_metadata();
+  void handle_get_metadata(int r);
+
+  void write_image_state();
+  void handle_write_image_state(int r);
+
+  void finish(int r);
+};
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+extern template class librbd::mirror::snapshot::SetImageStateRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_MIRROR_SNAPSHOT_SET_IMAGE_STATE_REQUEST_H
diff --git a/src/librbd/mirror/snapshot/Types.cc b/src/librbd/mirror/snapshot/Types.cc
new file mode 100644 (file)
index 0000000..00232cb
--- /dev/null
@@ -0,0 +1,104 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "common/Formatter.h"
+#include "include/encoding.h"
+#include "include/stringify.h"
+#include "librbd/mirror/snapshot/Types.h"
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+void ImageStateHeader::encode(bufferlist& bl) const {
+  ENCODE_START(1, 1, bl);
+  encode(object_count, bl);
+  ENCODE_FINISH(bl);
+}
+
+void ImageStateHeader::decode(bufferlist::const_iterator& bl) {
+  DECODE_START(1, bl);
+  decode(object_count, bl);
+  DECODE_FINISH(bl);
+}
+
+void SnapState::encode(bufferlist& bl) const {
+  ENCODE_START(1, 1, bl);
+  encode(id, bl);
+  encode(snap_namespace, bl);
+  encode(name, bl);
+  encode(protection_status, bl);
+  ENCODE_FINISH(bl);
+}
+
+void SnapState::decode(bufferlist::const_iterator& bl) {
+  DECODE_START(1, bl);
+  decode(id, bl);
+  decode(snap_namespace, bl);
+  decode(name, bl);
+  DECODE_FINISH(bl);
+}
+
+void SnapState::dump(Formatter *f) const {
+  f->dump_unsigned("id", id);
+  f->open_object_section("namespace");
+  snap_namespace.dump(f);
+  f->close_section();
+  f->dump_string("name", name);
+  f->dump_unsigned("protection_status", protection_status);
+}
+
+std::ostream& operator<<(std::ostream& os, const SnapState& snap_state) {
+  os << "[" << snap_state.id << " " << snap_state.snap_namespace << " "
+     << snap_state.name << " " << snap_state.protection_status << "]";
+  return os;
+}
+
+void ImageState::encode(bufferlist& bl) const {
+  ENCODE_START(1, 1, bl);
+  encode(name, bl);
+  encode(features, bl);
+  encode(snap_limit, bl);
+  encode(snapshots, bl);
+  encode(metadata, bl);
+  ENCODE_FINISH(bl);
+}
+
+void ImageState::decode(bufferlist::const_iterator& bl) {
+  DECODE_START(1, bl);
+  decode(name, bl);
+  decode(features, bl);
+  decode(snap_limit, bl);
+  decode(snapshots, bl);
+  decode(metadata, bl);
+  DECODE_FINISH(bl);
+}
+
+void ImageState::dump(Formatter *f) const {
+  f->dump_string("name", name);
+  f->dump_unsigned("features", features);
+  f->dump_unsigned("snap_limit", snap_limit);
+  f->open_array_section("snapshots");
+  for (auto &[id, snap_state] : snapshots) {
+    f->open_object_section(stringify(id).c_str());
+    snap_state.dump(f);
+    f->close_section(); // snap_state
+  }
+  f->close_section(); // snapshots
+  f->open_object_section("metadata");
+  for (auto &it : metadata) {
+    f->dump_stream(it.first.c_str()) << it.second;
+  }
+  f->close_section(); // metadata
+}
+
+std::ostream& operator<<(std::ostream& os, const ImageState& image_state) {
+  os << "[" << image_state.name << " " << image_state.features << " "
+     << image_state.snap_limit << " " << image_state.snapshots.size()
+     << " " << image_state.metadata.size() << "]";
+  return os;
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
diff --git a/src/librbd/mirror/snapshot/Types.h b/src/librbd/mirror/snapshot/Types.h
new file mode 100644 (file)
index 0000000..42f4aea
--- /dev/null
@@ -0,0 +1,120 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_MIRROR_SNAPSHOT_TYPES_H
+#define CEPH_LIBRBD_MIRROR_SNAPSHOT_TYPES_H
+
+#include "cls/rbd/cls_rbd_types.h"
+#include "include/buffer.h"
+#include "include/types.h"
+
+#include <map>
+#include <string>
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+struct ImageStateHeader {
+  uint32_t object_count = 0;
+
+  ImageStateHeader() {
+  }
+  ImageStateHeader(uint32_t object_count) : object_count(object_count) {
+  }
+
+  void encode(bufferlist &bl) const;
+  void decode(bufferlist::const_iterator &it);
+};
+
+WRITE_CLASS_ENCODER(ImageStateHeader);
+
+struct SnapState {
+  uint64_t id = CEPH_NOSNAP;
+  cls::rbd::SnapshotNamespace snap_namespace;
+  std::string name;
+  uint8_t protection_status = 0;
+
+  SnapState() {
+  }
+  SnapState(uint64_t id, const cls::rbd::SnapshotNamespace &snap_namespace,
+            const std::string &name, uint8_t protection_status)
+    : id(id), snap_namespace(snap_namespace), name(name),
+      protection_status(protection_status) {
+  }
+
+  bool operator==(const SnapState& rhs) const {
+    return id == rhs.id && snap_namespace == rhs.snap_namespace &&
+           name == rhs.name && protection_status == rhs.protection_status;
+  }
+
+  bool operator<(const SnapState& rhs) const {
+    if (id != rhs.id) {
+      return id < rhs.id;
+    }
+    if (snap_namespace != rhs.snap_namespace) {
+      return snap_namespace < rhs.snap_namespace;
+    }
+    if (name != rhs.name) {
+      return name < rhs.name;
+    }
+    return protection_status < rhs.protection_status;
+  }
+
+  void encode(bufferlist &bl) const;
+  void decode(bufferlist::const_iterator &it);
+  void dump(Formatter *f) const;
+};
+
+std::ostream& operator<<(std::ostream& os, const SnapState& snap_state);
+
+WRITE_CLASS_ENCODER(SnapState);
+
+struct ImageState {
+  std::string name;
+  uint64_t features = 0;
+  uint64_t snap_limit = 0;
+  std::map<uint64_t, SnapState> snapshots;
+  std::map<std::string, bufferlist> metadata;
+
+  ImageState() {
+  }
+  ImageState(const std::string &name, uint64_t features, uint64_t snap_limit,
+             const std::map<uint64_t, SnapState> &snapshots,
+             const std::map<std::string, bufferlist> &metadata)
+    : name(name), features(features), snap_limit(snap_limit),
+      snapshots(snapshots), metadata(metadata) {
+  }
+
+  bool operator==(const ImageState& rhs) const {
+    return name == rhs.name && features == rhs.features &&
+           snap_limit == rhs.snap_limit && snapshots == rhs.snapshots;
+  }
+
+  bool operator<(const ImageState& rhs) const {
+    if (name != rhs.name) {
+      return name < rhs.name;
+    }
+    if (features != rhs.features) {
+      return features < rhs.features;
+    }
+    if (snap_limit != rhs.snap_limit) {
+      return snap_limit < rhs.snap_limit;
+    }
+    return snapshots < rhs.snapshots;
+  }
+
+  void encode(bufferlist &bl) const;
+  void decode(bufferlist::const_iterator &it);
+  void dump(Formatter *f) const;
+};
+
+std::ostream& operator<<(std::ostream& os, const ImageState& image_state);
+
+WRITE_CLASS_ENCODER(ImageState);
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_MIRROR_SNAPSHOT_TYPES_H
diff --git a/src/librbd/mirror/snapshot/Utils.h b/src/librbd/mirror/snapshot/Utils.h
new file mode 100644 (file)
index 0000000..daf82f2
--- /dev/null
@@ -0,0 +1,28 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_MIRROR_SNAPSHOT_UTILS_H
+#define CEPH_LIBRBD_MIRROR_SNAPSHOT_UTILS_H
+
+#include "include/stringify.h"
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+namespace util {
+
+const std::string IMAGE_STATE_OBJECT_PREFIX = "rbd_mirror_snapshot.";
+
+template <typename ImageCtxT = librbd::ImageCtx>
+std::string image_state_object_name(ImageCtxT *image_ctx, uint64_t snap_id,
+                                    uint64_t index) {
+  return IMAGE_STATE_OBJECT_PREFIX + image_ctx->id + "." +
+    stringify(snap_id) + "." + stringify(index);
+}
+
+} // namespace util
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_MIRROR_SNAPSHOT_UTILS_H
diff --git a/src/librbd/mirror/snapshot/WriteImageStateRequest.cc b/src/librbd/mirror/snapshot/WriteImageStateRequest.cc
new file mode 100644 (file)
index 0000000..c179b9f
--- /dev/null
@@ -0,0 +1,120 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/mirror/snapshot/WriteImageStateRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/Utils.h"
+#include "librbd/mirror/snapshot/Utils.h"
+
+#define dout_subsys ceph_subsys_rbd
+
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::mirror::snapshot::WriteImageStateRequest: " \
+                           << this << " " << __func__ << ": "
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+namespace {
+
+static size_t header_length() {
+  bufferlist bl;
+  ImageStateHeader header;
+
+  using ceph::encode;
+  encode(header, bl);
+
+  return bl.length();
+}
+
+}
+using librbd::util::create_rados_callback;
+
+template <typename I>
+WriteImageStateRequest<I>::WriteImageStateRequest(I *image_ctx,
+                                                  uint64_t snap_id,
+                                                  const ImageState &image_state,
+                                                  Context *on_finish)
+  : m_image_ctx(image_ctx), m_snap_id(snap_id), m_image_state(image_state),
+    m_on_finish(on_finish), m_object_size(
+      1 << image_ctx->config.template get_val<uint64_t>("rbd_default_order")) {
+  bufferlist bl;
+  encode(m_image_state, bl);
+
+  m_object_count = 1 + (header_length() + bl.length()) / m_object_size;
+  ImageStateHeader header(m_object_count);
+
+  encode(header, m_bl);
+  m_bl.claim_append(bl);
+}
+
+template <typename I>
+void WriteImageStateRequest<I>::send() {
+  write_object();
+}
+
+template <typename I>
+void WriteImageStateRequest<I>::write_object() {
+  CephContext *cct = m_image_ctx->cct;
+  ceph_assert(m_object_count > 0);
+
+  m_object_count--;
+
+  auto oid = util::image_state_object_name(m_image_ctx, m_snap_id,
+                                           m_object_count);
+  ldout(cct, 20) << oid << dendl;
+
+  size_t off = m_object_count * m_object_size;
+  size_t len = std::min(m_bl.length() - off, m_object_size);
+  bufferlist bl;
+  bl.substr_of(m_bl, off, len);
+
+  librados::ObjectWriteOperation op;
+  op.write_full(bl);
+
+  librados::AioCompletion *comp = create_rados_callback<
+    WriteImageStateRequest<I>,
+    &WriteImageStateRequest<I>::handle_write_object>(this);
+  int r = m_image_ctx->md_ctx.aio_operate(oid, comp, &op);
+  ceph_assert(r == 0);
+  comp->release();
+}
+
+template <typename I>
+void WriteImageStateRequest<I>::handle_write_object(int r) {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(cct) << "failed to write object: " << cpp_strerror(r)
+               << dendl;
+    finish(r);
+    return;
+  }
+
+  if (m_object_count == 0) {
+    finish(0);
+    return;
+  }
+
+  write_object();
+}
+
+template <typename I>
+void WriteImageStateRequest<I>::finish(int r) {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << "r=" << r << dendl;
+
+  m_on_finish->complete(r);
+  delete this;
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+template class librbd::mirror::snapshot::WriteImageStateRequest<librbd::ImageCtx>;
diff --git a/src/librbd/mirror/snapshot/WriteImageStateRequest.h b/src/librbd/mirror/snapshot/WriteImageStateRequest.h
new file mode 100644 (file)
index 0000000..d2c4a7f
--- /dev/null
@@ -0,0 +1,73 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_MIRROR_SNAPSHOT_WRITE_IMAGE_STATE_REQUEST_H
+#define CEPH_LIBRBD_MIRROR_SNAPSHOT_WRITE_IMAGE_STATE_REQUEST_H
+
+#include "librbd/mirror/snapshot/Types.h"
+
+#include <map>
+#include <string>
+
+struct Context;
+
+namespace librbd {
+
+struct ImageCtx;
+
+namespace mirror {
+namespace snapshot {
+
+template <typename ImageCtxT = librbd::ImageCtx>
+class WriteImageStateRequest {
+public:
+  static WriteImageStateRequest *create(ImageCtxT *image_ctx, uint64_t snap_id,
+                                        const ImageState &image_state,
+                                        Context *on_finish) {
+    return new WriteImageStateRequest(image_ctx, snap_id, image_state,
+                                      on_finish);
+  }
+
+  WriteImageStateRequest(ImageCtxT *image_ctx, uint64_t snap_id,
+                         const ImageState &image_state, Context *on_finish);
+
+  void send();
+
+private:
+  /**
+   * @verbatim
+   *
+   * <start>
+   *    |
+   *    v
+   * WRITE_OBJECT (repeat for
+   *    |          every object)
+   *    v
+   * <finish>
+   *
+   * @endverbatim
+   */
+
+  ImageCtxT *m_image_ctx;
+  uint64_t m_snap_id;
+  ImageState m_image_state;
+  Context *m_on_finish;
+
+  bufferlist m_bl;
+
+  const size_t m_object_size;
+  size_t m_object_count = 0;
+
+  void write_object();
+  void handle_write_object(int r);
+
+  void finish(int r);
+};
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+extern template class librbd::mirror::snapshot::WriteImageStateRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_MIRROR_SNAPSHOT_WRITE_IMAGE_STATE_REQUEST_H
index 76518cda15ffabb72ad919fb92d404472c529f86..b085a11f138c8df3246010aa318d1de20bcdcd89 100644 (file)
@@ -24,6 +24,9 @@
 #include "librbd/io/ImageRequest.h"
 #include "librbd/io/ImageRequestWQ.h"
 #include "librbd/journal/Types.h"
+#include "librbd/mirror/snapshot/GetImageStateRequest.h"
+#include "librbd/mirror/snapshot/RemoveImageStateRequest.h"
+#include "librbd/mirror/snapshot/SetImageStateRequest.h"
 #include "librbd/mirror/snapshot/UnlinkPeerRequest.h"
 #include "journal/Journaler.h"
 #include "journal/Settings.h"
@@ -1289,3 +1292,107 @@ TEST_F(TestMirroring, SnapshotUnlinkPeer)
   ASSERT_EQ(0, m_rbd.mirror_peer_site_remove(m_ioctx, peer2_uuid));
   ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED));
 }
+
+TEST_F(TestMirroring, SnapshotImageState)
+{
+  REQUIRE_FORMAT_V2();
+
+  uint64_t features;
+  ASSERT_TRUE(get_features(&features));
+  features &= ~RBD_FEATURE_JOURNALING;
+  int order = 20;
+  ASSERT_EQ(0, m_rbd.create2(m_ioctx, image_name.c_str(), 4096, features,
+                             &order));
+
+  librbd::Image image;
+  ASSERT_EQ(0, m_rbd.open(m_ioctx, image, image_name.c_str()));
+  ASSERT_EQ(0, image.snap_create("snap"));
+
+  auto ictx = new librbd::ImageCtx(image_name, "", nullptr, m_ioctx, false);
+  ASSERT_EQ(0, ictx->state->open(0));
+  BOOST_SCOPE_EXIT(&ictx) {
+    if (ictx != nullptr) {
+      ictx->state->close();
+    }
+  } BOOST_SCOPE_EXIT_END;
+
+  {
+    C_SaferCond cond;
+    auto req = librbd::mirror::snapshot::SetImageStateRequest<>::create(
+      ictx, 123, &cond);
+    req->send();
+    ASSERT_EQ(0, cond.wait());
+  }
+
+  librbd::mirror::snapshot::ImageState image_state;
+  {
+    C_SaferCond cond;
+    auto req = librbd::mirror::snapshot::GetImageStateRequest<>::create(
+      ictx, 123, &image_state, &cond);
+    req->send();
+    ASSERT_EQ(0, cond.wait());
+  }
+
+  ASSERT_EQ(image_name, image_state.name);
+  ASSERT_EQ(0, image.features(&features));
+  ASSERT_EQ(features, image_state.features);
+  ASSERT_EQ(1U, image_state.snapshots.size());
+  ASSERT_EQ("snap", image_state.snapshots.begin()->second.name);
+  ASSERT_TRUE(image_state.metadata.empty());
+
+  {
+    C_SaferCond cond;
+    auto req = librbd::mirror::snapshot::RemoveImageStateRequest<>::create(
+      ictx, 123, &cond);
+    req->send();
+    ASSERT_EQ(0, cond.wait());
+  }
+
+  // test storing "large" image state in multiple objects
+
+  ASSERT_EQ(0, ictx->config.set_val("rbd_default_order", "8"));
+
+  for (int i = 0; i < 10; i++) {
+    ASSERT_EQ(0, image.metadata_set(stringify(i), std::string(1024, 'A' + i)));
+  }
+
+  {
+    C_SaferCond cond;
+    auto req = librbd::mirror::snapshot::SetImageStateRequest<>::create(
+      ictx, 123, &cond);
+    req->send();
+    ASSERT_EQ(0, cond.wait());
+  }
+
+  {
+    C_SaferCond cond;
+    auto req = librbd::mirror::snapshot::GetImageStateRequest<>::create(
+      ictx, 123, &image_state, &cond);
+    req->send();
+    ASSERT_EQ(0, cond.wait());
+  }
+
+  ASSERT_EQ(image_name, image_state.name);
+  ASSERT_EQ(features, image_state.features);
+  ASSERT_EQ(10U, image_state.metadata.size());
+  for (int i = 0; i < 10; i++) {
+    auto &bl = image_state.metadata[stringify(i)];
+    ASSERT_EQ(0, strncmp(std::string(1024, 'A' + i).c_str(), bl.c_str(),
+                         bl.length()));
+  }
+
+  {
+    C_SaferCond cond;
+    auto req = librbd::mirror::snapshot::RemoveImageStateRequest<>::create(
+      ictx, 123, &cond);
+    req->send();
+    ASSERT_EQ(0, cond.wait());
+  }
+
+  ASSERT_EQ(0, ictx->state->close());
+  ictx = nullptr;
+
+  ASSERT_EQ(0, image.snap_remove("snap"));
+  ASSERT_EQ(0, image.close());
+  ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str()));
+}