]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
tests/libcephfs: add tests for snap metadata mutations
authorRishabh Dave <ridave@redhat.com>
Wed, 19 Nov 2025 17:12:35 +0000 (22:42 +0530)
committerRishabh Dave <ridave@redhat.com>
Thu, 18 Jun 2026 06:27:45 +0000 (11:57 +0530)
Signed-off-by: Rishabh Dave <ridave@redhat.com>
src/test/libcephfs/multiclient.cc
src/test/libcephfs/test.cc

index a7155853f572a6313b00872ee47ddd8961619bcb..371b82b7bb6e70477e7f36055c805a4e459046ed 100644 (file)
@@ -16,6 +16,7 @@
 #include "gtest/gtest.h"
 #include "include/compat.h"
 #include "include/cephfs/libcephfs.h"
+#include "include/ceph_fs.h"
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -179,3 +180,68 @@ TEST(LibCephFS, MulticlientRevokeCaps) {
   thread1.join();
   thread2.join();
 }
+
+
+// Test that client #2 can successfully read snap metadata mutation made by
+// client #1.
+TEST(LibCephFS, SnapMdMutate) {
+  struct ceph_mount_info *cmount, *cmount2;
+
+  ASSERT_EQ(ceph_create(&cmount, NULL), 0);
+  ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0);
+  ASSERT_EQ(ceph_conf_parse_env(cmount, NULL), 0);
+  ASSERT_EQ(ceph_mount(cmount, NULL), 0);
+
+  ASSERT_EQ(ceph_create(&cmount2, NULL), 0);
+  ASSERT_EQ(ceph_conf_read_file(cmount2, NULL), 0);
+  ASSERT_EQ(ceph_conf_parse_env(cmount2, NULL), 0);
+  ASSERT_EQ(ceph_mount(cmount2, NULL), 0);
+
+  char dir_path[64];
+  char snap_name[64];
+  char snap_path[PATH_MAX];
+  sprintf(dir_path, "/dir0_%d-5", getpid());
+  sprintf(snap_name, "snap_%d_5", getpid());
+  sprintf(snap_path, "%s/.snap/%s", dir_path, snap_name);
+
+  ASSERT_EQ(0, ceph_mkdir(cmount, dir_path, 0755));
+  // snapshot with custom metadata
+  struct snap_metadata snap_meta[] = {{"foo", "bar"},
+                                      {"this", "that"},
+                                      {"abcde", "12345"}};
+  ASSERT_EQ(0, ceph_mksnap(cmount, dir_path, snap_name, 0755, snap_meta,
+                           std::size(snap_meta)));
+
+  // actual test -
+  ASSERT_EQ(0, ceph_do_snap_md_op(cmount, snap_path, "foo", "bar123",
+                                  CEPH_SNAP_MD_OP_CREATE));
+
+  struct snap_info info;
+  ASSERT_EQ(0, ceph_get_snap_info(cmount2, snap_path, &info));
+  ASSERT_GT(info.id, 1);
+  ASSERT_EQ(info.nr_snap_metadata, 3);
+
+  // verify snap metadata
+  struct snap_metadata snap_meta2[] = {{"foo", "bar123"}, {"this", "that"},
+                                       {"abcde", "12345"}};
+  for (size_t i = 0; i < info.nr_snap_metadata; ++i) {
+    auto k = std::string(info.snap_metadata[i].key);
+    auto v = std::string(info.snap_metadata[i].value);
+
+    bool found = false;
+    for (size_t j = 0;  j < std::size(snap_meta2); ++j) {
+      if (k == snap_meta2[j].key and v == snap_meta2[j].value) {
+        found = true;
+        break;
+      }
+    }
+
+    ASSERT_EQ(found, true);
+  }
+
+  // teardown
+  ASSERT_EQ(0, ceph_rmsnap(cmount, dir_path, snap_name));
+  ASSERT_EQ(0, ceph_rmdir(cmount, dir_path));
+  ceph_shutdown(cmount);
+  ceph_shutdown(cmount2);
+}
index 7d3aed44a2b76f84fd7194893b1daf253d8cc1bf..2347cc456703c6be121e1ed6dddc79f71fabb039 100644 (file)
@@ -17,6 +17,7 @@
 #include "include/filepath.h"
 #include "gtest/gtest.h"
 #include "include/cephfs/libcephfs.h"
