]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
tools/cephfs_mirror: Helper functions for mirroring checkpoints
authorKarthik U S <karthik.u.s1@ibm.com>
Tue, 12 May 2026 23:21:53 +0000 (04:51 +0530)
committerKarthik U S <karthik.u.s1@ibm.com>
Thu, 2 Jul 2026 09:19:03 +0000 (14:49 +0530)
Implementation of helper functions and data structures for the
snapshot based mirroring checkpoints feature.

Fixes: https://tracker.ceph.com/issues/73454
Signed-off-by: Karthik U S <karthik.u.s1@ibm.com>
src/tools/cephfs_mirror/CMakeLists.txt
src/tools/cephfs_mirror/Checkpoint.cc [new file with mode: 0644]
src/tools/cephfs_mirror/Checkpoint.h [new file with mode: 0644]
src/tools/cephfs_mirror/PeerReplayer.cc
src/tools/cephfs_mirror/Utils.cc
src/tools/cephfs_mirror/Utils.h

index 4b6dea7a160672304aa5c34aa05bdb6da00cfcc2..bcba2a277e94bcb7c5125841b23e832f7ab26179 100644 (file)
@@ -1,4 +1,5 @@
 set(cephfs_mirror_internal
+  Checkpoint.cc
   ClusterWatcher.cc
   Mirror.cc
   FSMirror.cc
diff --git a/src/tools/cephfs_mirror/Checkpoint.cc b/src/tools/cephfs_mirror/Checkpoint.cc
new file mode 100644 (file)
index 0000000..f21b838
--- /dev/null
@@ -0,0 +1,170 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*-
+// vim: ts=8 sw=2 sts=2 expandtab
+
+#include "Checkpoint.h"
+#include "Utils.h"
+
+#include "common/strtol.h"
+
+#include <cstdio>
+#include <vector>
+
+namespace cephfs {
+namespace mirror {
+
+namespace {
+
+const std::vector<std::string> CHECKPOINT_METADATA_KEY_LIST = {
+  CHECKPOINT_STATUS_KEY,
+  CHECKPOINT_CREATED_AT_KEY,
+  CHECKPOINT_UPDATED_AT_KEY,
+  CHECKPOINT_ERROR_MSG_KEY,
+};
+
+std::map<std::string, std::string> decode_snap_metadata(snap_metadata *md,
+                                                        size_t nr_snap_metadata) {
+  std::map<std::string, std::string> metadata;
+  for (size_t i = 0; i < nr_snap_metadata; ++i) {
+    metadata.emplace(md[i].key, md[i].value);
+  }
+  return metadata;
+}
+
+} // anonymous namespace
+
+std::string utime_to_epoch_string(const utime_t &t) {
+  double epoch = (double)t.sec() + (double)t.nsec() / 1000000000.0;
+  char buf[32];
+  snprintf(buf, sizeof(buf), "%.9f", epoch);
+  return std::string(buf);
+}
+
+bool utime_from_epoch_string(const std::string &s, utime_t *t) {
+  std::string err;
+  double epoch = strict_strtod(s, &err);
+  if (!err.empty()) {
+    return false;
+  }
+  t->set_from_double(epoch);
+  return true;
+}
+
+CheckpointInfo::CheckpointInfo()
+  : snap_id(0),
+    status(CheckpointStatus::CREATED) {
+}
+
+CheckpointInfo::CheckpointInfo(uint64_t snap_id_, const std::string &snap_name_)
+  : snap_id(snap_id_),
+    snap_name(snap_name_),
+    status(CheckpointStatus::CREATED) {
+}
+
+std::map<std::string, std::string> CheckpointInfo::to_metadata() const {
+  std::map<std::string, std::string> metadata;
+  metadata[CHECKPOINT_STATUS_KEY] = std::to_string(static_cast<uint8_t>(status));
+  metadata[CHECKPOINT_CREATED_AT_KEY] = utime_to_epoch_string(created_at);
+  metadata[CHECKPOINT_UPDATED_AT_KEY] = utime_to_epoch_string(updated_at);
+  if (!error_msg.empty()) {
+    metadata[CHECKPOINT_ERROR_MSG_KEY] = error_msg;
+  }
+  return metadata;
+}
+
+CheckpointInfo CheckpointInfo::from_metadata(uint64_t snap_id, const std::string &snap_name,
+                                              const std::map<std::string, std::string> &metadata) {
+  CheckpointInfo info(snap_id, snap_name);
+
+  auto it = metadata.find(CHECKPOINT_STATUS_KEY);
+  if (it != metadata.end()) {
+    info.status = static_cast<CheckpointStatus>(std::stoul(it->second));
+  }
+
+  it = metadata.find(CHECKPOINT_CREATED_AT_KEY);
+  if (it != metadata.end()) {
+    utime_from_epoch_string(it->second, &info.created_at);
+  }
+
+  it = metadata.find(CHECKPOINT_UPDATED_AT_KEY);
+  if (it != metadata.end()) {
+    utime_from_epoch_string(it->second, &info.updated_at);
+  }
+
+  it = metadata.find(CHECKPOINT_ERROR_MSG_KEY);
+  if (it != metadata.end()) {
+    info.error_msg = it->second;
+  }
+
+  return info;
+}
+
+int read_snap_metadata(MountRef mnt, const std::string &snap_path,
+                       std::map<std::string, std::string> *metadata) {
+  snap_info info;
+  int r = ceph_get_snap_info(mnt, snap_path.c_str(), &info);
+  if (r < 0) {
+    return r;
+  }
+
+  metadata->clear();
+  if (info.nr_snap_metadata) {
+    *metadata = decode_snap_metadata(info.snap_metadata, info.nr_snap_metadata);
+    ceph_free_snap_info_buffer(&info);
+  }
+  return 0;
+}
+
+CheckpointInfo read_checkpoint_metadata(uint64_t snap_id,
+                                        const std::string &snap_name,
+                                        const std::map<std::string, std::string> &snap_metadata) {
+  return CheckpointInfo::from_metadata(snap_id, snap_name, snap_metadata);
+}
+
+int write_checkpoint_metadata(CephContext *cct, MountRef mnt,
+                               const std::string &dir_root,
+                               const std::string &snap_name,
+                               const std::map<std::string, std::string> &snap_metadata,
+                               const CheckpointInfo &info) {
+  auto snap_path = snapshot_path(cct, dir_root, snap_name);
+  auto checkpoint_metadata = info.to_metadata();
+
+  // Write/update checkpoint metadata keys
+  for (const auto &[key, val] : checkpoint_metadata) {
+    // created_at is set by the mgr when the checkpoint is created; mirror
+    // daemon updates must not rewrite it.
+    if (key == CHECKPOINT_CREATED_AT_KEY && snap_metadata.count(key)) {
+      continue;
+    }
+
+    auto it = snap_metadata.find(key);
+    if (it != snap_metadata.end() && it->second == val) {
+      continue;
+    }
+
+    // For updates: use CREATE (allows both create and update)
+    // For new creates: use CREATE | EXCL (create only, reject update)
+    unsigned int op_flag = it != snap_metadata.end() ?
+      CEPH_SNAP_MD_OP_CREATE : (CEPH_SNAP_MD_OP_CREATE | CEPH_SNAP_MD_OP_EXCL);
+    int r = ceph_do_snap_md_op(mnt, snap_path.c_str(), key.c_str(), val.c_str(),
+                               op_flag);
+    if (r < 0) {
+      return r;
+    }
+  }
+
+  // Remove checkpoint metadata keys that are no longer present
+  for (const auto &key : CHECKPOINT_METADATA_KEY_LIST) {
+    if (snap_metadata.count(key) && !checkpoint_metadata.count(key)) {
+      int r = ceph_do_snap_md_op(mnt, snap_path.c_str(), key.c_str(), "",
+                                 CEPH_SNAP_MD_OP_REMOVE);
+      if (r < 0) {
+        return r;
+      }
+    }
+  }
+
+  return 0;
+}
+
+} // namespace mirror
+} // namespace cephfs
diff --git a/src/tools/cephfs_mirror/Checkpoint.h b/src/tools/cephfs_mirror/Checkpoint.h
new file mode 100644 (file)
index 0000000..8b04a50
--- /dev/null
@@ -0,0 +1,93 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*-
+// vim: ts=8 sw=2 sts=2 expandtab
+
+#ifndef CEPHFS_MIRROR_CHECKPOINT_H
+#define CEPHFS_MIRROR_CHECKPOINT_H
+
+#include <string>
+#include <map>
+
+#include "include/utime.h"
+#include "Types.h"
+
+namespace cephfs {
+namespace mirror {
+
+// Checkpoint status enumeration
+enum class CheckpointStatus : uint8_t {
+  CREATED = 0,   // Checkpoint created but not yet synced
+  COMPLETE = 1,  // Checkpoint successfully synced to remote
+  FAILED = 2     // Checkpoint sync failed
+};
+
+// Convert checkpoint status to string
+inline std::string checkpoint_status_to_string(CheckpointStatus status) {
+  switch (status) {
+    case CheckpointStatus::CREATED:
+      return "created";
+    case CheckpointStatus::COMPLETE:
+      return "complete";
+    case CheckpointStatus::FAILED:
+      return "failed";
+    default:
+      return "unknown";
+  }
+}
+
+// Checkpoint metadata keys stored in snapshot metadata
+inline static const std::string CHECKPOINT_STATUS_KEY = "cephfs.mirror.checkpoint.status";
+inline static const std::string CHECKPOINT_CREATED_AT_KEY = "cephfs.mirror.checkpoint.created_at";
+inline static const std::string CHECKPOINT_UPDATED_AT_KEY = "cephfs.mirror.checkpoint.updated_at";
+inline static const std::string CHECKPOINT_ERROR_MSG_KEY = "cephfs.mirror.checkpoint.error_msg";
+
+// Snapshot metadata stores created_at/updated_at as UNIX epoch strings with
+// 9 decimal places (Python get_checkpoint_epoch() or utime_to_epoch_string()).
+std::string utime_to_epoch_string(const utime_t &t);
+bool utime_from_epoch_string(const std::string &s, utime_t *t);
+
+// Checkpoint information structure
+struct CheckpointInfo {
+  uint64_t snap_id;              // Snapshot ID (unique identifier)
+  std::string snap_name;         // Snapshot name
+  CheckpointStatus status;       // Current status of the checkpoint
+  utime_t created_at;            // When the checkpoint was created (epoch in metadata)
+  utime_t updated_at;            // Last status update time (epoch in metadata)
+  std::string error_msg;         // Error message if status is FAILED
+
+  CheckpointInfo();
+  CheckpointInfo(uint64_t snap_id_, const std::string &snap_name_);
+
+  // Convert to/from snapshot metadata map
+  std::map<std::string, std::string> to_metadata() const;
+  static CheckpointInfo from_metadata(uint64_t snap_id, const std::string &snap_name,
+                                       const std::map<std::string, std::string> &metadata);
+};
+
+
+// Helper functions for checkpoint metadata operations
+
+// Read snapshot metadata
+int read_snap_metadata(MountRef mnt, const std::string &snap_path,
+                       std::map<std::string, std::string> *metadata);
+
+// Read checkpoint metadata from a snapshot
+CheckpointInfo read_checkpoint_metadata(uint64_t snap_id,
+                                        const std::string &snap_name,
+                                        const std::map<std::string, std::string> &snap_metadata);
+
+// Write checkpoint metadata to a snapshot
+int write_checkpoint_metadata(CephContext *cct, MountRef mnt,
+                              const std::string &dir_root,
+                              const std::string &snap_name,
+                              const std::map<std::string, std::string> &snap_metadata,
+                              const CheckpointInfo &info);
+
+// Check if checkpoint key exists in snapshot metadata
+inline bool has_checkpoint(const std::map<std::string, std::string> &snap_metadata) {
+  return snap_metadata.find(CHECKPOINT_STATUS_KEY) != snap_metadata.end();
+}
+
+} // namespace mirror
+} // namespace cephfs
+
+#endif // CEPHFS_MIRROR_CHECKPOINT_H
index aafc6eba29866e7f606f55be1e3a558e7cfdd59b..1e9c2cf491a339a7d5a8074ea3e3b33dcdf59fb4 100644 (file)
@@ -92,18 +92,6 @@ const std::string PEER_CONFIG_KEY_PREFIX = "cephfs/mirror/peer";
 // Matches the mgr mirroring omap read batch size (dir_map/load.py MAX_RETURN).
 constexpr size_t SYNC_STAT_OMAP_LOAD_BATCH_SIZE = 256;
 
-std::string snapshot_dir_path(CephContext *cct, const std::string &path) {
-  return path + "/" + cct->_conf->client_snapdir;
-}
-
-std::string snapshot_path(const std::string &snap_dir, const std::string &snap_name) {
-  return snap_dir + "/" + snap_name;
-}
-
-std::string snapshot_path(CephContext *cct, const std::string &path, const std::string &snap_name) {
-  return path + "/" + cct->_conf->client_snapdir + "/" + snap_name;
-}
-
 std::string entry_path(const std::string &dir, const std::string &name) {
   return dir + "/" + name;
 }
index cad07fdb90ff497c18fc5fa1d14a10f972e6f352..e4692b333ff2cc539cd38bfec3b8060e307ac597 100644 (file)
@@ -162,5 +162,18 @@ int mount(RadosRef cluster, const Filesystem &filesystem, bool cross_check_fscid
   return 0;
 }
 
+std::string snapshot_dir_path(CephContext *cct, const std::string &dir_root) {
+  return dir_root + "/" + cct->_conf->client_snapdir;
+}
+
+std::string snapshot_path(const std::string &snap_dir, const std::string &snap_name) {
+  return snap_dir + "/" + snap_name;
+}
+
+std::string snapshot_path(CephContext *cct, const std::string &dir_root,
+                          const std::string &snap_name) {
+  return snapshot_dir_path(cct, dir_root) + "/" + snap_name;
+}
+
 } // namespace mirror
 } // namespace cephfs
index ab6c80ab5c6e30f7c84cfe9e7566b4e4bd4518e0..28dc08fd0e94f17a43065d4fdc08cb19909d0aa5 100644 (file)
@@ -4,11 +4,18 @@
 #ifndef CEPHFS_MIRROR_UTILS_H
 #define CEPHFS_MIRROR_UTILS_H
 
+#include <string>
+
 #include "Types.h"
 
 namespace cephfs {
 namespace mirror {
 
+std::string snapshot_dir_path(CephContext *cct, const std::string &dir_root);
+std::string snapshot_path(const std::string &snap_dir, const std::string &snap_name);
+std::string snapshot_path(CephContext *cct, const std::string &dir_root,
+                          const std::string &snap_name);
+
 int connect(std::string_view client_name, std::string_view cluster_name,
             RadosRef *cluster, std::string_view mon_host={}, std::string_view cephx_key={},
             std::vector<const char *> args={});