From a1715c82b4c684e5f25aa476a37bef262ce1f2ab Mon Sep 17 00:00:00 2001 From: Dhairya Parmar Date: Mon, 12 Jun 2023 23:21:10 +0530 Subject: [PATCH] qa: add test cases and some helpers Fixes: https://tracker.ceph.com/issues/58072 Signed-off-by: Dhairya Parmar --- qa/tasks/cephfs/filesystem.py | 47 ++++++++- qa/tasks/cephfs/test_misc.py | 180 ++++++++++++++++++++++++++++++++++ qa/tasks/vstart_runner.py | 14 ++- 3 files changed, 232 insertions(+), 9 deletions(-) diff --git a/qa/tasks/cephfs/filesystem.py b/qa/tasks/cephfs/filesystem.py index 943b9cb3247..7aa9037133b 100644 --- a/qa/tasks/cephfs/filesystem.py +++ b/qa/tasks/cephfs/filesystem.py @@ -389,8 +389,12 @@ class MDSCluster(CephCluster): def mds_is_running(self, mds_id): return self.mds_daemons[mds_id].running() - def newfs(self, name='cephfs', create=True): - return Filesystem(self._ctx, name=name, create=create) + def newfs(self, name='cephfs', create=True, **kwargs): + """ + kwargs accepts recover: bool, allow_dangerous_metadata_overlay: bool, + yes_i_really_really_mean_it: bool and fs_ops: list[str] + """ + return Filesystem(self._ctx, name=name, create=create, **kwargs) def status(self, epoch=None): return FSStatus(self.mon_manager, epoch) @@ -521,7 +525,12 @@ class Filesystem(MDSCluster): This object is for driving a CephFS filesystem. The MDS daemons driven by MDSCluster may be shared with other Filesystems. """ - def __init__(self, ctx, fs_config={}, fscid=None, name=None, create=False): + def __init__(self, ctx, fs_config={}, fscid=None, name=None, create=False, + **kwargs): + """ + kwargs accepts recover: bool, allow_dangerous_metadata_overlay: bool, + yes_i_really_really_mean_it: bool and fs_ops: list[str] + """ super(Filesystem, self).__init__(ctx) self.name = name @@ -540,7 +549,7 @@ class Filesystem(MDSCluster): if fscid is not None: raise RuntimeError("cannot specify fscid when creating fs") if create and not self.legacy_configured(): - self.create() + self.create(**kwargs) else: if fscid is not None: self.id = fscid @@ -662,7 +671,11 @@ class Filesystem(MDSCluster): target_size_ratio = 0.9 target_size_ratio_ec = 0.9 - def create(self, recover=False, metadata_overlay=False): + def create(self, **kwargs): + """ + kwargs accepts recover: bool, allow_dangerous_metadata_overlay: bool, + yes_i_really_really_mean_it: bool and fs_ops: list[str] + """ if self.name is None: self.name = "cephfs" if self.metadata_pool_name is None: @@ -672,6 +685,12 @@ class Filesystem(MDSCluster): else: data_pool_name = self.data_pool_name + recover = kwargs.pop("recover", False) + metadata_overlay = kwargs.pop("metadata_overlay", False) + yes_i_really_really_mean_it = kwargs.pop("yes_i_really_really_mean_it", + False) + fs_ops = kwargs.pop("fs_ops", None) + # will use the ec pool to store the data and a small amount of # metadata still goes to the primary data pool for all files. if not metadata_overlay and self.ec_profile and 'disabled' not in self.ec_profile: @@ -705,6 +724,12 @@ class Filesystem(MDSCluster): args.append('--recover') if metadata_overlay: args.append('--allow-dangerous-metadata-overlay') + if yes_i_really_really_mean_it: + args.append('--yes-i-really-really-mean-it') + if fs_ops: + args.append('set') + for key_or_val in fs_ops: + args.append(key_or_val) self.run_ceph_cmd(*args) if not recover: @@ -924,6 +949,18 @@ class Filesystem(MDSCluster): def get_var(self, var, status=None): return self.get_mds_map(status=status)[var] + def get_var_from_fs(self, fsname, var): + val = None + for fs in self.status().get_filesystems(): + if fs["mdsmap"]["fs_name"] == fsname: + try: + val = fs["mdsmap"][var] + break + except KeyError: + val = fs["mdsmap"]["flags_state"][var] + break + return val + def set_dir_layout(self, mount, path, layout): for name, value in layout.items(): mount.run_shell(args=["setfattr", "-n", "ceph.dir.layout."+name, "-v", str(value), path]) diff --git a/qa/tasks/cephfs/test_misc.py b/qa/tasks/cephfs/test_misc.py index 58c4e379095..34e90681c95 100644 --- a/qa/tasks/cephfs/test_misc.py +++ b/qa/tasks/cephfs/test_misc.py @@ -656,3 +656,183 @@ class TestSkipReplayInoTable(CephFSTestCase): ls_out = set(self.mount_a.ls("test_alloc_ino/")) self.assertEqual(ls_out, set({"dir1", "dir2"})) + + +class TestNewFSCreation(CephFSTestCase): + MDSS_REQUIRED = 1 + TEST_FS = "test_fs" + TEST_FS1 = "test_fs1" + + def test_fs_creation_valid_ops(self): + """ + Test setting fs ops with CLI command `ceph fs new`. + """ + fs_ops = [["max_mds", "3"], ["refuse_client_session", "true"], + ["allow_new_snaps", "true", "max_file_size", "65536"], + ["session_timeout", "234", "session_autoclose", + "100", "max_xattr_size", "150"]] + + for fs_ops_list in fs_ops: + test_fs = None + try: + test_fs = self.mds_cluster.newfs(name=self.TEST_FS, + create=True, + fs_ops=fs_ops_list) + + for i in range(0, len(fs_ops_list), 2): + # edge case: for option `allow_new_snaps`, the flag name + # is `allow_snaps` in mdsmap + if fs_ops_list[i] == "allow_new_snaps": + fs_ops_list[i] = "allow_snaps" + fs_op_val = str(test_fs.get_var_from_fs( + self.TEST_FS, fs_ops_list[i])).lower() + self.assertEqual(fs_op_val, fs_ops_list[i+1]) + finally: + if test_fs is not None: + test_fs.destroy() + + def test_fs_creation_invalid_ops(self): + """ + Test setting invalid fs ops with CLI command `ceph fs new`. + """ + invalid_fs_ops = {("inline_data", "true"): errno.EPERM, + ("session_timeout", "3"): errno.ERANGE, + ("session_autoclose", "foo"): errno.EINVAL, + ("max_mds", "-1"): errno.EINVAL, + ("bal_rank_mask", ""): errno.EINVAL, + ("foo", "2"): errno.EINVAL, + ("", ""): errno.EINVAL, + ("session_timeout", "180", "", "3"): errno.EINVAL, + ("allow_new_snaps", "true", "max_mddds", "3"): + errno.EINVAL, + ("allow_new_snapsss", "true", "max_mds", "3"): + errno.EINVAL, + ("session_timeout", "20", "max_mddds", "3"): + errno.ERANGE} + + for invalid_op_list, expected_errno in invalid_fs_ops.items(): + test_fs = None + try: + test_fs = self.mds_cluster.newfs(name=self.TEST_FS, create=True, + fs_ops=invalid_op_list) + except CommandFailedError as e: + self.assertEqual(e.exitstatus, expected_errno) + else: + self.fail(f"Expected {expected_errno}") + finally: + if test_fs is not None: + test_fs.destroy() + + def test_fs_creation_incomplete_args(self): + """ + Test sending incomplete key-val pair of fs ops. + """ + invalid_args_fs_ops = [["max_mds"], ["max_mds", "2", "3"], [""]] + + for incomplete_args in invalid_args_fs_ops: + test_fs = None + try: + test_fs = self.mds_cluster.newfs(name=self.TEST_FS, create=True, + fs_ops=incomplete_args) + except CommandFailedError as e: + self.assertEqual(e.exitstatus, errno.EINVAL) + else: + self.fail("Expected EINVAL") + finally: + if test_fs is not None: + test_fs.destroy() + + def test_endure_fs_fields_post_failure(self): + """ + Test fields like epoch and legacy_client_fscid should not change after + fs creation failure. + """ + initial_epoch_ = self.mds_cluster.status()["epoch"] + initial_default_fscid = self.mds_cluster.status()["default_fscid"] + + test_fs = None + try: + test_fs = self.mds_cluster.newfs(name=self.TEST_FS, create=True, + fs_ops=["foo"]) + except CommandFailedError as e: + self.assertEqual(e.exitstatus, errno.EINVAL) + self.assertEqual(initial_epoch_, + self.mds_cluster.status()["epoch"]) + self.assertEqual(initial_default_fscid, + self.mds_cluster.status()["default_fscid"]) + else: + self.fail("Expected EINVAL") + finally: + if test_fs is not None: + test_fs.destroy() + + def test_yes_i_really_really_mean_it(self): + """ + --yes-i-really-really-mean-it can be used while creating fs with + CLI command `ceph fs new`, test fs creation succeeds. + """ + test_fs = None + try: + test_fs = self.mds_cluster.newfs(name=self.TEST_FS, create=True, + yes_i_really_really_mean_it=True) + self.assertTrue(test_fs.exists()) + finally: + if test_fs is not None: + test_fs.destroy() + + def test_inline_data(self): + """ + inline_data needs --yes-i-really-really-mean-it to get it enabled. + Test fs creation by with/without providing it. + NOTE: inline_data is deprecated, this test case would be removed in + the future. + """ + test_fs = None + try: + test_fs = self.mds_cluster.newfs(name=self.TEST_FS, create=True, + fs_ops=["inline_data", "true"]) + except CommandFailedError as e: + self.assertEqual(e.exitstatus, errno.EPERM) + test_fs = self.mds_cluster.newfs(name=self.TEST_FS, create=True, + fs_ops=["inline_data", "true"], + yes_i_really_really_mean_it=True) + self.assertIn("mds uses inline data", str(test_fs.status())) + else: + self.fail("Expected EPERM") + finally: + if test_fs is not None: + test_fs.destroy() + + def test_no_fs_id_incr_on_fs_creation_fail(self): + """ + Failure while creating fs due to error in setting fs ops will keep on + incrementing `next_filesystem_id`, test its value is preserved and + rolled back in case fs creation fails. + """ + + test_fs, test_fs1 = None, None + try: + test_fs = self.mds_cluster.newfs(name=self.TEST_FS, create=True) + + for _ in range(5): + try: + self.mds_cluster.newfs(name=self.TEST_FS1, create=True, + fs_ops=["max_mdss", "2"]) + except CommandFailedError as e: + self.assertEqual(e.exitstatus, errno.EINVAL) + + test_fs1 = self.mds_cluster.newfs(name=self.TEST_FS1, create=True, + fs_ops=["max_mds", "2"]) + + test_fs_id, test_fs1_id = None, None + for fs in self.mds_cluster.status().get_filesystems(): + if fs["mdsmap"]["fs_name"] == self.TEST_FS: + test_fs_id = fs["id"] + if fs["mdsmap"]["fs_name"] == self.TEST_FS1: + test_fs1_id = fs["id"] + self.assertEqual(test_fs_id, test_fs1_id - 1) + finally: + if test_fs is not None: + test_fs.destroy() + if test_fs1 is not None: + test_fs1.destroy() diff --git a/qa/tasks/vstart_runner.py b/qa/tasks/vstart_runner.py index 96dc9fffba2..485e454d2ef 100644 --- a/qa/tasks/vstart_runner.py +++ b/qa/tasks/vstart_runner.py @@ -915,8 +915,8 @@ class LocalMDSCluster(LocalCephCluster, MDSCluster): # FIXME: unimplemented pass - def newfs(self, name='cephfs', create=True): - return LocalFilesystem(self._ctx, name=name, create=create) + def newfs(self, name='cephfs', create=True, **kwargs): + return LocalFilesystem(self._ctx, name=name, create=create, **kwargs) def delete_all_filesystems(self): """ @@ -935,7 +935,8 @@ class LocalMgrCluster(LocalCephCluster, MgrCluster): class LocalFilesystem(LocalMDSCluster, Filesystem): - def __init__(self, ctx, fs_config={}, fscid=None, name=None, create=False): + def __init__(self, ctx, fs_config={}, fscid=None, name=None, create=False, + **kwargs): # Deliberately skip calling Filesystem constructor LocalMDSCluster.__init__(self, ctx) @@ -958,7 +959,12 @@ class LocalFilesystem(LocalMDSCluster, Filesystem): if fscid is not None: raise RuntimeError("cannot specify fscid when creating fs") if create and not self.legacy_configured(): - self.create() + self.create(recover=kwargs.pop("fs_recover", False), + metadata_overlay=kwargs.pop("fs_metadata_overlay", + False), + fs_ops=kwargs.pop("fs_ops", None), + yes_i_really_really_mean_it=kwargs.pop( + "yes_i_really_really_mean_it", False)) else: if fscid is not None: self.id = fscid -- 2.47.3