summaryrefslogtreecommitdiffstats
path: root/qa/tasks/cephfs/test_admin.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--qa/tasks/cephfs/test_admin.py1494
1 files changed, 1494 insertions, 0 deletions
diff --git a/qa/tasks/cephfs/test_admin.py b/qa/tasks/cephfs/test_admin.py
new file mode 100644
index 000000000..9890381c6
--- /dev/null
+++ b/qa/tasks/cephfs/test_admin.py
@@ -0,0 +1,1494 @@
+import errno
+import json
+import logging
+import time
+import uuid
+from io import StringIO
+from os.path import join as os_path_join
+
+from teuthology.exceptions import CommandFailedError
+
+from tasks.cephfs.cephfs_test_case import CephFSTestCase, classhook
+from tasks.cephfs.filesystem import FileLayout, FSMissing
+from tasks.cephfs.fuse_mount import FuseMount
+from tasks.cephfs.caps_helper import CapTester
+
+log = logging.getLogger(__name__)
+
+class TestAdminCommands(CephFSTestCase):
+ """
+ Tests for administration command.
+ """
+
+ CLIENTS_REQUIRED = 1
+ MDSS_REQUIRED = 1
+
+ def check_pool_application_metadata_key_value(self, pool, app, key, value):
+ output = self.fs.mon_manager.raw_cluster_cmd(
+ 'osd', 'pool', 'application', 'get', pool, app, key)
+ self.assertEqual(str(output.strip()), value)
+
+ def setup_ec_pools(self, n, metadata=True, overwrites=True):
+ if metadata:
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', n+"-meta", "8")
+ cmd = ['osd', 'erasure-code-profile', 'set', n+"-profile", "m=2", "k=2", "crush-failure-domain=osd"]
+ self.fs.mon_manager.raw_cluster_cmd(*cmd)
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', n+"-data", "8", "erasure", n+"-profile")
+ if overwrites:
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'set', n+"-data", 'allow_ec_overwrites', 'true')
+
+@classhook('_add_valid_tell')
+class TestValidTell(TestAdminCommands):
+ @classmethod
+ def _add_valid_tell(cls):
+ tells = [
+ ['cache', 'status'],
+ ['damage', 'ls'],
+ ['dump_blocked_ops'],
+ ['dump_blocked_ops_count'],
+ ['dump_historic_ops'],
+ ['dump_historic_ops_by_duration'],
+ ['dump_mempools'],
+ ['dump_ops_in_flight'],
+ ['flush', 'journal'],
+ ['get', 'subtrees'],
+ ['ops', 'locks'],
+ ['ops'],
+ ['status'],
+ ['version'],
+ ]
+ def test(c):
+ def f(self):
+ J = self.fs.rank_tell(c)
+ json.dumps(J)
+ log.debug("dumped:\n%s", str(J))
+ return f
+ for c in tells:
+ setattr(cls, 'test_valid_' + '_'.join(c), test(c))
+
+class TestFsStatus(TestAdminCommands):
+ """
+ Test "ceph fs status subcommand.
+ """
+
+ def test_fs_status(self):
+ """
+ That `ceph fs status` command functions.
+ """
+
+ s = self.fs.mon_manager.raw_cluster_cmd("fs", "status")
+ self.assertTrue("active" in s)
+
+ mdsmap = json.loads(self.fs.mon_manager.raw_cluster_cmd("fs", "status", "--format=json-pretty"))["mdsmap"]
+ self.assertEqual(mdsmap[0]["state"], "active")
+
+ mdsmap = json.loads(self.fs.mon_manager.raw_cluster_cmd("fs", "status", "--format=json"))["mdsmap"]
+ self.assertEqual(mdsmap[0]["state"], "active")
+
+
+class TestAddDataPool(TestAdminCommands):
+ """
+ Test "ceph fs add_data_pool" subcommand.
+ """
+
+ def test_add_data_pool_root(self):
+ """
+ That a new data pool can be added and used for the root directory.
+ """
+
+ p = self.fs.add_data_pool("foo")
+ self.fs.set_dir_layout(self.mount_a, ".", FileLayout(pool=p))
+
+ def test_add_data_pool_application_metadata(self):
+ """
+ That the application metadata set on a newly added data pool is as expected.
+ """
+ pool_name = "foo"
+ mon_cmd = self.fs.mon_manager.raw_cluster_cmd
+ mon_cmd('osd', 'pool', 'create', pool_name, '--pg_num_min',
+ str(self.fs.pg_num_min))
+ # Check whether https://tracker.ceph.com/issues/43061 is fixed
+ mon_cmd('osd', 'pool', 'application', 'enable', pool_name, 'cephfs')
+ self.fs.add_data_pool(pool_name, create=False)
+ self.check_pool_application_metadata_key_value(
+ pool_name, 'cephfs', 'data', self.fs.name)
+
+ def test_add_data_pool_subdir(self):
+ """
+ That a new data pool can be added and used for a sub-directory.
+ """
+
+ p = self.fs.add_data_pool("foo")
+ self.mount_a.run_shell("mkdir subdir")
+ self.fs.set_dir_layout(self.mount_a, "subdir", FileLayout(pool=p))
+
+ def test_add_data_pool_non_alphamueric_name_as_subdir(self):
+ """
+ That a new data pool with non-alphanumeric name can be added and used for a sub-directory.
+ """
+ p = self.fs.add_data_pool("I-am-data_pool00.")
+ self.mount_a.run_shell("mkdir subdir")
+ self.fs.set_dir_layout(self.mount_a, "subdir", FileLayout(pool=p))
+
+ def test_add_data_pool_ec(self):
+ """
+ That a new EC data pool can be added.
+ """
+
+ n = "test_add_data_pool_ec"
+ self.setup_ec_pools(n, metadata=False)
+ self.fs.add_data_pool(n+"-data", create=False)
+
+ def test_add_already_in_use_data_pool(self):
+ """
+ That command try to add data pool which is already in use with another fs.
+ """
+
+ # create first data pool, metadata pool and add with filesystem
+ first_fs = "first_fs"
+ first_metadata_pool = "first_metadata_pool"
+ first_data_pool = "first_data_pool"
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', first_metadata_pool)
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', first_data_pool)
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', first_fs, first_metadata_pool, first_data_pool)
+
+ # create second data pool, metadata pool and add with filesystem
+ second_fs = "second_fs"
+ second_metadata_pool = "second_metadata_pool"
+ second_data_pool = "second_data_pool"
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', second_metadata_pool)
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', second_data_pool)
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', second_fs, second_metadata_pool, second_data_pool)
+
+ # try to add 'first_data_pool' with 'second_fs'
+ # Expecting EINVAL exit status because 'first_data_pool' is already in use with 'first_fs'
+ try:
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'add_data_pool', second_fs, first_data_pool)
+ except CommandFailedError as e:
+ self.assertEqual(e.exitstatus, errno.EINVAL)
+ else:
+ self.fail("Expected EINVAL because data pool is already in use as data pool for first_fs")
+
+ def test_add_already_in_use_metadata_pool(self):
+ """
+ That command try to add metadata pool which is already in use with another fs.
+ """
+
+ # create first data pool, metadata pool and add with filesystem
+ first_fs = "first_fs"
+ first_metadata_pool = "first_metadata_pool"
+ first_data_pool = "first_data_pool"
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', first_metadata_pool)
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', first_data_pool)
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', first_fs, first_metadata_pool, first_data_pool)
+
+ # create second data pool, metadata pool and add with filesystem
+ second_fs = "second_fs"
+ second_metadata_pool = "second_metadata_pool"
+ second_data_pool = "second_data_pool"
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', second_metadata_pool)
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', second_data_pool)
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', second_fs, second_metadata_pool, second_data_pool)
+
+ # try to add 'second_metadata_pool' with 'first_fs' as a data pool
+ # Expecting EINVAL exit status because 'second_metadata_pool'
+ # is already in use with 'second_fs' as a metadata pool
+ try:
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'add_data_pool', first_fs, second_metadata_pool)
+ except CommandFailedError as e:
+ self.assertEqual(e.exitstatus, errno.EINVAL)
+ else:
+ self.fail("Expected EINVAL because data pool is already in use as metadata pool for 'second_fs'")
+
+class TestFsNew(TestAdminCommands):
+ """
+ Test "ceph fs new" subcommand.
+ """
+ MDSS_REQUIRED = 3
+
+ def test_fsnames_can_only_by_goodchars(self):
+ n = 'test_fsnames_can_only_by_goodchars'
+ metapoolname, datapoolname = n+'-testmetapool', n+'-testdatapool'
+ badname = n+'badname@#'
+
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create',
+ n+metapoolname)
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create',
+ n+datapoolname)
+
+ # test that fsname not with "goodchars" fails
+ args = ['fs', 'new', badname, metapoolname, datapoolname]
+ proc = self.fs.mon_manager.run_cluster_cmd(args=args,stderr=StringIO(),
+ check_status=False)
+ self.assertIn('invalid chars', proc.stderr.getvalue().lower())
+
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'rm', metapoolname,
+ metapoolname,
+ '--yes-i-really-really-mean-it-not-faking')
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'rm', datapoolname,
+ datapoolname,
+ '--yes-i-really-really-mean-it-not-faking')
+
+ def test_new_default_ec(self):
+ """
+ That a new file system warns/fails with an EC default data pool.
+ """
+
+ self.mount_a.umount_wait(require_clean=True)
+ self.mds_cluster.delete_all_filesystems()
+ n = "test_new_default_ec"
+ self.setup_ec_pools(n)
+ try:
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', n, n+"-meta", n+"-data")
+ except CommandFailedError as e:
+ if e.exitstatus == 22:
+ pass
+ else:
+ raise
+ else:
+ raise RuntimeError("expected failure")
+
+ def test_new_default_ec_force(self):
+ """
+ That a new file system succeeds with an EC default data pool with --force.
+ """
+
+ self.mount_a.umount_wait(require_clean=True)
+ self.mds_cluster.delete_all_filesystems()
+ n = "test_new_default_ec_force"
+ self.setup_ec_pools(n)
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', n, n+"-meta", n+"-data", "--force")
+
+ def test_new_default_ec_no_overwrite(self):
+ """
+ That a new file system fails with an EC default data pool without overwrite.
+ """
+
+ self.mount_a.umount_wait(require_clean=True)
+ self.mds_cluster.delete_all_filesystems()
+ n = "test_new_default_ec_no_overwrite"
+ self.setup_ec_pools(n, overwrites=False)
+ try:
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', n, n+"-meta", n+"-data")
+ except CommandFailedError as e:
+ if e.exitstatus == 22:
+ pass
+ else:
+ raise
+ else:
+ raise RuntimeError("expected failure")
+ # and even with --force !
+ try:
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', n, n+"-meta", n+"-data", "--force")
+ except CommandFailedError as e:
+ if e.exitstatus == 22:
+ pass
+ else:
+ raise
+ else:
+ raise RuntimeError("expected failure")
+
+ def test_fs_new_pool_application_metadata(self):
+ """
+ That the application metadata set on the pools of a newly created filesystem are as expected.
+ """
+ self.mount_a.umount_wait(require_clean=True)
+ self.mds_cluster.delete_all_filesystems()
+ fs_name = "test_fs_new_pool_application"
+ keys = ['metadata', 'data']
+ pool_names = [fs_name+'-'+key for key in keys]
+ mon_cmd = self.fs.mon_manager.raw_cluster_cmd
+ for p in pool_names:
+ mon_cmd('osd', 'pool', 'create', p, '--pg_num_min', str(self.fs.pg_num_min))
+ mon_cmd('osd', 'pool', 'application', 'enable', p, 'cephfs')
+ mon_cmd('fs', 'new', fs_name, pool_names[0], pool_names[1])
+ for i in range(2):
+ self.check_pool_application_metadata_key_value(
+ pool_names[i], 'cephfs', keys[i], fs_name)
+
+ def test_fs_new_with_specific_id(self):
+ """
+ That a file system can be created with a specific ID.
+ """
+ fs_name = "test_fs_specific_id"
+ fscid = 100
+ keys = ['metadata', 'data']
+ pool_names = [fs_name+'-'+key for key in keys]
+ for p in pool_names:
+ self.run_cluster_cmd(f'osd pool create {p}')
+ self.run_cluster_cmd(f'fs new {fs_name} {pool_names[0]} {pool_names[1]} --fscid {fscid} --force')
+ self.fs.status().get_fsmap(fscid)
+ for i in range(2):
+ self.check_pool_application_metadata_key_value(pool_names[i], 'cephfs', keys[i], fs_name)
+
+ def test_fs_new_with_specific_id_idempotency(self):
+ """
+ That command to create file system with specific ID is idempotent.
+ """
+ fs_name = "test_fs_specific_id"
+ fscid = 100
+ keys = ['metadata', 'data']
+ pool_names = [fs_name+'-'+key for key in keys]
+ for p in pool_names:
+ self.run_cluster_cmd(f'osd pool create {p}')
+ self.run_cluster_cmd(f'fs new {fs_name} {pool_names[0]} {pool_names[1]} --fscid {fscid} --force')
+ self.run_cluster_cmd(f'fs new {fs_name} {pool_names[0]} {pool_names[1]} --fscid {fscid} --force')
+ self.fs.status().get_fsmap(fscid)
+
+ def test_fs_new_with_specific_id_fails_without_force_flag(self):
+ """
+ That command to create file system with specific ID fails without '--force' flag.
+ """
+ fs_name = "test_fs_specific_id"
+ fscid = 100
+ keys = ['metadata', 'data']
+ pool_names = [fs_name+'-'+key for key in keys]
+ for p in pool_names:
+ self.run_cluster_cmd(f'osd pool create {p}')
+ try:
+ self.run_cluster_cmd(f'fs new {fs_name} {pool_names[0]} {pool_names[1]} --fscid {fscid}')
+ except CommandFailedError as ce:
+ self.assertEqual(ce.exitstatus, errno.EINVAL,
+ "invalid error code on creating a file system with specifc ID without --force flag")
+ else:
+ self.fail("expected creating file system with specific ID without '--force' flag to fail")
+
+ def test_fs_new_with_specific_id_fails_already_in_use(self):
+ """
+ That creating file system with ID already in use fails.
+ """
+ fs_name = "test_fs_specific_id"
+ # file system ID already in use
+ fscid = self.fs.status().map['filesystems'][0]['id']
+ keys = ['metadata', 'data']
+ pool_names = [fs_name+'-'+key for key in keys]
+ for p in pool_names:
+ self.run_cluster_cmd(f'osd pool create {p}')
+ try:
+ self.run_cluster_cmd(f'fs new {fs_name} {pool_names[0]} {pool_names[1]} --fscid {fscid} --force')
+ except CommandFailedError as ce:
+ self.assertEqual(ce.exitstatus, errno.EINVAL,
+ "invalid error code on creating a file system with specifc ID that is already in use")
+ else:
+ self.fail("expected creating file system with ID already in use to fail")
+
+ def test_fs_new_metadata_pool_already_in_use(self):
+ """
+ That creating file system with metadata pool already in use.
+ """
+
+ # create first data pool, metadata pool and add with filesystem
+ first_fs = "first_fs"
+ first_metadata_pool = "first_metadata_pool"
+ first_data_pool = "first_data_pool"
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', first_metadata_pool)
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', first_data_pool)
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', first_fs, first_metadata_pool, first_data_pool)
+
+ second_fs = "second_fs"
+ second_data_pool = "second_data_pool"
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', second_data_pool)
+
+ # try to create new fs 'second_fs' with following configuration
+ # metadata pool -> 'first_metadata_pool'
+ # data pool -> 'second_data_pool'
+ # Expecting EINVAL exit status because 'first_metadata_pool'
+ # is already in use with 'first_fs'
+ try:
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', second_fs, first_metadata_pool, second_data_pool)
+ except CommandFailedError as e:
+ self.assertEqual(e.exitstatus, errno.EINVAL)
+ else:
+ self.fail("Expected EINVAL because metadata pool is already in use for 'first_fs'")
+
+ def test_fs_new_data_pool_already_in_use(self):
+ """
+ That creating file system with data pool already in use.
+ """
+
+ # create first data pool, metadata pool and add with filesystem
+ first_fs = "first_fs"
+ first_metadata_pool = "first_metadata_pool"
+ first_data_pool = "first_data_pool"
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', first_metadata_pool)
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', first_data_pool)
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', first_fs, first_metadata_pool, first_data_pool)
+
+ second_fs = "second_fs"
+ second_metadata_pool = "second_metadata_pool"
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', second_metadata_pool)
+
+ # try to create new fs 'second_fs' with following configuration
+ # metadata pool -> 'second_metadata_pool'
+ # data pool -> 'first_data_pool'
+ # Expecting EINVAL exit status because 'first_data_pool'
+ # is already in use with 'first_fs'
+ try:
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', second_fs, second_metadata_pool, first_data_pool)
+ except CommandFailedError as e:
+ self.assertEqual(e.exitstatus, errno.EINVAL)
+ else:
+ self.fail("Expected EINVAL because data pool is already in use for 'first_fs'")
+
+ def test_fs_new_metadata_and_data_pool_in_use_by_another_same_fs(self):
+ """
+ That creating file system with metadata and data pool which is already in use by another same fs.
+ """
+
+ # create first data pool, metadata pool and add with filesystem
+ first_fs = "first_fs"
+ first_metadata_pool = "first_metadata_pool"
+ first_data_pool = "first_data_pool"
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', first_metadata_pool)
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', first_data_pool)
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', first_fs, first_metadata_pool, first_data_pool)
+
+ second_fs = "second_fs"
+
+ # try to create new fs 'second_fs' with following configuration
+ # metadata pool -> 'first_metadata_pool'
+ # data pool -> 'first_data_pool'
+ # Expecting EINVAL exit status because 'first_metadata_pool' and 'first_data_pool'
+ # is already in use with 'first_fs'
+ try:
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', second_fs, first_metadata_pool, first_data_pool)
+ except CommandFailedError as e:
+ self.assertEqual(e.exitstatus, errno.EINVAL)
+ else:
+ self.fail("Expected EINVAL because metadata and data pool is already in use for 'first_fs'")
+
+ def test_fs_new_metadata_and_data_pool_in_use_by_different_fs(self):
+ """
+ That creating file system with metadata and data pool which is already in use by different fs.
+ """
+
+ # create first data pool, metadata pool and add with filesystem
+ first_fs = "first_fs"
+ first_metadata_pool = "first_metadata_pool"
+ first_data_pool = "first_data_pool"
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', first_metadata_pool)
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', first_data_pool)
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', first_fs, first_metadata_pool, first_data_pool)
+
+ # create second data pool, metadata pool and add with filesystem
+ second_fs = "second_fs"
+ second_metadata_pool = "second_metadata_pool"
+ second_data_pool = "second_data_pool"
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', second_metadata_pool)
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', second_data_pool)
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', second_fs, second_metadata_pool, second_data_pool)
+
+ third_fs = "third_fs"
+
+ # try to create new fs 'third_fs' with following configuration
+ # metadata pool -> 'first_metadata_pool'
+ # data pool -> 'second_data_pool'
+ # Expecting EINVAL exit status because 'first_metadata_pool' and 'second_data_pool'
+ # is already in use with 'first_fs' and 'second_fs'
+ try:
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', third_fs, first_metadata_pool, second_data_pool)
+ except CommandFailedError as e:
+ self.assertEqual(e.exitstatus, errno.EINVAL)
+ else:
+ self.fail("Expected EINVAL because metadata and data pool is already in use for 'first_fs' and 'second_fs'")
+
+ def test_fs_new_interchange_already_in_use_metadata_and_data_pool_of_same_fs(self):
+ """
+ That creating file system with interchanging metadata and data pool which is already in use by same fs.
+ """
+
+ # create first data pool, metadata pool and add with filesystem
+ first_fs = "first_fs"
+ first_metadata_pool = "first_metadata_pool"
+ first_data_pool = "first_data_pool"
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', first_metadata_pool)
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', first_data_pool)
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', first_fs, first_metadata_pool, first_data_pool)
+
+ second_fs = "second_fs"
+
+ # try to create new fs 'second_fs' with following configuration
+ # metadata pool -> 'first_data_pool' (already used as data pool for 'first_fs')
+ # data pool -> 'first_metadata_pool' (already used as metadata pool for 'first_fs')
+ # Expecting EINVAL exit status because 'first_data_pool' and 'first_metadata_pool'
+ # is already in use with 'first_fs'
+ try:
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', second_fs, first_data_pool, first_metadata_pool)
+ except CommandFailedError as e:
+ self.assertEqual(e.exitstatus, errno.EINVAL)
+ else:
+ self.fail("Expected EINVAL because metadata and data pool is already in use for 'first_fs'")
+
+ def test_fs_new_interchange_already_in_use_metadata_and_data_pool_of_different_fs(self):
+ """
+ That creating file system with interchanging metadata and data pool which is already in use by defferent fs.
+ """
+
+ # create first data pool, metadata pool and add with filesystem
+ first_fs = "first_fs"
+ first_metadata_pool = "first_metadata_pool"
+ first_data_pool = "first_data_pool"
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', first_metadata_pool)
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', first_data_pool)
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', first_fs, first_metadata_pool, first_data_pool)
+
+ # create second data pool, metadata pool and add with filesystem
+ second_fs = "second_fs"
+ second_metadata_pool = "second_metadata_pool"
+ second_data_pool = "second_data_pool"
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', second_metadata_pool)
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', second_data_pool)
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', second_fs, second_metadata_pool, second_data_pool)
+
+ third_fs = "third_fs"
+
+ # try to create new fs 'third_fs' with following configuration
+ # metadata pool -> 'first_data_pool' (already used as data pool for 'first_fs')
+ # data pool -> 'second_metadata_pool' (already used as metadata pool for 'second_fs')
+ # Expecting EINVAL exit status because 'first_data_pool' and 'second_metadata_pool'
+ # is already in use with 'first_fs' and 'second_fs'
+ try:
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', third_fs, first_data_pool, second_metadata_pool)
+ except CommandFailedError as e:
+ self.assertEqual(e.exitstatus, errno.EINVAL)
+ else:
+ self.fail("Expected EINVAL because metadata and data pool is already in use for 'first_fs' and 'second_fs'")
+
+ def test_fs_new_metadata_pool_already_in_use_with_rbd(self):
+ """
+ That creating new file system with metadata pool already used by rbd.
+ """
+
+ # create pool and initialise with rbd
+ new_pool = "new_pool"
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', new_pool)
+ self.ctx.cluster.run(args=['rbd', 'pool', 'init', new_pool])
+
+ new_fs = "new_fs"
+ new_data_pool = "new_data_pool"
+
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', new_data_pool)
+
+ # try to create new fs 'new_fs' with following configuration
+ # metadata pool -> 'new_pool' (already used by rbd app)
+ # data pool -> 'new_data_pool'
+ # Expecting EINVAL exit status because 'new_pool' is already in use with 'rbd' app
+ try:
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', new_fs, new_pool, new_data_pool)
+ except CommandFailedError as e:
+ self.assertEqual(e.exitstatus, errno.EINVAL)
+ else:
+ self.fail("Expected EINVAL because metadata pool is already in use for rbd")
+
+ def test_fs_new_data_pool_already_in_use_with_rbd(self):
+ """
+ That creating new file system with data pool already used by rbd.
+ """
+
+ # create pool and initialise with rbd
+ new_pool = "new_pool"
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', new_pool)
+ self.ctx.cluster.run(args=['rbd', 'pool', 'init', new_pool])
+
+ new_fs = "new_fs"
+ new_metadata_pool = "new_metadata_pool"
+
+ self.fs.mon_manager.raw_cluster_cmd('osd', 'pool', 'create', new_metadata_pool)
+
+ # try to create new fs 'new_fs' with following configuration
+ # metadata pool -> 'new_metadata_pool'
+ # data pool -> 'new_pool' (already used by rbd app)
+ # Expecting EINVAL exit status because 'new_pool' is already in use with 'rbd' app
+ try:
+ self.fs.mon_manager.raw_cluster_cmd('fs', 'new', new_fs, new_metadata_pool, new_pool)
+ except CommandFailedError as e:
+ self.assertEqual(e.exitstatus, errno.EINVAL)
+ else:
+ self.fail("Expected EINVAL because data pool is already in use for rbd")
+
+class TestRenameCommand(TestAdminCommands):
+ """
+ Tests for rename command.
+ """
+
+ CLIENTS_REQUIRED = 1
+ MDSS_REQUIRED = 2
+
+ def test_fs_rename(self):
+ """
+ That the file system can be renamed, and the application metadata set on its pools are as expected.
+ """
+ # Renaming the file system breaks this mount as the client uses
+ # file system specific authorization. The client cannot read
+ # or write even if the client's cephx ID caps are updated to access
+ # the new file system name without the client being unmounted and
+ # re-mounted.
+ self.mount_a.umount_wait(require_clean=True)
+ orig_fs_name = self.fs.name
+ new_fs_name = 'new_cephfs'
+ client_id = 'test_new_cephfs'
+
+ self.run_cluster_cmd(f'fs rename {orig_fs_name} {new_fs_name} --yes-i-really-mean-it')
+
+ # authorize a cephx ID access to the renamed file system.
+ # use the ID to write to the file system.
+ self.fs.name = new_fs_name
+ keyring = self.fs.authorize(client_id, ('/', 'rw'))
+ keyring_path = self.mount_a.client_remote.mktemp(data=keyring)
+ self.mount_a.remount(client_id=client_id,
+ client_keyring_path=keyring_path,
+ cephfs_mntpt='/',
+ cephfs_name=self.fs.name)
+ filedata, filename = 'some data on fs', 'file_on_fs'
+ filepath = os_path_join(self.mount_a.hostfs_mntpt, filename)
+ self.mount_a.write_file(filepath, filedata)
+ self.check_pool_application_metadata_key_value(
+ self.fs.get_data_pool_name(), 'cephfs', 'data', new_fs_name)
+ self.check_pool_application_metadata_key_value(
+ self.fs.get_metadata_pool_name(), 'cephfs', 'metadata', new_fs_name)
+
+ # cleanup
+ self.mount_a.umount_wait()
+ self.run_cluster_cmd(f'auth rm client.{client_id}')
+
+ def test_fs_rename_idempotency(self):
+ """
+ That the file system rename operation is idempotent.
+ """
+ # Renaming the file system breaks this mount as the client uses
+ # file system specific authorization.
+ self.mount_a.umount_wait(require_clean=True)
+ orig_fs_name = self.fs.name
+ new_fs_name = 'new_cephfs'
+
+ self.run_cluster_cmd(f'fs rename {orig_fs_name} {new_fs_name} --yes-i-really-mean-it')
+ self.run_cluster_cmd(f'fs rename {orig_fs_name} {new_fs_name} --yes-i-really-mean-it')
+
+ # original file system name does not appear in `fs ls` command
+ self.assertFalse(self.fs.exists())
+ self.fs.name = new_fs_name
+ self.assertTrue(self.fs.exists())
+
+ def test_fs_rename_fs_new_fails_with_old_fsname_existing_pools(self):
+ """
+ That after renaming a file system, creating a file system with
+ old name and existing FS pools fails.
+ """
+ # Renaming the file system breaks this mount as the client uses
+ # file system specific authorization.
+ self.mount_a.umount_wait(require_clean=True)
+ orig_fs_name = self.fs.name
+ new_fs_name = 'new_cephfs'
+ data_pool = self.fs.get_data_pool_name()
+ metadata_pool = self.fs.get_metadata_pool_name()
+ self.run_cluster_cmd(f'fs rename {orig_fs_name} {new_fs_name} --yes-i-really-mean-it')
+
+ try:
+ self.run_cluster_cmd(f"fs new {orig_fs_name} {metadata_pool} {data_pool}")
+ except CommandFailedError as ce:
+ self.assertEqual(ce.exitstatus, errno.EINVAL,
+ "invalid error code on creating a new file system with old "
+ "name and existing pools.")
+ else:
+ self.fail("expected creating new file system with old name and "
+ "existing pools to fail.")
+
+ try:
+ self.run_cluster_cmd(f"fs new {orig_fs_name} {metadata_pool} {data_pool} --force")
+ except CommandFailedError as ce:
+ self.assertEqual(ce.exitstatus, errno.EINVAL,
+ "invalid error code on creating a new file system with old "
+ "name, existing pools and --force flag.")
+ else:
+ self.fail("expected creating new file system with old name, "
+ "existing pools, and --force flag to fail.")
+
+ try:
+ self.run_cluster_cmd(f"fs new {orig_fs_name} {metadata_pool} {data_pool} "
+ "--allow-dangerous-metadata-overlay")
+ except CommandFailedError as ce:
+ self.assertEqual(ce.exitstatus, errno.EINVAL,
+ "invalid error code on creating a new file system with old name, "
+ "existing pools and --allow-dangerous-metadata-overlay flag.")
+ else:
+ self.fail("expected creating new file system with old name, "
+ "existing pools, and --allow-dangerous-metadata-overlay flag to fail.")
+
+ def test_fs_rename_fails_without_yes_i_really_mean_it_flag(self):
+ """
+ That renaming a file system without '--yes-i-really-mean-it' flag fails.
+ """
+ try:
+ self.run_cluster_cmd(f"fs rename {self.fs.name} new_fs")
+ except CommandFailedError as ce:
+ self.assertEqual(ce.exitstatus, errno.EPERM,
+ "invalid error code on renaming a file system without the "
+ "'--yes-i-really-mean-it' flag")
+ else:
+ self.fail("expected renaming of file system without the "
+ "'--yes-i-really-mean-it' flag to fail ")
+
+ def test_fs_rename_fails_for_non_existent_fs(self):
+ """
+ That renaming a non-existent file system fails.
+ """
+ try:
+ self.run_cluster_cmd("fs rename non_existent_fs new_fs --yes-i-really-mean-it")
+ except CommandFailedError as ce:
+ self.assertEqual(ce.exitstatus, errno.ENOENT, "invalid error code on renaming a non-existent fs")
+ else:
+ self.fail("expected renaming of a non-existent file system to fail")
+
+ def test_fs_rename_fails_new_name_already_in_use(self):
+ """
+ That renaming a file system fails if the new name refers to an existing file system.
+ """
+ self.fs2 = self.mds_cluster.newfs(name='cephfs2', create=True)
+
+ try:
+ self.run_cluster_cmd(f"fs rename {self.fs.name} {self.fs2.name} --yes-i-really-mean-it")
+ except CommandFailedError as ce:
+ self.assertEqual(ce.exitstatus, errno.EINVAL,
+ "invalid error code on renaming to a fs name that is already in use")
+ else:
+ self.fail("expected renaming to a new file system name that is already in use to fail.")
+
+ def test_fs_rename_fails_with_mirroring_enabled(self):
+ """
+ That renaming a file system fails if mirroring is enabled on it.
+ """
+ orig_fs_name = self.fs.name
+ new_fs_name = 'new_cephfs'
+
+ self.run_cluster_cmd(f'fs mirror enable {orig_fs_name}')
+ try:
+ self.run_cluster_cmd(f'fs rename {orig_fs_name} {new_fs_name} --yes-i-really-mean-it')
+ except CommandFailedError as ce:
+ self.assertEqual(ce.exitstatus, errno.EPERM, "invalid error code on renaming a mirrored file system")
+ else:
+ self.fail("expected renaming of a mirrored file system to fail")
+ self.run_cluster_cmd(f'fs mirror disable {orig_fs_name}')
+
+
+class TestDump(CephFSTestCase):
+ CLIENTS_REQUIRED = 0
+ MDSS_REQUIRED = 1
+
+ def test_fs_dump_epoch(self):
+ """
+ That dumping a specific epoch works.
+ """
+
+ status1 = self.fs.status()
+ status2 = self.fs.status(epoch=status1["epoch"]-1)
+ self.assertEqual(status1["epoch"], status2["epoch"]+1)
+
+ def test_fsmap_trim(self):
+ """
+ That the fsmap is trimmed normally.
+ """
+
+ paxos_service_trim_min = 25
+ self.config_set('mon', 'paxos_service_trim_min', paxos_service_trim_min)
+ mon_max_mdsmap_epochs = 20
+ self.config_set('mon', 'mon_max_mdsmap_epochs', mon_max_mdsmap_epochs)
+
+ status = self.fs.status()
+ epoch = status["epoch"]
+
+ # for N mutations
+ mutations = paxos_service_trim_min + mon_max_mdsmap_epochs
+ b = False
+ for i in range(mutations):
+ self.fs.set_joinable(b)
+ b = not b
+
+ time.sleep(10) # for tick/compaction
+
+ try:
+ self.fs.status(epoch=epoch)
+ except CommandFailedError as e:
+ self.assertEqual(e.exitstatus, errno.ENOENT, "invalid error code when trying to fetch FSMap that was trimmed")
+ else:
+ self.fail("trimming did not occur as expected")
+
+ def test_fsmap_force_trim(self):
+ """
+ That the fsmap is trimmed forcefully.
+ """
+
+ status = self.fs.status()
+ epoch = status["epoch"]
+
+ paxos_service_trim_min = 1
+ self.config_set('mon', 'paxos_service_trim_min', paxos_service_trim_min)
+ mon_mds_force_trim_to = epoch+1
+ self.config_set('mon', 'mon_mds_force_trim_to', mon_mds_force_trim_to)
+
+ # force a new fsmap
+ self.fs.set_joinable(False)
+ time.sleep(10) # for tick/compaction
+
+ status = self.fs.status()
+ log.debug(f"new epoch is {status['epoch']}")
+ self.fs.status(epoch=epoch+1) # epoch+1 is not trimmed, may not == status["epoch"]
+
+ try:
+ self.fs.status(epoch=epoch)
+ except CommandFailedError as e:
+ self.assertEqual(e.exitstatus, errno.ENOENT, "invalid error code when trying to fetch FSMap that was trimmed")
+ else:
+ self.fail("trimming did not occur as expected")
+
+
+class TestRequiredClientFeatures(CephFSTestCase):
+ CLIENTS_REQUIRED = 0
+ MDSS_REQUIRED = 1
+
+ def test_required_client_features(self):
+ """
+ That `ceph fs required_client_features` command functions.
+ """
+
+ def is_required(index):
+ out = self.fs.mon_manager.raw_cluster_cmd('fs', 'get', self.fs.name, '--format=json-pretty')
+ features = json.loads(out)['mdsmap']['required_client_features']
+ if "feature_{0}".format(index) in features:
+ return True;
+ return False;
+
+ features = json.loads(self.fs.mon_manager.raw_cluster_cmd('fs', 'feature', 'ls', '--format=json-pretty'))
+ self.assertGreater(len(features), 0);
+
+ for f in features:
+ self.fs.required_client_features('rm', str(f['index']))
+
+ for f in features:
+ index = f['index']
+ feature = f['name']
+ if feature == 'reserved':
+ feature = str(index)
+
+ if index % 3 == 0:
+ continue;
+ self.fs.required_client_features('add', feature)
+ self.assertTrue(is_required(index))
+
+ if index % 2 == 0:
+ continue;
+ self.fs.required_client_features('rm', feature)
+ self.assertFalse(is_required(index))
+
+ def test_required_client_feature_add_reserved(self):
+ """
+ That `ceph fs required_client_features X add reserved` fails.
+ """
+
+ p = self.fs.required_client_features('add', 'reserved', check_status=False, stderr=StringIO())
+ self.assertIn('Invalid feature name', p.stderr.getvalue())
+
+ def test_required_client_feature_rm_reserved(self):
+ """
+ That `ceph fs required_client_features X rm reserved` fails.
+ """
+
+ p = self.fs.required_client_features('rm', 'reserved', check_status=False, stderr=StringIO())
+ self.assertIn('Invalid feature name', p.stderr.getvalue())
+
+ def test_required_client_feature_add_reserved_bit(self):
+ """
+ That `ceph fs required_client_features X add <reserved_bit>` passes.
+ """
+
+ p = self.fs.required_client_features('add', '1', stderr=StringIO())
+ self.assertIn("added feature 'reserved' to required_client_features", p.stderr.getvalue())
+
+ def test_required_client_feature_rm_reserved_bit(self):
+ """
+ That `ceph fs required_client_features X rm <reserved_bit>` passes.
+ """
+
+ self.fs.required_client_features('add', '1')
+ p = self.fs.required_client_features('rm', '1', stderr=StringIO())
+ self.assertIn("removed feature 'reserved' from required_client_features", p.stderr.getvalue())
+
+class TestCompatCommands(CephFSTestCase):
+ """
+ """
+
+ CLIENTS_REQUIRED = 0
+ MDSS_REQUIRED = 3
+
+ def test_add_compat(self):
+ """
+ Test adding a compat.
+ """
+
+ self.fs.fail()
+ self.fs.add_compat(63, 'placeholder')
+ mdsmap = self.fs.get_mds_map()
+ self.assertIn("feature_63", mdsmap['compat']['compat'])
+
+ def test_add_incompat(self):
+ """
+ Test adding an incompat.
+ """
+
+ self.fs.fail()
+ self.fs.add_incompat(63, 'placeholder')
+ mdsmap = self.fs.get_mds_map()
+ log.info(f"{mdsmap}")
+ self.assertIn("feature_63", mdsmap['compat']['incompat'])
+
+ def test_rm_compat(self):
+ """
+ Test removing a compat.
+ """
+
+ self.fs.fail()
+ self.fs.add_compat(63, 'placeholder')
+ self.fs.rm_compat(63)
+ mdsmap = self.fs.get_mds_map()
+ self.assertNotIn("feature_63", mdsmap['compat']['compat'])
+
+ def test_rm_incompat(self):
+ """
+ Test removing an incompat.
+ """
+
+ self.fs.fail()
+ self.fs.add_incompat(63, 'placeholder')
+ self.fs.rm_incompat(63)
+ mdsmap = self.fs.get_mds_map()
+ self.assertNotIn("feature_63", mdsmap['compat']['incompat'])
+
+ def test_standby_compat(self):
+ """
+ That adding a compat does not prevent standbys from joining.
+ """
+
+ self.fs.fail()
+ self.fs.add_compat(63, "placeholder")
+ self.fs.set_joinable()
+ self.fs.wait_for_daemons()
+ mdsmap = self.fs.get_mds_map()
+ self.assertIn("feature_63", mdsmap['compat']['compat'])
+
+ def test_standby_incompat_reject(self):
+ """
+ That adding an incompat feature prevents incompatible daemons from joining.
+ """
+
+ self.fs.fail()
+ self.fs.add_incompat(63, "placeholder")
+ self.fs.set_joinable()
+ try:
+ self.fs.wait_for_daemons(timeout=60)
+ except RuntimeError as e:
+ if "Timed out waiting for MDS daemons to become healthy" in str(e):
+ pass
+ else:
+ raise
+ else:
+ self.fail()
+
+ def test_standby_incompat_upgrade(self):
+ """
+ That an MDS can upgrade the compat of a fs.
+ """
+
+ self.fs.fail()
+ self.fs.rm_incompat(1)
+ self.fs.set_joinable()
+ self.fs.wait_for_daemons()
+ mdsmap = self.fs.get_mds_map()
+ self.assertIn("feature_1", mdsmap['compat']['incompat'])
+
+ def test_standby_replay_not_upgradeable(self):
+ """
+ That the mons will not upgrade the MDSMap compat if standby-replay is
+ enabled.
+ """
+
+ self.fs.fail()
+ self.fs.rm_incompat(1)
+ self.fs.set_allow_standby_replay(True)
+ self.fs.set_joinable()
+ try:
+ self.fs.wait_for_daemons(timeout=60)
+ except RuntimeError as e:
+ if "Timed out waiting for MDS daemons to become healthy" in str(e):
+ pass
+ else:
+ raise
+ else:
+ self.fail()
+
+ def test_standby_incompat_reject_multifs(self):
+ """
+ Like test_standby_incompat_reject but with a second fs.
+ """
+
+ fs2 = self.mds_cluster.newfs(name="cephfs2", create=True)
+ fs2.fail()
+ fs2.add_incompat(63, 'placeholder')
+ fs2.set_joinable()
+ try:
+ fs2.wait_for_daemons(timeout=60)
+ except RuntimeError as e:
+ if "Timed out waiting for MDS daemons to become healthy" in str(e):
+ pass
+ else:
+ raise
+ else:
+ self.fail()
+ # did self.fs lose MDS or standbys suicide?
+ self.fs.wait_for_daemons()
+ mdsmap = fs2.get_mds_map()
+ self.assertIn("feature_63", mdsmap['compat']['incompat'])
+
+class TestConfigCommands(CephFSTestCase):
+ """
+ Test that daemons and clients respond to the otherwise rarely-used
+ runtime config modification operations.
+ """
+
+ CLIENTS_REQUIRED = 1
+ MDSS_REQUIRED = 1
+
+ def test_ceph_config_show(self):
+ """
+ That I can successfully show MDS configuration.
+ """
+
+ names = self.fs.get_rank_names()
+ for n in names:
+ s = self.fs.mon_manager.raw_cluster_cmd("config", "show", "mds."+n)
+ self.assertTrue("NAME" in s)
+ self.assertTrue("mon_host" in s)
+
+
+ def test_client_config(self):
+ """
+ That I can successfully issue asok "config set" commands
+
+ :return:
+ """
+
+ if not isinstance(self.mount_a, FuseMount):
+ self.skipTest("Test only applies to FUSE clients")
+
+ test_key = "client_cache_size"
+ test_val = "123"
+ self.mount_a.admin_socket(['config', 'set', test_key, test_val])
+ out = self.mount_a.admin_socket(['config', 'get', test_key])
+ self.assertEqual(out[test_key], test_val)
+
+
+ def test_mds_config_asok(self):
+ test_key = "mds_max_purge_ops"
+ test_val = "123"
+ self.fs.mds_asok(['config', 'set', test_key, test_val])
+ out = self.fs.mds_asok(['config', 'get', test_key])
+ self.assertEqual(out[test_key], test_val)
+
+ def test_mds_dump_cache_asok(self):
+ cache_file = "cache_file"
+ timeout = "1"
+ self.fs.rank_asok(['dump', 'cache', cache_file, timeout])
+
+ def test_mds_config_tell(self):
+ test_key = "mds_max_purge_ops"
+ test_val = "123"
+
+ self.fs.rank_tell(['injectargs', "--{0}={1}".format(test_key, test_val)])
+
+ # Read it back with asok because there is no `tell` equivalent
+ out = self.fs.rank_tell(['config', 'get', test_key])
+ self.assertEqual(out[test_key], test_val)
+
+
+class TestMirroringCommands(CephFSTestCase):
+ CLIENTS_REQUIRED = 1
+ MDSS_REQUIRED = 1
+
+ def _enable_mirroring(self, fs_name):
+ self.fs.mon_manager.raw_cluster_cmd("fs", "mirror", "enable", fs_name)
+
+ def _disable_mirroring(self, fs_name):
+ self.fs.mon_manager.raw_cluster_cmd("fs", "mirror", "disable", fs_name)
+
+ def _add_peer(self, fs_name, peer_spec, remote_fs_name):
+ peer_uuid = str(uuid.uuid4())
+ self.fs.mon_manager.raw_cluster_cmd("fs", "mirror", "peer_add", fs_name, peer_uuid, peer_spec, remote_fs_name)
+
+ def _remove_peer(self, fs_name, peer_uuid):
+ self.fs.mon_manager.raw_cluster_cmd("fs", "mirror", "peer_remove", fs_name, peer_uuid)
+
+ def _verify_mirroring(self, fs_name, flag_str):
+ status = self.fs.status()
+ fs_map = status.get_fsmap_byname(fs_name)
+ if flag_str == 'enabled':
+ self.assertTrue('mirror_info' in fs_map)
+ elif flag_str == 'disabled':
+ self.assertTrue('mirror_info' not in fs_map)
+ else:
+ raise RuntimeError(f'invalid flag_str {flag_str}')
+
+ def _get_peer_uuid(self, fs_name, peer_spec):
+ status = self.fs.status()
+ fs_map = status.get_fsmap_byname(fs_name)
+ mirror_info = fs_map.get('mirror_info', None)
+ self.assertTrue(mirror_info is not None)
+ for peer_uuid, remote in mirror_info['peers'].items():
+ client_name = remote['remote']['client_name']
+ cluster_name = remote['remote']['cluster_name']
+ spec = f'{client_name}@{cluster_name}'
+ if spec == peer_spec:
+ return peer_uuid
+ return None
+
+ def test_mirroring_command(self):
+ """basic mirroring command test -- enable, disable mirroring on a
+ filesystem"""
+ self._enable_mirroring(self.fs.name)
+ self._verify_mirroring(self.fs.name, "enabled")
+ self._disable_mirroring(self.fs.name)
+ self._verify_mirroring(self.fs.name, "disabled")
+
+ def test_mirroring_peer_commands(self):
+ """test adding and removing peers to a mirror enabled filesystem"""
+ self._enable_mirroring(self.fs.name)
+ self._add_peer(self.fs.name, "client.site-b@site-b", "fs_b")
+ self._add_peer(self.fs.name, "client.site-c@site-c", "fs_c")
+ self._verify_mirroring(self.fs.name, "enabled")
+ uuid_peer_b = self._get_peer_uuid(self.fs.name, "client.site-b@site-b")
+ uuid_peer_c = self._get_peer_uuid(self.fs.name, "client.site-c@site-c")
+ self.assertTrue(uuid_peer_b is not None)
+ self.assertTrue(uuid_peer_c is not None)
+ self._remove_peer(self.fs.name, uuid_peer_b)
+ self._remove_peer(self.fs.name, uuid_peer_c)
+ self._disable_mirroring(self.fs.name)
+ self._verify_mirroring(self.fs.name, "disabled")
+
+ def test_mirroring_command_idempotency(self):
+ """test to check idempotency of mirroring family of commands """
+ self._enable_mirroring(self.fs.name)
+ self._verify_mirroring(self.fs.name, "enabled")
+ self._enable_mirroring(self.fs.name)
+ # add peer
+ self._add_peer(self.fs.name, "client.site-b@site-b", "fs_b")
+ uuid_peer_b1 = self._get_peer_uuid(self.fs.name, "client.site-b@site-b")
+ self.assertTrue(uuid_peer_b1 is not None)
+ # adding the peer again should be idempotent
+ self._add_peer(self.fs.name, "client.site-b@site-b", "fs_b")
+ uuid_peer_b2 = self._get_peer_uuid(self.fs.name, "client.site-b@site-b")
+ self.assertTrue(uuid_peer_b2 is not None)
+ self.assertTrue(uuid_peer_b1 == uuid_peer_b2)
+ # remove peer
+ self._remove_peer(self.fs.name, uuid_peer_b1)
+ uuid_peer_b3 = self._get_peer_uuid(self.fs.name, "client.site-b@site-b")
+ self.assertTrue(uuid_peer_b3 is None)
+ # removing the peer again should be idempotent
+ self._remove_peer(self.fs.name, uuid_peer_b1)
+ self._disable_mirroring(self.fs.name)
+ self._verify_mirroring(self.fs.name, "disabled")
+ self._disable_mirroring(self.fs.name)
+
+ def test_mirroring_disable_with_peers(self):
+ """test disabling mirroring for a filesystem with active peers"""
+ self._enable_mirroring(self.fs.name)
+ self._add_peer(self.fs.name, "client.site-b@site-b", "fs_b")
+ self._verify_mirroring(self.fs.name, "enabled")
+ uuid_peer_b = self._get_peer_uuid(self.fs.name, "client.site-b@site-b")
+ self.assertTrue(uuid_peer_b is not None)
+ self._disable_mirroring(self.fs.name)
+ self._verify_mirroring(self.fs.name, "disabled")
+ # enable mirroring to check old peers
+ self._enable_mirroring(self.fs.name)
+ self._verify_mirroring(self.fs.name, "enabled")
+ # peer should be gone
+ uuid_peer_b = self._get_peer_uuid(self.fs.name, "client.site-b@site-b")
+ self.assertTrue(uuid_peer_b is None)
+ self._disable_mirroring(self.fs.name)
+ self._verify_mirroring(self.fs.name, "disabled")
+
+ def test_mirroring_with_filesystem_reset(self):
+ """test to verify mirroring state post filesystem reset"""
+ self._enable_mirroring(self.fs.name)
+ self._add_peer(self.fs.name, "client.site-b@site-b", "fs_b")
+ self._verify_mirroring(self.fs.name, "enabled")
+ uuid_peer_b = self._get_peer_uuid(self.fs.name, "client.site-b@site-b")
+ self.assertTrue(uuid_peer_b is not None)
+ # reset filesystem
+ self.fs.fail()
+ self.fs.reset()
+ self.fs.wait_for_daemons()
+ self._verify_mirroring(self.fs.name, "disabled")
+
+
+class TestFsAuthorize(CephFSTestCase):
+ client_id = 'testuser'
+ client_name = 'client.' + client_id
+
+ def test_single_path_r(self):
+ PERM = 'r'
+ FS_AUTH_CAPS = (('/', PERM),)
+ self.captester = CapTester()
+ self.setup_test_env(FS_AUTH_CAPS)
+
+ self.captester.run_mon_cap_tests(self.fs, self.client_id)
+ self.captester.run_mds_cap_tests(PERM)
+
+ def test_single_path_rw(self):
+ PERM = 'rw'
+ FS_AUTH_CAPS = (('/', PERM),)
+ self.captester = CapTester()
+ self.setup_test_env(FS_AUTH_CAPS)
+
+ self.captester.run_mon_cap_tests(self.fs, self.client_id)
+ self.captester.run_mds_cap_tests(PERM)
+
+ def test_single_path_rootsquash(self):
+ PERM = 'rw'
+ FS_AUTH_CAPS = (('/', PERM, 'root_squash'),)
+ self.captester = CapTester()
+ self.setup_test_env(FS_AUTH_CAPS)
+
+ # testing MDS caps...
+ # Since root_squash is set in client caps, client can read but not
+ # write even thought access level is set to "rw".
+ self.captester.conduct_pos_test_for_read_caps()
+ self.captester.conduct_neg_test_for_write_caps(sudo_write=True)
+
+ def test_single_path_authorize_on_nonalphanumeric_fsname(self):
+ """
+ That fs authorize command works on filesystems with names having [_.-]
+ characters
+ """
+ self.mount_a.umount_wait(require_clean=True)
+ self.mds_cluster.delete_all_filesystems()
+ fs_name = "cephfs-_."
+ self.fs = self.mds_cluster.newfs(name=fs_name)
+ self.fs.wait_for_daemons()
+ self.run_cluster_cmd(f'auth caps client.{self.mount_a.client_id} '
+ f'mon "allow r" '
+ f'osd "allow rw pool={self.fs.get_data_pool_name()}" '
+ f'mds allow')
+ self.mount_a.remount(cephfs_name=self.fs.name)
+ PERM = 'rw'
+ FS_AUTH_CAPS = (('/', PERM),)
+ self.captester = CapTester()
+ self.setup_test_env(FS_AUTH_CAPS)
+ self.captester.run_mds_cap_tests(PERM)
+
+ def test_multiple_path_r(self):
+ PERM = 'r'
+ FS_AUTH_CAPS = (('/dir1/dir12', PERM), ('/dir2/dir22', PERM))
+ for c in FS_AUTH_CAPS:
+ self.mount_a.run_shell(f'mkdir -p .{c[0]}')
+ self.captesters = (CapTester(), CapTester())
+ self.setup_test_env(FS_AUTH_CAPS)
+
+ self.run_cap_test_one_by_one(FS_AUTH_CAPS)
+
+ def test_multiple_path_rw(self):
+ PERM = 'rw'
+ FS_AUTH_CAPS = (('/dir1/dir12', PERM), ('/dir2/dir22', PERM))
+ for c in FS_AUTH_CAPS:
+ self.mount_a.run_shell(f'mkdir -p .{c[0]}')
+ self.captesters = (CapTester(), CapTester())
+ self.setup_test_env(FS_AUTH_CAPS)
+
+ self.run_cap_test_one_by_one(FS_AUTH_CAPS)
+
+ def run_cap_test_one_by_one(self, fs_auth_caps):
+ keyring = self.run_cluster_cmd(f'auth get {self.client_name}')
+ for i, c in enumerate(fs_auth_caps):
+ self.assertIn(i, (0, 1))
+ PATH = c[0]
+ PERM = c[1]
+ self._remount(keyring, PATH)
+ # actual tests...
+ self.captesters[i].run_mon_cap_tests(self.fs, self.client_id)
+ self.captesters[i].run_mds_cap_tests(PERM, PATH)
+
+ def tearDown(self):
+ self.mount_a.umount_wait()
+ self.run_cluster_cmd(f'auth rm {self.client_name}')
+
+ super(type(self), self).tearDown()
+
+ def _remount(self, keyring, path='/'):
+ keyring_path = self.mount_a.client_remote.mktemp(data=keyring)
+ self.mount_a.remount(client_id=self.client_id,
+ client_keyring_path=keyring_path,
+ cephfs_mntpt=path)
+
+ def setup_for_single_path(self, fs_auth_caps):
+ self.captester.write_test_files((self.mount_a,), '/')
+ keyring = self.fs.authorize(self.client_id, fs_auth_caps)
+ self._remount(keyring)
+
+ def setup_for_multiple_paths(self, fs_auth_caps):
+ for i, c in enumerate(fs_auth_caps):
+ PATH = c[0]
+ self.captesters[i].write_test_files((self.mount_a,), PATH)
+
+ self.fs.authorize(self.client_id, fs_auth_caps)
+
+ def setup_test_env(self, fs_auth_caps):
+ if len(fs_auth_caps) == 1:
+ self.setup_for_single_path(fs_auth_caps[0])
+ else:
+ self.setup_for_multiple_paths(fs_auth_caps)
+
+
+class TestAdminCommandIdempotency(CephFSTestCase):
+ """
+ Tests for administration command idempotency.
+ """
+
+ CLIENTS_REQUIRED = 0
+ MDSS_REQUIRED = 1
+
+ def test_rm_idempotency(self):
+ """
+ That a removing a fs twice is idempotent.
+ """
+
+ data_pools = self.fs.get_data_pool_names(refresh=True)
+ self.fs.fail()
+ self.fs.rm()
+ try:
+ self.fs.get_mds_map()
+ except FSMissing:
+ pass
+ else:
+ self.fail("get_mds_map should raise")
+ p = self.fs.rm()
+ self.assertIn("does not exist", p.stderr.getvalue())
+ self.fs.remove_pools(data_pools)
+
+
+class TestAdminCommandDumpTree(CephFSTestCase):
+ """
+ Tests for administration command subtrees.
+ """
+
+ CLIENTS_REQUIRED = 0
+ MDSS_REQUIRED = 1
+
+ def test_dump_subtrees(self):
+ """
+ Dump all the subtrees to make sure the MDS daemon won't crash.
+ """
+
+ subtrees = self.fs.mds_asok(['get', 'subtrees'])
+ log.info(f"dumping {len(subtrees)} subtrees:")
+ for subtree in subtrees:
+ log.info(f" subtree: '{subtree['dir']['path']}'")
+ self.fs.mds_asok(['dump', 'tree', subtree['dir']['path']])
+
+ log.info("dumping 2 special subtrees:")
+ log.info(" subtree: '/'")
+ self.fs.mds_asok(['dump', 'tree', '/'])
+ log.info(" subtree: '~mdsdir'")
+ self.fs.mds_asok(['dump', 'tree', '~mdsdir'])
+
+class TestAdminCommandDumpLoads(CephFSTestCase):
+ """
+ Tests for administration command dump loads.
+ """
+
+ CLIENTS_REQUIRED = 0
+ MDSS_REQUIRED = 1
+
+ def test_dump_loads(self):
+ """
+ make sure depth limit param is considered when dump loads for a MDS daemon.
+ """
+
+ log.info("dumping loads")
+ loads = self.fs.mds_asok(['dump', 'loads', '1'])
+ self.assertIsNotNone(loads)
+ self.assertIn("dirfrags", loads)
+ for d in loads["dirfrags"]:
+ self.assertLessEqual(d["path"].count("/"), 1)
+
+class TestFsBalRankMask(CephFSTestCase):
+ """
+ Tests ceph fs set <fs_name> bal_rank_mask
+ """
+
+ CLIENTS_REQUIRED = 0
+ MDSS_REQUIRED = 2
+
+ def test_bal_rank_mask(self):
+ """
+ check whether a specified bal_rank_mask value is valid or not.
+ """
+ bal_rank_mask = '0x0'
+ log.info(f"set bal_rank_mask {bal_rank_mask}")
+ self.fs.set_bal_rank_mask(bal_rank_mask)
+ self.assertEqual(bal_rank_mask, self.fs.get_var('bal_rank_mask'))
+
+ bal_rank_mask = '0'
+ log.info(f"set bal_rank_mask {bal_rank_mask}")
+ self.fs.set_bal_rank_mask(bal_rank_mask)
+ self.assertEqual(bal_rank_mask, self.fs.get_var('bal_rank_mask'))
+
+ bal_rank_mask = '-1'
+ log.info(f"set bal_rank_mask {bal_rank_mask}")
+ self.fs.set_bal_rank_mask(bal_rank_mask)
+ self.assertEqual(bal_rank_mask, self.fs.get_var('bal_rank_mask'))
+
+ bal_rank_mask = 'all'
+ log.info(f"set bal_rank_mask {bal_rank_mask}")
+ self.fs.set_bal_rank_mask(bal_rank_mask)
+ self.assertEqual(bal_rank_mask, self.fs.get_var('bal_rank_mask'))
+
+ bal_rank_mask = '0x1'
+ log.info(f"set bal_rank_mask {bal_rank_mask}")
+ self.fs.set_bal_rank_mask(bal_rank_mask)
+ self.assertEqual(bal_rank_mask, self.fs.get_var('bal_rank_mask'))
+
+ bal_rank_mask = '1'
+ log.info(f"set bal_rank_mask {bal_rank_mask}")
+ self.fs.set_bal_rank_mask(bal_rank_mask)
+ self.assertEqual(bal_rank_mask, self.fs.get_var('bal_rank_mask'))
+
+ bal_rank_mask = 'f0'
+ log.info(f"set bal_rank_mask {bal_rank_mask}")
+ self.fs.set_bal_rank_mask(bal_rank_mask)
+ self.assertEqual(bal_rank_mask, self.fs.get_var('bal_rank_mask'))
+
+ bal_rank_mask = 'ab'
+ log.info(f"set bal_rank_mask {bal_rank_mask}")
+ self.fs.set_bal_rank_mask(bal_rank_mask)
+ self.assertEqual(bal_rank_mask, self.fs.get_var('bal_rank_mask'))
+
+ bal_rank_mask = '0xfff0'
+ log.info(f"set bal_rank_mask {bal_rank_mask}")
+ self.fs.set_bal_rank_mask(bal_rank_mask)
+ self.assertEqual(bal_rank_mask, self.fs.get_var('bal_rank_mask'))
+
+ MAX_MDS = 256
+ bal_rank_mask = '0x' + 'f' * int(MAX_MDS / 4)
+ log.info(f"set bal_rank_mask {bal_rank_mask}")
+ self.fs.set_bal_rank_mask(bal_rank_mask)
+ self.assertEqual(bal_rank_mask, self.fs.get_var('bal_rank_mask'))
+
+ bal_rank_mask = ''
+ log.info("set bal_rank_mask to empty string")
+ try:
+ self.fs.set_bal_rank_mask(bal_rank_mask)
+ except CommandFailedError as e:
+ self.assertEqual(e.exitstatus, errno.EINVAL)
+
+ bal_rank_mask = '0x1' + 'f' * int(MAX_MDS / 4)
+ log.info(f"set bal_rank_mask {bal_rank_mask}")
+ try:
+ self.fs.set_bal_rank_mask(bal_rank_mask)
+ except CommandFailedError as e:
+ self.assertEqual(e.exitstatus, errno.EINVAL)