diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:54:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:54:28 +0000 |
commit | e6918187568dbd01842d8d1d2c808ce16a894239 (patch) | |
tree | 64f88b554b444a49f656b6c656111a145cbbaa28 /src/test/libcephfs/deleg.cc | |
parent | Initial commit. (diff) | |
download | ceph-upstream/18.2.2.tar.xz ceph-upstream/18.2.2.zip |
Adding upstream version 18.2.2.upstream/18.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/test/libcephfs/deleg.cc')
-rw-r--r-- | src/test/libcephfs/deleg.cc | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/src/test/libcephfs/deleg.cc b/src/test/libcephfs/deleg.cc new file mode 100644 index 000000000..061e13763 --- /dev/null +++ b/src/test/libcephfs/deleg.cc @@ -0,0 +1,401 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Tests for Ceph delegation handling + * + * (c) 2017, Jeff Layton <jlayton@redhat.com> + */ + +#include "gtest/gtest.h" +#include "include/compat.h" +#include "include/cephfs/libcephfs.h" +#include "include/fs_types.h" +#include "include/stat.h" +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <sys/uio.h> + +#ifdef __linux__ +#include <limits.h> +#include <sys/xattr.h> +#endif + +#include <map> +#include <vector> +#include <thread> +#include <atomic> + +#include "include/ceph_assert.h" + +/* in ms -- 1 minute */ +#define MAX_WAIT (60 * 1000) + +static void wait_for_atomic_bool(std::atomic_bool &recalled) +{ + int i = 0; + + while (!recalled.load()) { + ASSERT_LT(i++, MAX_WAIT); + usleep(1000); + } +} + +static int ceph_ll_delegation_wait(struct ceph_mount_info *cmount, Fh *fh, + unsigned cmd, ceph_deleg_cb_t cb, void *priv) +{ + int ret, retry = 0; + + /* Wait 10s at most */ + do { + ret = ceph_ll_delegation(cmount, fh, cmd, cb, priv); + usleep(10000); + } while (ret == -CEPHFS_EAGAIN && retry++ < 1000); + + return ret; +} + +static int set_default_deleg_timeout(struct ceph_mount_info *cmount) +{ + uint32_t session_timeout = ceph_get_cap_return_timeout(cmount); + return ceph_set_deleg_timeout(cmount, session_timeout - 1); +} + +static void dummy_deleg_cb(Fh *fh, void *priv) +{ + std::atomic_bool *recalled = (std::atomic_bool *)priv; + recalled->store(true); +} + +static void open_breaker_func(struct ceph_mount_info *cmount, const char *filename, int flags, std::atomic_bool *opened) +{ + bool do_shutdown = false; + + if (!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(ceph_mount(cmount, "/"), 0); + ASSERT_EQ(set_default_deleg_timeout(cmount), 0); + do_shutdown = true; + } + + Inode *root, *file; + ASSERT_EQ(ceph_ll_lookup_root(cmount, &root), 0); + + Fh *fh; + struct ceph_statx stx; + UserPerm *perms = ceph_mount_perms(cmount); + + ASSERT_EQ(ceph_ll_lookup(cmount, root, filename, &file, &stx, CEPH_STATX_ALL_STATS, 0, perms), 0); + int ret, i = 0; + for (;;) { + ASSERT_EQ(ceph_ll_getattr(cmount, file, &stx, CEPH_STATX_ALL_STATS, 0, perms), 0); + ret = ceph_ll_open(cmount, file, flags, &fh, perms); + if (ret != -CEPHFS_EAGAIN) + break; + ASSERT_LT(i++, MAX_WAIT); + usleep(1000); + } + ASSERT_EQ(ret, 0); + opened->store(true); + ASSERT_EQ(ceph_ll_close(cmount, fh), 0); + + if (do_shutdown) + ceph_shutdown(cmount); +} + +enum { + DelegTestLink, + DelegTestRename, + DelegTestUnlink +}; + +static void namespace_breaker_func(struct ceph_mount_info *cmount, int cmd, const char *oldname, const char *newname) +{ + bool do_shutdown = false; + + if (!cmount) { + ASSERT_EQ(ceph_create(&cmount, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(ceph_mount(cmount, "/"), 0); + ASSERT_EQ(set_default_deleg_timeout(cmount), 0); + do_shutdown = true; + } + + Inode *root, *file = nullptr; + ASSERT_EQ(ceph_ll_lookup_root(cmount, &root), 0); + + struct ceph_statx stx; + UserPerm *perms = ceph_mount_perms(cmount); + + int ret, i = 0; + for (;;) { + switch (cmd) { + case DelegTestRename: + ret = ceph_ll_rename(cmount, root, oldname, root, newname, perms); + break; + case DelegTestLink: + if (!file) { + ASSERT_EQ(ceph_ll_lookup(cmount, root, oldname, &file, &stx, 0, 0, perms), 0); + } + ret = ceph_ll_link(cmount, file, root, newname, perms); + break; + case DelegTestUnlink: + ret = ceph_ll_unlink(cmount, root, oldname, perms); + break; + default: + // Bad command + ceph_abort(); + } + if (ret != -CEPHFS_EAGAIN) + break; + ASSERT_LT(i++, MAX_WAIT); + usleep(1000); + } + ASSERT_EQ(ret, 0); + + if (do_shutdown) + ceph_shutdown(cmount); +} + +static void simple_deleg_test(struct ceph_mount_info *cmount, struct ceph_mount_info *tcmount) +{ + Inode *root, *file; + + ASSERT_EQ(ceph_ll_lookup_root(cmount, &root), 0); + + char filename[32]; + + Fh *fh; + struct ceph_statx stx; + UserPerm *perms = ceph_mount_perms(cmount); + + std::atomic_bool recalled(false); + std::atomic_bool opened(false); + + // ensure r/w open breaks a r/w delegation + sprintf(filename, "deleg.rwrw.%x", getpid()); + ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, + O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); + ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_WR, dummy_deleg_cb, &recalled), 0); + std::thread breaker1(open_breaker_func, tcmount, filename, O_RDWR, &opened); + + wait_for_atomic_bool(recalled); + ASSERT_EQ(opened.load(), false); + ASSERT_EQ(ceph_ll_delegation(cmount, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, &recalled), 0); + breaker1.join(); + ASSERT_EQ(ceph_ll_close(cmount, fh), 0); + ASSERT_EQ(ceph_ll_unlink(cmount, root, filename, perms), 0); + + // ensure r/o open breaks a r/w delegation + recalled.store(false); + opened.store(false); + sprintf(filename, "deleg.rorw.%x", getpid()); + ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, + O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); + ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_WR, dummy_deleg_cb, &recalled), 0); + std::thread breaker2(open_breaker_func, tcmount, filename, O_RDONLY, &opened); + wait_for_atomic_bool(recalled); + ASSERT_EQ(opened.load(), false); + ASSERT_EQ(ceph_ll_delegation(cmount, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, &recalled), 0); + breaker2.join(); + ASSERT_EQ(ceph_ll_close(cmount, fh), 0); + ASSERT_EQ(ceph_ll_unlink(cmount, root, filename, perms), 0); + + // ensure r/o open does not break a r/o delegation + sprintf(filename, "deleg.rwro.%x", getpid()); + ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, + O_RDONLY|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); + recalled.store(false); + ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_RD, dummy_deleg_cb, &recalled), 0); + std::thread breaker3(open_breaker_func, tcmount, filename, O_RDONLY, &opened); + breaker3.join(); + ASSERT_EQ(recalled.load(), false); + + // ensure that r/w open breaks r/o delegation + opened.store(false); + std::thread breaker4(open_breaker_func, tcmount, filename, O_WRONLY, &opened); + wait_for_atomic_bool(recalled); + usleep(1000); + ASSERT_EQ(opened.load(), false); + ASSERT_EQ(ceph_ll_delegation(cmount, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, &recalled), 0); + breaker4.join(); + ASSERT_EQ(ceph_ll_close(cmount, fh), 0); + ASSERT_EQ(ceph_ll_unlink(cmount, root, filename, perms), 0); + + // ensure hardlinking breaks a r/w delegation + recalled.store(false); + char newname[32]; + sprintf(filename, "deleg.old.%x", getpid()); + sprintf(newname, "deleg.new.%x", getpid()); + ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, + O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); + ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_WR, dummy_deleg_cb, &recalled), 0); + std::thread breaker5(namespace_breaker_func, tcmount, DelegTestLink, filename, newname); + wait_for_atomic_bool(recalled); + ASSERT_EQ(ceph_ll_delegation(cmount, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, &recalled), 0); + breaker5.join(); + ASSERT_EQ(ceph_ll_close(cmount, fh), 0); + ASSERT_EQ(ceph_ll_unlink(cmount, root, filename, perms), 0); + ASSERT_EQ(ceph_ll_unlink(cmount, root, newname, perms), 0); + + // ensure renaming breaks a r/w delegation + recalled.store(false); + ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, + O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); + ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_WR, dummy_deleg_cb, &recalled), 0); + std::thread breaker6(namespace_breaker_func, tcmount, DelegTestRename, filename, newname); + wait_for_atomic_bool(recalled); + ASSERT_EQ(ceph_ll_delegation(cmount, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, &recalled), 0); + breaker6.join(); + ASSERT_EQ(ceph_ll_close(cmount, fh), 0); + ASSERT_EQ(ceph_ll_unlink(cmount, root, newname, perms), 0); + + // ensure unlinking breaks a r/w delegation + recalled.store(false); + ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, + O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); + ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_WR, dummy_deleg_cb, &recalled), 0); + std::thread breaker7(namespace_breaker_func, tcmount, DelegTestUnlink, filename, nullptr); + wait_for_atomic_bool(recalled); + ASSERT_EQ(ceph_ll_delegation(cmount, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, &recalled), 0); + breaker7.join(); + ASSERT_EQ(ceph_ll_close(cmount, fh), 0); +} + +TEST(LibCephFS, DelegMultiClient) { + struct ceph_mount_info *cmount; + + ASSERT_EQ(ceph_create(&cmount, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(ceph_mount(cmount, "/"), 0); + ASSERT_EQ(set_default_deleg_timeout(cmount), 0); + + simple_deleg_test(cmount, nullptr); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, DelegSingleClient) { + struct ceph_mount_info *cmount; + + ASSERT_EQ(ceph_create(&cmount, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(ceph_mount(cmount, "/"), 0); + ASSERT_EQ(set_default_deleg_timeout(cmount), 0); + + simple_deleg_test(cmount, cmount); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, DelegTimeout) { + struct ceph_mount_info *cmount; + ASSERT_EQ(ceph_create(&cmount, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(ceph_mount(cmount, "/"), 0); + // tweak timeout to run quickly, since we don't plan to return it anyway + ASSERT_EQ(ceph_set_deleg_timeout(cmount, 2), 0); + + Inode *root, *file; + ASSERT_EQ(ceph_ll_lookup_root(cmount, &root), 0); + + char filename[32]; + sprintf(filename, "delegtimeo%x", getpid()); + + Fh *fh; + struct ceph_statx stx; + UserPerm *perms = ceph_mount_perms(cmount); + + ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, + O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); + + /* Reopen read-only */ + ASSERT_EQ(ceph_ll_close(cmount, fh), 0); + ASSERT_EQ(ceph_ll_open(cmount, file, O_RDONLY, &fh, perms), 0); + + std::atomic_bool recalled(false); + ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_RD, dummy_deleg_cb, &recalled), 0); + std::atomic_bool opened(false); + std::thread breaker1(open_breaker_func, nullptr, filename, O_RDWR, &opened); + breaker1.join(); + ASSERT_EQ(recalled.load(), true); + ASSERT_EQ(ceph_ll_getattr(cmount, root, &stx, 0, 0, perms), -CEPHFS_ENOTCONN); + ceph_release(cmount); +} + +TEST(LibCephFS, RecalledGetattr) { + struct ceph_mount_info *cmount1; + ASSERT_EQ(ceph_create(&cmount1, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(cmount1, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(cmount1, NULL)); + ASSERT_EQ(ceph_mount(cmount1, "/"), 0); + ASSERT_EQ(set_default_deleg_timeout(cmount1), 0); + + Inode *root, *file; + ASSERT_EQ(ceph_ll_lookup_root(cmount1, &root), 0); + + char filename[32]; + sprintf(filename, "recalledgetattr%x", getpid()); + + Fh *fh; + struct ceph_statx stx; + UserPerm *perms = ceph_mount_perms(cmount1); + + ASSERT_EQ(ceph_ll_create(cmount1, root, filename, 0666, + O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); + ASSERT_EQ(ceph_ll_write(cmount1, fh, 0, sizeof(filename), filename), + static_cast<int>(sizeof(filename))); + ASSERT_EQ(ceph_ll_close(cmount1, fh), 0); + + /* New mount for read delegation */ + struct ceph_mount_info *cmount2; + ASSERT_EQ(ceph_create(&cmount2, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(cmount2, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(cmount2, NULL)); + ASSERT_EQ(ceph_mount(cmount2, "/"), 0); + ASSERT_EQ(set_default_deleg_timeout(cmount2), 0); + + ASSERT_EQ(ceph_ll_lookup_root(cmount2, &root), 0); + perms = ceph_mount_perms(cmount2); + ASSERT_EQ(ceph_ll_lookup(cmount2, root, filename, &file, &stx, 0, 0, perms), 0); + + ASSERT_EQ(ceph_ll_open(cmount2, file, O_WRONLY, &fh, perms), 0); + ASSERT_EQ(ceph_ll_write(cmount2, fh, 0, sizeof(filename), filename), + static_cast<int>(sizeof(filename))); + ASSERT_EQ(ceph_ll_close(cmount2, fh), 0); + + ASSERT_EQ(ceph_ll_open(cmount2, file, O_RDONLY, &fh, perms), 0); + + /* Break delegation */ + std::atomic_bool recalled(false); + ASSERT_EQ(ceph_ll_delegation_wait(cmount2, fh, CEPH_DELEGATION_RD, dummy_deleg_cb, &recalled), 0); + ASSERT_EQ(ceph_ll_read(cmount2, fh, 0, sizeof(filename), filename), + static_cast<int>(sizeof(filename))); + ASSERT_EQ(ceph_ll_getattr(cmount2, file, &stx, CEPH_STATX_ALL_STATS, 0, perms), 0); + std::atomic_bool opened(false); + std::thread breaker1(open_breaker_func, cmount1, filename, O_WRONLY, &opened); + int i = 0; + do { + ASSERT_EQ(ceph_ll_getattr(cmount2, file, &stx, CEPH_STATX_ALL_STATS, 0, perms), 0); + ASSERT_LT(i++, MAX_WAIT); + usleep(1000); + } while (!recalled.load()); + ASSERT_EQ(opened.load(), false); + ASSERT_EQ(ceph_ll_getattr(cmount2, file, &stx, CEPH_STATX_ALL_STATS, 0, perms), 0); + ASSERT_EQ(ceph_ll_delegation(cmount2, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, nullptr), 0); + breaker1.join(); + ASSERT_EQ(ceph_ll_close(cmount2, fh), 0); + ceph_unmount(cmount2); + ceph_release(cmount2); + ceph_unmount(cmount1); + ceph_release(cmount1); +} |