diff options
Diffstat (limited to '')
-rw-r--r-- | src/test/libcephfs/deleg.cc | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/src/test/libcephfs/deleg.cc b/src/test/libcephfs/deleg.cc new file mode 100644 index 00000000..1a6376ee --- /dev/null +++ b/src/test/libcephfs/deleg.cc @@ -0,0 +1,385 @@ +// -*- 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/cephfs/libcephfs.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 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 != -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 != -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(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(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(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(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(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(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(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), -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(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); +} |