tests/libcephfs: add tests for snap metadata mutations

Signed-off-by: Rishabh Dave <ridave@redhat.com>
This commit is contained in:
Rishabh Dave 2025-11-19 22:42:35 +05:30
parent 5726edbcf3
commit 446bd6af11
2 changed files with 492 additions and 0 deletions

View 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);
}

View 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);