+#include "include/ceph_fs.h"
 #include "mds/mdstypes.h"
 #include "include/stat.h"
 #include <errno.h>
@@ -2799,6 +2800,431 @@ TEST(LibCephFS, SnapInfo) {
   ceph_shutdown(cmount);
 }
 
+// Test that ceph_do_snap_md_op() in create mode successfully adds new k-v
+// pairs to snapshot metadata.
+TEST(LibCephFS, SnapMdCreateAdds) {
+  struct ceph_mount_info *cmount;
+  ASSERT_EQ(ceph_create(&cmount, NULL), 0);
+  ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0);
+  ASSERT_EQ(ceph_conf_parse_env(cmount, NULL), 0);
+  ASSERT_EQ(do_ceph_mount(cmount, NULL), 0);
+
+  char dir_path[64];
+  char snap_name[64];
+  char snap_path[PATH_MAX];
+  sprintf(dir_path, "/dir0_%d-2", getpid());
+  sprintf(snap_name, "snap_%d_2", getpid());
+  sprintf(snap_path, "%s/.snap/%s", dir_path, snap_name);
+
+  ASSERT_EQ(0, ceph_mkdir(cmount, dir_path, 0755));
+  // snapshot with custom metadata
+  struct snap_metadata snap_meta[] = {{"foo", "bar"},
+                                      {"this", "that"},
+                                      {"abcde", "12345"}};
+  ASSERT_EQ(0, ceph_mksnap(cmount, dir_path, snap_name, 0755, snap_meta,
+                           std::size(snap_meta)));
+
+  // actual test.
+  ASSERT_EQ(0, ceph_do_snap_md_op(cmount, snap_path, "foo2", "bar2",
+                                  CEPH_SNAP_MD_OP_CREATE));
+
+  // get latest snap metadata
+  struct snap_info info;
+  ASSERT_EQ(0, ceph_get_snap_info(cmount, snap_path, &info));
+  ASSERT_GT(info.id, 1);
+  ASSERT_EQ(info.nr_snap_metadata, 4);
+
+  // verify snap metadata
+  struct snap_metadata snap_meta2[] = {{"foo", "bar"}, {"foo2", "bar2"},
+                                       {"this", "that"}, {"abcde", "12345"}};
+  for (size_t i = 0; i < info.nr_snap_metadata; ++i) {
+    auto k = std::string(info.snap_metadata[i].key);
+    auto v = std::string(info.snap_metadata[i].value);
+
+    bool found = false;
+    for (size_t j = 0;  j < std::size(snap_meta2); ++j) {
+      if (k == snap_meta2[j].key and v == snap_meta2[j].value) {
+        found = true;
+        break;
+      }
+    }
+
+    ASSERT_EQ(found, true);
+  }
+
+  // teardown
+  ceph_free_snap_info_buffer(&info);
+  ASSERT_EQ(0, ceph_rmsnap(cmount, dir_path, snap_name));
+  ASSERT_EQ(0, ceph_rmdir(cmount, dir_path));
+  ceph_shutdown(cmount);
+}
+
+
+// Test that ceph_do_snap_md_op() in create mode successfully updates new k-v
+// pairs to snapshot metadata.
+TEST(LibCephFS, SnapMdCreateUpdates) {
+  struct ceph_mount_info *cmount;
+  ASSERT_EQ(ceph_create(&cmount, NULL), 0);
+  ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0);
+  ASSERT_EQ(ceph_conf_parse_env(cmount, NULL), 0);
+  ASSERT_EQ(do_ceph_mount(cmount, NULL), 0);
+
+  char dir_path[64];
+  char snap_name[64];
+  char snap_path[PATH_MAX];
+  sprintf(dir_path, "/dir0_%d-2", getpid());
+  sprintf(snap_name, "snap_%d_2", getpid());
+  sprintf(snap_path, "%s/.snap/%s", dir_path, snap_name);
+
+  ASSERT_EQ(0, ceph_mkdir(cmount, dir_path, 0755));
+  // snapshot with custom metadata
+  struct snap_metadata snap_meta[] = {{"foo", "bar"},
+                                      {"this", "that"},
+                                      {"abcde", "12345"}};
+  ASSERT_EQ(0, ceph_mksnap(cmount, dir_path, snap_name, 0755, snap_meta,
+                           std::size(snap_meta)));
+
+  // actual test.
+  ASSERT_EQ(0, ceph_do_snap_md_op(cmount, snap_path, "foo", "bar2",
+                                  CEPH_SNAP_MD_OP_CREATE));
+
+  // get latest snap metadata
+  struct snap_info info;
+  ASSERT_EQ(0, ceph_get_snap_info(cmount, snap_path, &info));
+  ASSERT_GT(info.id, 1);
+  ASSERT_EQ(info.nr_snap_metadata, 3);
+
+  // verify snap metadata
+  struct snap_metadata snap_meta2[] = {{"foo", "bar2"}, {"this", "that"},
+                                       {"abcde", "12345"}};
+  for (size_t i = 0; i < info.nr_snap_metadata; ++i) {
+    auto k = std::string(info.snap_metadata[i].key);
+    auto v = std::string(info.snap_metadata[i].value);
+
+    bool found = false;
+    for (size_t j = 0;  j < std::size(snap_meta2); ++j) {
+      if (k == snap_meta2[j].key and v == snap_meta2[j].value) {
+        found = true;
+        break;
+      }
+    }
+
+    ASSERT_EQ(found, true);
+  }
+
+  // teardown
+  ceph_free_snap_info_buffer(&info);
+  ASSERT_EQ(0, ceph_rmsnap(cmount, dir_path, snap_name));
+  ASSERT_EQ(0, ceph_rmdir(cmount, dir_path));
+  ceph_shutdown(cmount);
+}
+
+
+// Test that k-v pair is added successfully by ceph_do_snap_md_op() in
+// EXCL mode.
+TEST(LibCephFS, SnapMdExclAdds) {
+  struct ceph_mount_info *cmount;
+  ASSERT_EQ(ceph_create(&cmount, NULL), 0);
+  ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0);
+  ASSERT_EQ(ceph_conf_parse_env(cmount, NULL), 0);
+  ASSERT_EQ(do_ceph_mount(cmount, NULL), 0);
+
+  char dir_path[64];
+  char snap_name[64];
+  char snap_path[PATH_MAX];
+  sprintf(dir_path, "/dir0_%d-1", getpid());
+  sprintf(snap_name, "snap_%d_1", getpid());
+  sprintf(snap_path, "%s/.snap/%s", dir_path, snap_name);
+
+  ASSERT_EQ(0, ceph_mkdir(cmount, dir_path, 0755));
+  // snapshot with custom metadata
+  struct snap_metadata snap_meta[] = {{"foo", "bar"}, {"this", "that"},
+                                      {"abcde", "12345"}};
+  ASSERT_EQ(0, ceph_mksnap(cmount, dir_path, snap_name, 0755, snap_meta,
+                           std::size(snap_meta)));
+
+  // actual test.
+  ASSERT_EQ(0,
+            ceph_do_snap_md_op(cmount, snap_path, "foo2", "bar2",
+                               CEPH_SNAP_MD_OP_CREATE | CEPH_SNAP_MD_OP_EXCL));
+
+  // get latest snap metadata
+  struct snap_info info;
+  ASSERT_EQ(0, ceph_get_snap_info(cmount, snap_path, &info));
+  ASSERT_GT(info.id, 1);
+  ASSERT_EQ(info.nr_snap_metadata, 4);
+
+  // verify snap metadata
+  struct snap_metadata snap_meta2[] = {{"foo", "bar"}, {"foo2", "bar2"},
+                                       {"this", "that"}, {"abcde", "12345"}};
+  for (size_t i = 0; i < info.nr_snap_metadata; ++i) {
+    auto k = std::string(info.snap_metadata[i].key);
+    auto v = std::string(info.snap_metadata[i].value);
+
+    bool found = false;
+    for (size_t j = 0;  j < std::size(snap_meta2); ++j) {
+      if (k == snap_meta2[j].key and v == snap_meta2[j].value) {
+        found = true;
+        break;
+      }
+    }
+
+    ASSERT_EQ(found, true);
+  }
+
+  // teardown
+  ceph_free_snap_info_buffer(&info);
+  ASSERT_EQ(0, ceph_rmsnap(cmount, dir_path, snap_name));
+  ASSERT_EQ(0, ceph_rmdir(cmount, dir_path));
+  ceph_shutdown(cmount);
+}
+
+
+// Test that k-v pair update is rejected successfully by ceph_do_snap_md_op() in
+// EXCL mode.
+TEST(LibCephFS, SnapMdExclRejectsUpdate) {
+  struct ceph_mount_info *cmount;
+  ASSERT_EQ(ceph_create(&cmount, NULL), 0);
+  ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0);
+  ASSERT_EQ(ceph_conf_parse_env(cmount, NULL), 0);
+  ASSERT_EQ(do_ceph_mount(cmount, NULL), 0);
+
+  char dir_path[64];
+  char snap_name[64];
+  char snap_path[PATH_MAX];
+  sprintf(dir_path, "/dir0_%d-1", getpid());
+  sprintf(snap_name, "snap_%d_1", getpid());
+  sprintf(snap_path, "%s/.snap/%s", dir_path, snap_name);
+
+  ASSERT_EQ(0, ceph_mkdir(cmount, dir_path, 0755));
+  // snapshot with custom metadata
+  struct snap_metadata snap_meta[] = {{"foo", "bar"}, {"this", "that"},
+                                      {"abcde", "12345"}};
+  ASSERT_EQ(0, ceph_mksnap(cmount, dir_path, snap_name, 0755, snap_meta,
+                           std::size(snap_meta)));
+
+  // actual test.
+  ASSERT_EQ(-EINVAL,
+            ceph_do_snap_md_op(cmount, snap_path, "foo", "bar2",
+                               CEPH_SNAP_MD_OP_CREATE | CEPH_SNAP_MD_OP_EXCL));
+
+  // get latest snap metadata
+  struct snap_info info;
+  ASSERT_EQ(0, ceph_get_snap_info(cmount, snap_path, &info));
+  ASSERT_GT(info.id, 1);
+  ASSERT_EQ(info.nr_snap_metadata, 3);
+
+  // verify snap metadata
+  for (size_t i = 0; i < info.nr_snap_metadata; ++i) {
+    auto k = std::string(info.snap_metadata[i].key);
+    auto v = std::string(info.snap_metadata[i].value);
+
+    bool found = false;
+    for (size_t j = 0;  j < std::size(snap_meta); ++j) {
+      if (k == snap_meta[j].key and v == snap_meta[j].value) {
+        found = true;
+        break;
+      }
+    }
+
+    ASSERT_EQ(found, true);
+  }
+
+  // teardown
+  ceph_free_snap_info_buffer(&info);
+  ASSERT_EQ(0, ceph_rmsnap(cmount, dir_path, snap_name));
+  ASSERT_EQ(0, ceph_rmdir(cmount, dir_path));
+  ceph_shutdown(cmount);
+}
+
+
+// Test that ceph_do_snap_md_op() in remove mode successfully removes k-v
+// pairs from snapshot metadata.
+TEST(LibCephFS, SnapMdRemove) {
+  struct ceph_mount_info *cmount;
+  ASSERT_EQ(ceph_create(&cmount, NULL), 0);
+  ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0);
+  ASSERT_EQ(ceph_conf_parse_env(cmount, NULL), 0);
+  ASSERT_EQ(do_ceph_mount(cmount, NULL), 0);
+
+  char dir_path[64];
+  char snap_name[64];
+  char snap_path[PATH_MAX];
+  sprintf(dir_path, "/dir0_%d-3", getpid());
+  sprintf(snap_name, "snap_%d_3", getpid());
+  sprintf(snap_path, "%s/.snap/%s", dir_path, snap_name);
+
+  ASSERT_EQ(0, ceph_mkdir(cmount, dir_path, 0755));
+  // snapshot with custom metadata
+  struct snap_metadata snap_meta[] = {{"foo", "bar"},
+                                      {"this", "that"},
+                                      {"abcde", "12345"}};
+  ASSERT_EQ(0, ceph_mksnap(cmount, dir_path, snap_name, 0755, snap_meta,
+                           std::size(snap_meta)));
+
+  // actual test.
+  ASSERT_EQ(0, ceph_do_snap_md_op(cmount, snap_path, "foo", "bar",
+                                  CEPH_SNAP_MD_OP_REMOVE));
+
+  // get latest snap metadata
+  struct snap_info info;
+  ASSERT_EQ(0, ceph_get_snap_info(cmount, snap_path, &info));
+  ASSERT_GT(info.id, 1);
+  ASSERT_EQ(info.nr_snap_metadata, 2);
+
+  // verify snap metadata
+  for (size_t i = 0; i < info.nr_snap_metadata; ++i) {
+    auto k = std::string(info.snap_metadata[i].key);
+    auto v = std::string(info.snap_metadata[i].value);
+
+    assert(k != "foo");
+    assert(v != "bar");
+
+    bool found = false;
+    for (size_t j = 1; j < std::size(snap_meta); ++j) {
+      if (k == snap_meta[j].key and v == snap_meta[j].value) {
+        found = true;
+        break;
+      }
+    }
+
+    ASSERT_EQ(found, true);
+  }
+
+  // teardown
+  ASSERT_EQ(0, ceph_rmsnap(cmount, dir_path, snap_name));
+  ASSERT_EQ(0, ceph_rmdir(cmount, dir_path));
+  ceph_shutdown(cmount);
+}
+
+// Test all snap MD operations with empty strings.
+TEST(LibCephFS, SnapMdOpEmptyStrings) {
+  struct ceph_mount_info *cmount;
+  ASSERT_EQ(ceph_create(&cmount, NULL), 0);
+  ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0);
+  ASSERT_EQ(ceph_conf_parse_env(cmount, NULL), 0);
+  ASSERT_EQ(do_ceph_mount(cmount, NULL), 0);
+
+  char dir_path[64];
+  char snap_name[64];
+  char snap_path[PATH_MAX];
+  sprintf(dir_path, "/dir0_%d-4", getpid());
+  sprintf(snap_name, "snap_%d_4", getpid());
+  sprintf(snap_path, "%s/.snap/%s", dir_path, snap_name);
+
+  ASSERT_EQ(0, ceph_mkdir(cmount, dir_path, 0755));
+  // snapshot with custom metadata
+  struct snap_metadata snap_meta[] = {{"foo", "bar"}, {"this", "that"},
+                                      {"abcde", "12345"}};
+  ASSERT_EQ(0, ceph_mksnap(cmount, dir_path, snap_name, 0755, snap_meta,
+                           std::size(snap_meta)));
+
+  // actual testing begins...
+  ASSERT_EQ(0, ceph_do_snap_md_op(cmount, snap_path, "", "",
+                                  CEPH_SNAP_MD_OP_CREATE));
+  ASSERT_EQ(0, ceph_do_snap_md_op(cmount, snap_path, "", "1",
+                                  CEPH_SNAP_MD_OP_CREATE));
+
+  ASSERT_EQ(0,
+            ceph_do_snap_md_op(cmount, snap_path, "1", "",
+                               CEPH_SNAP_MD_OP_CREATE | CEPH_SNAP_MD_OP_EXCL));
+
+  ASSERT_EQ(0, ceph_do_snap_md_op(cmount, snap_path, "", "",
+                                  CEPH_SNAP_MD_OP_REMOVE));
+  ASSERT_EQ(0, ceph_do_snap_md_op(cmount, snap_path, "1", "",
+                                  CEPH_SNAP_MD_OP_REMOVE));
+
+  // get latest snap metadata
+  struct snap_info info;
+  ASSERT_EQ(0, ceph_get_snap_info(cmount, snap_path, &info));
+  ASSERT_GT(info.id, 1);
+  ASSERT_EQ(info.nr_snap_metadata, 3);
+
+  // verify snap metadata
+  for (size_t i = 0; i < info.nr_snap_metadata; ++i) {
+    auto k = std::string(info.snap_metadata[i].key);
+    auto v = std::string(info.snap_metadata[i].value);
+
+    bool found = false;
+    for (size_t j = 0; j < std::size(snap_meta); ++j) {
+      if (k == snap_meta[j].key and v == snap_meta[j].value) {
+        found = true;
+        break;
+      }
+    }
+
+    ASSERT_EQ(found, true);
+  }
+
+  // teardown
+  ASSERT_EQ(0, ceph_rmsnap(cmount, dir_path, snap_name));
+  ASSERT_EQ(0, ceph_rmdir(cmount, dir_path));
+  ceph_shutdown(cmount);
+}
+
+// Test that ceph_do_snap_md_op() disallows inapproriate ops depending on
+// mode.
+TEST(LibCephFS, SnapMdNegTest) {
+  struct ceph_mount_info *cmount;
+  ASSERT_EQ(ceph_create(&cmount, NULL), 0);
+  ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0);
+  ASSERT_EQ(ceph_conf_parse_env(cmount, NULL), 0);
+  ASSERT_EQ(do_ceph_mount(cmount, NULL), 0);
+
+  char dir_path[64];
+  char snap_name[64];
+  char snap_path[PATH_MAX];
+  sprintf(dir_path, "/dir0_%d-5", getpid());
+  sprintf(snap_name, "snap_%d_5", getpid());
+  sprintf(snap_path, "%s/.snap/%s", dir_path, snap_name);
+
+  ASSERT_EQ(0, ceph_mkdir(cmount, dir_path, 0755));
+  // snapshot with custom metadata
+  struct snap_metadata snap_meta[] = {{"foo", "bar"},
+                                      {"this", "that"},
+                                      {"abcde", "12345"}};
+  ASSERT_EQ(0, ceph_mksnap(cmount, dir_path, snap_name, 0755, snap_meta,
+                           std::size(snap_meta)));
+
+  // actual test -
+  // attempt create in EXCL mode.
+  ASSERT_EQ(-EINVAL,
+            ceph_do_snap_md_op(cmount, snap_path, "foo", "bar2",
+                               CEPH_SNAP_MD_OP_CREATE | CEPH_SNAP_MD_OP_EXCL));
+  // attempt remove for an unexisting key.
+  ASSERT_EQ(-EINVAL, ceph_do_snap_md_op(cmount, snap_path, "foo2", "bar2",
+                                        CEPH_SNAP_MD_OP_REMOVE));
+  // run ceph_do_snap_md_op() in invalid mode/with invalid op_flag.
+  ASSERT_EQ(-EINVAL, ceph_do_snap_md_op(cmount, snap_path, "foo123", "bar123",
+                                        5));
+
+  struct snap_info info;
+  ASSERT_EQ(0, ceph_get_snap_info(cmount, snap_path, &info));
+  ASSERT_GT(info.id, 1);
+  ASSERT_EQ(info.nr_snap_metadata, 3);
+
+  // verify snap metadata
+  for (size_t i = 0; i < info.nr_snap_metadata; ++i) {
+    auto k = std::string(info.snap_metadata[i].key);
+    auto v = std::string(info.snap_metadata[i].value);
+
+    bool found = false;
+    for (size_t j = 0; j < std::size(snap_meta); ++j) {
+      if (k == snap_meta[j].key and v == snap_meta[j].value) {
+        found = true;
+        break;
+      }
+    }
+
+    ASSERT_EQ(found, true);
+  }
+
+  // teardown
+  ASSERT_EQ(0, ceph_rmsnap(cmount, dir_path, snap_name));
+  ASSERT_EQ(0, ceph_rmdir(cmount, dir_path));
+  ceph_shutdown(cmount);
+}
+
 TEST(LibCephFS, LookupInoMDSDir) {
   struct ceph_mount_info *cmount;
   ASSERT_EQ(ceph_create(&cmount, NULL), 0);