]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
qa: add test cases and some helpers
authorDhairya Parmar <dparmar@redhat.com>
Mon, 12 Jun 2023 17:51:10 +0000 (23:21 +0530)
committerDhairya Parmar <dparmar@redhat.com>
Tue, 26 Dec 2023 10:24:24 +0000 (15:54 +0530)
Fixes: https://tracker.ceph.com/issues/58072
Signed-off-by: Dhairya Parmar <dparmar@redhat.com>
qa/tasks/cephfs/filesystem.py
qa/tasks/cephfs/test_misc.py
qa/tasks/vstart_runner.py

index 943b9cb324714193f6a82bce560b9fe6df4f0789..7aa9037133b79c2fcb6e820ad5a92c5e30e885af 100644 (file)
@@ -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])
index 58c4e379095a5843a909e7a1583b29bf815ce35b..34e90681c95b907e249a5391db21aab687da901a 100644 (file)
@@ -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()
index 96dc9fffba2fd919b0c37c172c42cfb6bfea483c..485e454d2ef8c707dcb854d1f6f1ff995a59d2c4 100644 (file)
@@ -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