From: Karthik U S Date: Tue, 12 May 2026 23:21:53 +0000 (+0530) Subject: tools/cephfs_mirror: Helper functions for mirroring checkpoints X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=0a394ef438abd0248d078ea72e8ae5ce4f3d5e84;p=ceph.git tools/cephfs_mirror: Helper functions for mirroring checkpoints 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 --- diff --git a/src/tools/cephfs_mirror/CMakeLists.txt b/src/tools/cephfs_mirror/CMakeLists.txt index 4b6dea7a160..bcba2a277e9 100644 --- a/src/tools/cephfs_mirror/CMakeLists.txt +++ b/src/tools/cephfs_mirror/CMakeLists.txt @@ -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 index 00000000000..f21b8389194 --- /dev/null +++ b/src/tools/cephfs_mirror/Checkpoint.cc @@ -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 +#include + +namespace cephfs { +namespace mirror { + +namespace { + +const std::vector CHECKPOINT_METADATA_KEY_LIST = { + CHECKPOINT_STATUS_KEY, + CHECKPOINT_CREATED_AT_KEY, + CHECKPOINT_UPDATED_AT_KEY, + CHECKPOINT_ERROR_MSG_KEY, +}; + +std::map decode_snap_metadata(snap_metadata *md, + size_t nr_snap_metadata) { + std::map 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 CheckpointInfo::to_metadata() const { + std::map metadata; + metadata[CHECKPOINT_STATUS_KEY] = std::to_string(static_cast(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 &metadata) { + CheckpointInfo info(snap_id, snap_name); + + auto it = metadata.find(CHECKPOINT_STATUS_KEY); + if (it != metadata.end()) { + info.status = static_cast(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 *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 &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 &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 index 00000000000..8b04a501291 --- /dev/null +++ b/src/tools/cephfs_mirror/Checkpoint.h @@ -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 +#include + +#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 to_metadata() const; + static CheckpointInfo from_metadata(uint64_t snap_id, const std::string &snap_name, + const std::map &metadata); +}; + + +// Helper functions for checkpoint metadata operations + +// Read snapshot metadata +int read_snap_metadata(MountRef mnt, const std::string &snap_path, + std::map *metadata); + +// Read checkpoint metadata from a snapshot +CheckpointInfo read_checkpoint_metadata(uint64_t snap_id, + const std::string &snap_name, + const std::map &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 &snap_metadata, + const CheckpointInfo &info); + +// Check if checkpoint key exists in snapshot metadata +inline bool has_checkpoint(const std::map &snap_metadata) { + return snap_metadata.find(CHECKPOINT_STATUS_KEY) != snap_metadata.end(); +} + +} // namespace mirror +} // namespace cephfs + +#endif // CEPHFS_MIRROR_CHECKPOINT_H diff --git a/src/tools/cephfs_mirror/PeerReplayer.cc b/src/tools/cephfs_mirror/PeerReplayer.cc index aafc6eba298..1e9c2cf491a 100644 --- a/src/tools/cephfs_mirror/PeerReplayer.cc +++ b/src/tools/cephfs_mirror/PeerReplayer.cc @@ -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; } diff --git a/src/tools/cephfs_mirror/Utils.cc b/src/tools/cephfs_mirror/Utils.cc index cad07fdb90f..e4692b333ff 100644 --- a/src/tools/cephfs_mirror/Utils.cc +++ b/src/tools/cephfs_mirror/Utils.cc @@ -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 diff --git a/src/tools/cephfs_mirror/Utils.h b/src/tools/cephfs_mirror/Utils.h index ab6c80ab5c6..28dc08fd0e9 100644 --- a/src/tools/cephfs_mirror/Utils.h +++ b/src/tools/cephfs_mirror/Utils.h @@ -4,11 +4,18 @@ #ifndef CEPHFS_MIRROR_UTILS_H #define CEPHFS_MIRROR_UTILS_H +#include + #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 args={});