From 3611bb2a54e0cdd56b1f5b3d923d2b059303085c Mon Sep 17 00:00:00 2001 From: Dhairya Parmar Date: Mon, 4 Aug 2025 03:22:11 +0530 Subject: [PATCH] qa/tasks/cephfs: add test cases for subvolume config snapshot_visibility Fixes: https://tracker.ceph.com/issues/71740 Signed-off-by: Dhairya Parmar --- .../test/subvolume-snapshots-visibility.yaml | 7 + qa/tasks/cephfs/test_volumes.py | 1026 +++++++++++++++++ 2 files changed, 1033 insertions(+) create mode 100644 qa/suites/fs/volumes/tasks/volumes/test/subvolume-snapshots-visibility.yaml diff --git a/qa/suites/fs/volumes/tasks/volumes/test/subvolume-snapshots-visibility.yaml b/qa/suites/fs/volumes/tasks/volumes/test/subvolume-snapshots-visibility.yaml new file mode 100644 index 00000000000..6ff228ff5a6 --- /dev/null +++ b/qa/suites/fs/volumes/tasks/volumes/test/subvolume-snapshots-visibility.yaml @@ -0,0 +1,7 @@ +tasks: + - cephfs_test_runner: + fail_on_skip: false + modules: + - tasks.cephfs.test_volumes.TestSubvolumeSnapshotVisibilityBasic + - tasks.cephfs.test_volumes.TestSubvolumeSnapshotVisibility + - tasks.cephfs.test_volumes.TestSubvolumeSnapshotVisibilityMgr diff --git a/qa/tasks/cephfs/test_volumes.py b/qa/tasks/cephfs/test_volumes.py index a2be0f68a2c..3823b0b250a 100644 --- a/qa/tasks/cephfs/test_volumes.py +++ b/qa/tasks/cephfs/test_volumes.py @@ -32,9 +32,25 @@ class TestVolumesHelper(CephFSTestCase): DEFAULT_FILE_SIZE = 1 # MB DEFAULT_NUMBER_OF_FILES = 1024 + # client side config to respect snapshot visibility flag + SNAPSHOT_VISIBILITY = "client_respect_subvolume_snapshot_visibility" + def _fs_cmd(self, *args): return self.get_ceph_cmd_stdout("fs", *args) + def _fs_cmd_grouped(self, *args): + """ + Wrapper around _fs_cmd that handles optional group + usage by omitting group arguments when empty. + """ + if args[-1] == "": + if args[-2] == "--group-name": + args = args[:-2] + else: + args = args[:-1] + + return self._fs_cmd(*args) + def _raw_cmd(self, *args): return self.get_ceph_cmd_stdout(args) @@ -538,6 +554,34 @@ class TestVolumesHelper(CephFSTestCase): self.assertGreaterEqual(len(volumes_inode_dump["old_inodes"]), 0) self.assertGreaterEqual(len(root_inode_dump["old_inodes"]), 2) + def get_client_snapshot_visibility_flag(self, who: str): + """ + Unset flag client_respect_subvolume_snapshot_visibility + """ + return self.get_ceph_cmd_stdout(f"config get {who} " + f"{self.SNAPSHOT_VISIBILITY}").strip() + + def set_client_snapshot_visbility_flag(self, who: str, value: str): + """ + Set flag client_respect_subvolume_snapshot_visibility + """ + self.config_set(who, self.SNAPSHOT_VISIBILITY, value) + self.assertEqual(self.get_client_snapshot_visibility_flag(who), value) + + def get_subvolume_snapshot_visibility(self, volname: str, subvolume: str, + group: str = ""): + return self._fs_cmd_grouped("subvolume", "snapshot_visibility", "get", + volname, subvolume, group).strip() + + def set_subvolume_snapshot_visibility(self, volname: str, subvolume: str, + value: str, group: str = ""): + bool_map = {"0": "false", "1": "true"} + self._fs_cmd_grouped("subvolume", "snapshot_visibility", "set", + volname, subvolume, value, group) + get_val = self.get_subvolume_snapshot_visibility(volname, subvolume, + group) + self.assertEqual(bool_map.get(get_val), value) + def setUp(self): super(TestVolumesHelper, self).setUp() self.volname = None @@ -9138,6 +9182,988 @@ class TestSubvolumeSnapshotClones(TestVolumesHelper): self._wait_for_trash_empty() +class TestSubvolumeSnapshotVisibilityBasic(TestVolumesHelper): + """ + Some basic testing around the snapshot_visibility flag and it's underlying + vxattr ceph.dir.subvolume.snaps.visible + """ + def test_toggling_snapshotvisibility_vxattr_on_non_subvolume_path(self): + """ + that setfattr/getfattr ceph.dir.subvolume.snaps.visible on a non-subvolume + path is a no-op (an exception allowed for subvol v2 path). + """ + non_subvolume_dir_name = "test_dir" + snapshot_visibility_vxattr = "ceph.dir.subvolume.snaps.visible" + self.mount_a.run_shell_payload(f"mkdir {non_subvolume_dir_name}") + # test setting vxattr + self.mount_a.setfattr(non_subvolume_dir_name, + snapshot_visibility_vxattr, "0") + # test getting vxattr + self.mount_a.getfattr(non_subvolume_dir_name, + snapshot_visibility_vxattr) + self.mount_a.run_shell_payload(f"rmdir {non_subvolume_dir_name}") + + def test_toggling_snapshotvisibility_vxattr_on_subvolume_path(self): + """ + that toggling ceph.dir.subvolume.snaps.visible works on a + subvolume path + """ + subvol_path = "group/subvol1" + snapshot_visibility_vxattr = "ceph.dir.subvolume.snaps.visible" + self.mount_a.run_shell_payload(f"mkdir -p {subvol_path}") + # mark as subvolume + self.mount_a.setfattr(subvol_path, "ceph.dir.subvolume", "1") + # default visibility is true + getval = self.mount_a.getfattr(subvol_path, snapshot_visibility_vxattr) + self.assertEqual(getval.strip(), "1") + # disable visibility + self.mount_a.setfattr(subvol_path, snapshot_visibility_vxattr, "0") + getval = self.mount_a.getfattr(subvol_path, snapshot_visibility_vxattr) + self.assertEqual(getval.strip(), "0") + # enable visibility + self.mount_a.setfattr(subvol_path, snapshot_visibility_vxattr, "1") + getval = self.mount_a.getfattr(subvol_path, snapshot_visibility_vxattr) + self.assertEqual(getval.strip(), "1") + self.mount_a.run_shell_payload(f"rmdir {subvol_path}") + + def test_toggling_snapshot_visibility_flag(self): + """ + test that toggling snapshot_visibility works as intended + """ + subvolume = self._gen_subvol_name() + + self._fs_cmd("subvolume", "create", self.volname, subvolume) + + # default visibility is true + visibility = self.get_subvolume_snapshot_visibility(self.volname, subvolume) + self.assertEqual(visibility, "1") + + self.set_subvolume_snapshot_visibility(self.volname, subvolume, "false") + visibility = self.get_subvolume_snapshot_visibility(self.volname, subvolume) + self.assertEqual(visibility, "0") + + self.set_subvolume_snapshot_visibility(self.volname, subvolume, "true") + visibility = self.get_subvolume_snapshot_visibility(self.volname, subvolume) + self.assertEqual(visibility, "1") + + # cleanup + self._fs_cmd("subvolume", "rm", self.volname, subvolume) + self._wait_for_trash_empty() + + def test_snapshot_visibility_after_failing_mds(self): + """ + test that after setting the snapshot_visibility flag, and failing the MDS, + snapshot_visibility doesn't change + """ + subvolume = self._gen_subvol_name() + + self._fs_cmd("subvolume", "create", self.volname, subvolume) + + # default visibility is true + visibility = self.get_subvolume_snapshot_visibility(self.volname, + subvolume) + self.assertEqual(visibility, "1") + + self.set_subvolume_snapshot_visibility(self.volname, subvolume, "false") + self.fs.fail() + self.fs.set_joinable() + self.fs.wait_for_daemons() + # visibility should be unchanged + visibility = self.get_subvolume_snapshot_visibility(self.volname, + subvolume) + self.assertEqual(visibility, "0") + + def test_snapshot_visibility_sized_subvolume(self): + """ + test that snapshot visibility is respected by subvol root and v2 path + sized subvolumes + """ + assert self.mount_a is not None + self.set_client_snapshot_visbility_flag("client", "true") + subvolume = self._gen_subvol_name() + snapshot = self._gen_subvol_snap_name() + self._fs_cmd("subvolume", "create", self.volname, subvolume, "--size", "1024") + self._fs_cmd("subvolume", "snapshot", "create", self.volname, + subvolume, snapshot) + subvol_v2_path = self._fs_cmd("subvolume", "getpath", self.volname, + subvolume).strip() + subvol_v2_snap_path = f"{self.mount_a.hostfs_mntpt}{subvol_v2_path}/.snap" + subvol_root_path = os.path.dirname(subvol_v2_path) + subvol_root_snap_path = f"{self.mount_a.hostfs_mntpt}{subvol_root_path}/.snap" + # since the snapshot visibility flag is true by default, listing snaps + # should pass + snaps = self.mount_a.get_shell_stdout(f"sudo ls -l {subvol_v2_snap_path}", + omit_sudo=False).strip() + self.assertIn(snapshot, snaps) + snaps = self.mount_a.get_shell_stdout(f"sudo ls -l {subvol_root_snap_path}", + omit_sudo=False).strip() + self.assertIn(snapshot, snaps) + # disable snapshot visibility + self.set_subvolume_snapshot_visibility(self.volname, subvolume, "false") + # should fail for both the paths + with self.assertRaises(CommandFailedError): + self.mount_a.run_shell(f"sudo ls -l {subvol_v2_snap_path}", + omit_sudo=False) + with self.assertRaises(CommandFailedError): + self.mount_a.run_shell(f"sudo ls -l {subvol_root_snap_path}", + omit_sudo=False) + # enable snapshot visibility + self.set_subvolume_snapshot_visibility(self.volname, subvolume, "true") + # should succeed for both the paths + snaps = self.mount_a.get_shell_stdout(f"sudo ls -l {subvol_v2_snap_path}", + omit_sudo=False).strip() + self.assertIn(snapshot, snaps) + snaps = self.mount_a.get_shell_stdout(f"sudo ls -l {subvol_root_snap_path}", + omit_sudo=False).strip() + self.assertIn(snapshot, snaps) + + # cleanup + self._fs_cmd("subvolume", "snapshot", "rm", self.volname, subvolume, + snapshot) + self._fs_cmd("subvolume", "rm", self.volname, subvolume) + # reset to default + self.set_client_snapshot_visbility_flag("client", "false") + self._wait_for_trash_empty() + + +class TestSubvolumeSnapshotVisibility(TestVolumesHelper): + """ + Test accessing or modifying .snap dir of a subvolume path based on client + config client_respect_subvolume_snapshot_visibility and subvolume flag + snapshot_visibility + """ + CLIENTS_REQUIRED = 2 + + def _test_snapshot_visbility_single_client(self, respect_client_config: bool, + grouped: bool): + """ + Test snapshot visibility with/with subvolumegroup with client config + client_respect_subvolume_snapshot_visibility using single client + """ + assert self.mount_a is not None + + if respect_client_config: + self.set_client_snapshot_visbility_flag("client.0", "true") + else: + self.set_client_snapshot_visbility_flag("client.0", "false") + + group = self._gen_subvol_grp_name() if grouped else "" + subvolume = self._gen_subvol_name() + snapshot = self._gen_subvol_snap_name() + + if grouped: + self._fs_cmd_grouped("subvolumegroup", "create", self.volname, + group) + self._fs_cmd_grouped("subvolume", "create", self.volname, subvolume, + group) + self._fs_cmd_grouped("subvolume", "snapshot", "create", self.volname, + subvolume, snapshot, group) + + subvolume_path = self._fs_cmd_grouped("subvolume", "getpath", + self.volname, subvolume, group).strip() + subvolume_snap_path = f"{self.mount_a.hostfs_mntpt}{subvolume_path}/.snap" + + # default visibility is true, `ls -l` should go through no matter what + # the client config client_respect_subvolume_snapshot_visibility is set + snaps = self.mount_a.get_shell_stdout(f"sudo ls -l {subvolume_snap_path}", + omit_sudo=False).strip() + self.assertIn(snapshot, snaps) + + # set visibility to false + self.set_subvolume_snapshot_visibility(self.volname, subvolume, "false", group) + if respect_client_config: + with self.assertRaises(CommandFailedError): + self.mount_a.run_shell(f"sudo ls -l {subvolume_snap_path}", + omit_sudo=False) + else: + snaps = self.mount_a.get_shell_stdout(f"sudo ls -l {subvolume_snap_path}", + omit_sudo=False).strip() + self.assertIn(snapshot, snaps) + + # re-enable visibility + self.set_subvolume_snapshot_visibility(self.volname, subvolume, "true", group) + snaps = self.mount_a.get_shell_stdout(f"sudo ls -l {subvolume_snap_path}", + omit_sudo=False).strip() + self.assertIn(snapshot, snaps) + + # cleanup + self._fs_cmd_grouped("subvolume", "snapshot", "rm", self.volname, + subvolume, snapshot, group) + self._fs_cmd_grouped("subvolume", "rm", self.volname, subvolume, group) + if grouped: + self._fs_cmd_grouped("subvolumegroup", "rm", self.volname, group) + # reset to default + if respect_client_config: + self.set_client_snapshot_visbility_flag("client.0", "false") + self._wait_for_trash_empty() + + def _test_snapshot_visibility_two_clients(self, grouped: bool): + """ + Test snapshot visibility with/without subvolumegroup with client config + client_respect_subvolume_snapshot_visibility using multiple clients + """ + assert self.mount_a is not None + assert self.mount_b is not None + + # let mount_a respect snapshot visibility + self.set_client_snapshot_visbility_flag("client.0", "true") + # let mount_b not respect snapshot visibility + self.set_client_snapshot_visbility_flag("client.1", "false") + + group = self._gen_subvol_grp_name() if grouped else "" + subvolume = self._gen_subvol_name() + snapshot = self._gen_subvol_snap_name() + + if grouped: + self._fs_cmd_grouped("subvolumegroup", "create", self.volname, + group) + self._fs_cmd_grouped("subvolume", "create", self.volname, subvolume, + group) + self._fs_cmd_grouped("subvolume", "snapshot", "create", self.volname, + subvolume, snapshot, group) + + subvolume_path = self._fs_cmd_grouped("subvolume", "getpath", + self.volname, subvolume, group).strip() + subvolume_snap_path_client_0 = f"{self.mount_a.hostfs_mntpt}{subvolume_path}/.snap" + subvolume_snap_path_client_1 = f"{self.mount_b.hostfs_mntpt}{subvolume_path}/.snap" + + # default visibility is true, `ls -l` should go through no matter what + # the client config client_respect_subvolume_snapshot_visibility is set + snaps = self.mount_a.get_shell_stdout(f"sudo ls -l {subvolume_snap_path_client_0}", + omit_sudo=False).strip() + self.assertIn(snapshot, snaps) + snaps = self.mount_a.get_shell_stdout(f"sudo ls -l {subvolume_snap_path_client_1}", + omit_sudo=False).strip() + self.assertIn(snapshot, snaps) + + # set visibility to false + self.set_subvolume_snapshot_visibility(self.volname, subvolume, "false", group) + # for mount_a (client.0), `ls -l` should fail since it respects + # client config + with self.assertRaises(CommandFailedError): + self.mount_a.run_shell(f"sudo ls -l {subvolume_snap_path_client_0}", + omit_sudo=False) + # mount_b (client.0) doesn't respect the client config, hence `ls -l` + # should succeed + snaps = self.mount_a.get_shell_stdout(f"sudo ls -l {subvolume_snap_path_client_1}", + omit_sudo=False).strip() + self.assertIn(snapshot, snaps) + + # re-enable visibility + self.set_subvolume_snapshot_visibility(self.volname, subvolume, "true", group) + # `ls -l` should go through for both the client since subvolume + # visibility is true + snaps = self.mount_a.get_shell_stdout(f"sudo ls -l {subvolume_snap_path_client_0}", + omit_sudo=False).strip() + self.assertIn(snapshot, snaps) + snaps = self.mount_a.get_shell_stdout(f"sudo ls -l {subvolume_snap_path_client_1}", + omit_sudo=False).strip() + self.assertIn(snapshot, snaps) + + # cleanup + self._fs_cmd_grouped("subvolume", "snapshot", "rm", self.volname, + subvolume, snapshot, group) + self._fs_cmd_grouped("subvolume", "rm", self.volname, subvolume, group) + if grouped: + self._fs_cmd_grouped("subvolumegroup", "rm", self.volname, group) + # reset mount_a to default value, skipping mount_b since it's already + # false + self.set_client_snapshot_visbility_flag("client.0", "false") + self._wait_for_trash_empty() + + def _test_modifying_snapdir_single_client(self, respect_client_config: bool, + grouped: bool): + """ + Test mkdir/rmdir in .snap dir of a subvolume path based on client + config client_respect_subvolume_snapshot_visibility and subvolume + flag snapshot_visibility + """ + assert self.mount_a is not None + + if respect_client_config: + self.set_client_snapshot_visbility_flag("client.0", "true") + else: + self.set_client_snapshot_visbility_flag("client.0", "false") + + group = self._gen_subvol_grp_name() if grouped else "" + subvolume = self._gen_subvol_name() + snapshot_default = self._gen_subvol_name() + snapshot = self._gen_subvol_snap_name() + + if grouped: + self._fs_cmd_grouped("subvolumegroup", "create", self.volname, + group) + self._fs_cmd_grouped("subvolume", "create", self.volname, subvolume, + group) + # this snapshot will be used to test removing snapshot if client + # config is true but snapshot_visibility is false + self._fs_cmd_grouped("subvolume", "snapshot", "create", self.volname, + subvolume, snapshot_default, group) + + subvolume_path = self._fs_cmd_grouped("subvolume", "getpath", + self.volname, subvolume, group).strip() + subvolume_path = os.path.dirname(subvolume_path) + subvolume_snap_path = f"{self.mount_a.hostfs_mntpt}{subvolume_path}/.snap" + + # default snapshot_visibility is true, snapshot creation/deletion should work + # irrespective of client config client_respect_subvolume_snapshot_visibility + visibility = self.get_subvolume_snapshot_visibility(self.volname, + subvolume, group) + self.assertEqual(visibility, "1") + self.mount_a.run_shell(f"sudo mkdir {subvolume_snap_path}/{snapshot}", + omit_sudo=False) + self.mount_a.run_shell(f"sudo rmdir {subvolume_snap_path}/{snapshot}", + omit_sudo=False) + + # ensure snapshot_default exists in snap dir + snaps = self.mount_a.get_shell_stdout(f"sudo ls -l {subvolume_snap_path}", + omit_sudo=False).strip() + self.assertIn(snapshot_default, snaps) + # disable snapshot_visibility + self.set_subvolume_snapshot_visibility(self.volname, subvolume, "false", + group) + # should not allow deleting snapshot if the client config is true + # but snapshot_visibility is false + if respect_client_config: + with self.assertRaises(CommandFailedError): + self.mount_a.run_shell(f"sudo rmdir {subvolume_snap_path}/{snapshot_default}", + omit_sudo=False) + else: + self.mount_a.run_shell(f"sudo mkdir {subvolume_snap_path}/{snapshot}", + omit_sudo=False) + self.mount_a.run_shell(f"sudo rmdir {subvolume_snap_path}/{snapshot}", + omit_sudo=False) + + # enable visibility + self.set_subvolume_snapshot_visibility(self.volname, subvolume, "true", + group) + # deletion/creation should work irrespective of the client config + # since subvolume flag snapshot_visibility is true + # re-use snapshot_default to test removal + self.mount_a.run_shell(f"sudo rmdir {subvolume_snap_path}/{snapshot_default}", + omit_sudo=False) + self.mount_a.run_shell(f"sudo mkdir {subvolume_snap_path}/{snapshot_default}", + omit_sudo=False) + + # cleanup + self.mount_a.run_shell(f"sudo rmdir {subvolume_snap_path}/{snapshot_default}", + omit_sudo=False) + self._fs_cmd_grouped("subvolume", "rm", self.volname, subvolume, group) + if grouped: + self._fs_cmd_grouped("subvolumegroup", "rm", self.volname, group) + # reset to default + if respect_client_config: + self.set_client_snapshot_visbility_flag("client.0", "false") + self._wait_for_trash_empty() + + def _test_modifying_snapdir_multiple_client(self, grouped: bool): + """ + Test mkdir/rmdir in .snap dir of a subvolume path based on client + config client_respect_subvolume_snapshot_visibility and subvolume + flag snapshot_visibility + """ + assert self.mount_a is not None + assert self.mount_b is not None + + # let mount_a respect snapshot visibility + self.set_client_snapshot_visbility_flag("client.0", "true") + # let mount_b not respect snapshot visibility + self.set_client_snapshot_visbility_flag("client.1", "false") + + group = self._gen_subvol_grp_name() if grouped else "" + subvolume = self._gen_subvol_name() + snapshot_default = self._gen_subvol_name() + snapshot = self._gen_subvol_snap_name() + + if grouped: + self._fs_cmd_grouped("subvolumegroup", "create", self.volname, + group) + self._fs_cmd_grouped("subvolume", "create", self.volname, subvolume, + group) + # this snapshot will be used to test removing snapshot if client + # config is true but snapshot_visibility is false + self._fs_cmd_grouped("subvolume", "snapshot", "create", self.volname, + subvolume, snapshot_default, group) + + subvolume_path = self._fs_cmd_grouped("subvolume", "getpath", + self.volname, subvolume, group).strip() + subvolume_path = os.path.dirname(subvolume_path) + subvolume_snap_path_mount_a = f"{self.mount_a.hostfs_mntpt}{subvolume_path}/.snap" + subvolume_snap_path_mount_b = f"{self.mount_b.hostfs_mntpt}{subvolume_path}/.snap" + + # default snapshot_visibility is true, snapshot creation/deletion should work + # irrespective of client config client_respect_subvolume_snapshot_visibility + visibility = self.get_subvolume_snapshot_visibility(self.volname, + subvolume, group) + self.assertEqual(visibility, "1") + + # mount_a - client_respect_subvolume_snapshot_visibility true + self.mount_a.run_shell(f"sudo mkdir {subvolume_snap_path_mount_a}/{snapshot}", + omit_sudo=False) + self.mount_a.run_shell(f"sudo rmdir {subvolume_snap_path_mount_a}/{snapshot}", + omit_sudo=False) + # mount_b - client_respect_subvolume_snapshot_visibility false + self.mount_b.run_shell(f"sudo mkdir {subvolume_snap_path_mount_b}/{snapshot}", + omit_sudo=False) + self.mount_b.run_shell(f"sudo rmdir {subvolume_snap_path_mount_b}/{snapshot}", + omit_sudo=False) + + # ensure snapshot_default exists in snap dir + snaps = self.mount_a.get_shell_stdout(f"sudo ls -l {subvolume_snap_path_mount_a}", + omit_sudo=False).strip() + self.assertIn(snapshot_default, snaps) + # disable snapshot_visibility + self.set_subvolume_snapshot_visibility(self.volname, subvolume, "false", + group) + # should not allow deleting snapshot for mount_a + with self.assertRaises(CommandFailedError): + self.mount_a.run_shell(f"sudo rmdir {subvolume_snap_path_mount_a}/{snapshot_default}", + omit_sudo=False) + # mkdir at mount_a should not go through + with self.assertRaises(CommandFailedError): + self.mount_a.run_shell(f"sudo mkdir {subvolume_snap_path_mount_a}/{snapshot}", + omit_sudo=False) + # mount_b is immune, mkdir/rmdir should work + self.mount_b.run_shell(f"sudo mkdir {subvolume_snap_path_mount_b}/{snapshot}", + omit_sudo=False) + self.mount_b.run_shell(f"sudo rmdir {subvolume_snap_path_mount_b}/{snapshot}", + omit_sudo=False) + + # enable visibility + self.set_subvolume_snapshot_visibility(self.volname, subvolume, "true", + group) + # mkdir/rmdir should work irrespective of the client config since + # subvolume flag snapshot_visibility is true + self.mount_a.run_shell(f"sudo rmdir {subvolume_snap_path_mount_a}/{snapshot_default}", + omit_sudo=False) + self.mount_a.run_shell(f"sudo mkdir {subvolume_snap_path_mount_a}/{snapshot_default}", + omit_sudo=False) + self.mount_b.run_shell(f"sudo mkdir {subvolume_snap_path_mount_b}/{snapshot}", + omit_sudo=False) + self.mount_b.run_shell(f"sudo rmdir {subvolume_snap_path_mount_b}/{snapshot}", + omit_sudo=False) + + # cleanup + self.mount_a.run_shell(f"sudo rmdir {subvolume_snap_path_mount_a}/{snapshot_default}", + omit_sudo=False) + self._fs_cmd_grouped("subvolume", "rm", self.volname, subvolume, group) + if grouped: + self._fs_cmd_grouped("subvolumegroup", "rm", self.volname, group) + # reset to default + self.set_client_snapshot_visbility_flag("client.0", "false") + self._wait_for_trash_empty() + + def test_snapshot_visibility_no_respect_client_config_nongrouped_single_client(self): + """ + that if snap dir can be looked up toggling subvolume snapshot_visiblity + when client config client_respect_subvolume_snapshot_visibility is false + for a non-grouped subvolume + """ + self._test_snapshot_visbility_single_client(respect_client_config=False, + grouped=False) + + def test_snapshot_visibility_no_respect_client_config_grouped_single_client(self): + """ + that if snap dir can be looked up toggling subvolume snapshot_visiblity + when client config client_respect_subvolume_snapshot_visibility is false + for a grouped subvolume + """ + self._test_snapshot_visbility_single_client(respect_client_config=False, + grouped=True) + + def test_snapshot_visibility_respect_client_config_nongrouped_single_client(self): + """ + that if snap dir can be looked up toggling subvolume snapshot_visiblity + when client config client_respect_subvolume_snapshot_visibility is true + for a non-grouped subvolume + """ + self._test_snapshot_visbility_single_client(respect_client_config=True, + grouped=False) + + def test_snapshot_visibility_respect_client_config_grouped_single_client(self): + """ + that if snap dir can be looked up toggling subvolume snapshot_visiblity + when client config client_respect_subvolume_snapshot_visibility is true + for a grouped subvolume + """ + self._test_snapshot_visbility_single_client(respect_client_config=True, + grouped=True) + + def test_snapshot_visibility_nongrouped_subvolume_two_clients(self): + """ + that for two clients, one respecting client config and other doesn't, + check if snap dir can be looked up toggling subvolume snapshot_visibility + for non-grouped subvolume + """ + self._test_snapshot_visibility_two_clients(grouped=False) + + def test_snapshot_visibility_grouped_subvolume_two_clients(self): + """ + that for two clients, one respecting client config and other doesn't, + check snap dir can be looked up toggling subvolume snapshot_visibility + for grouped subvolume + """ + self._test_snapshot_visibility_two_clients(grouped=True) + + def test_modify_snapdir_no_respect_client_config_nongrouped_single_client(self): + """ + that using a single client mount, mkdir/rmdir works as per subvolume + snapshot_visibility and not respecting client config + client_respect_subvolume_snapshot_visibility for a non-grouped subvolume + """ + self._test_modifying_snapdir_single_client(respect_client_config=False, + grouped=False) + + def test_modify_snapdir_no_respect_client_config_grouped_single_client(self): + """ + that using a single client mount, mkdir/rmdir works as per subvolume + snapshot_visibility and not respecting client config + client_respect_subvolume_snapshot_visibility for a grouped subvolume + """ + self._test_modifying_snapdir_single_client(respect_client_config=False, + grouped=True) + + def test_modify_snapdir_respect_client_config_nongrouped_single_client(self): + """ + that using a single client mount, mkdir/rmdir works as per subvolume + snapshot_visibility respecting client config + client_respect_subvolume_snapshot_visibility for a non-grouped subvolume + """ + self._test_modifying_snapdir_single_client(respect_client_config=True, + grouped=False) + + def test_modify_snapdir_respect_client_config_grouped_single_client(self): + """ + that using a single client mount, mkdir/rmdir works as per subvolume + snapshot_visibility respecting client config + client_respect_subvolume_snapshot_visibility for a grouped subvolume + """ + self._test_modifying_snapdir_single_client(respect_client_config=True, + grouped=True) + + def test_modify_snapdir_nongrouped_two_clients(self): + """ + that using two client mounts, one respecting client config while not + the other, mkdir/rmdir works as per subvolume snapshot_visibility + flag for a non-grouped subvolume + """ + self._test_modifying_snapdir_multiple_client(grouped=False) + + def test_modify_snapdir_grouped_two_clients(self): + """ + that using two client mounts, one respecting client config while not + the other, mkdir/rmdir works as per subvolume snapshot_visibility + flag for a non-grouped subvolume + """ + self._test_modifying_snapdir_multiple_client(grouped=True) + + +class TestSubvolumeSnapshotVisibilityMgr(TestVolumesHelper): + """ + ceph-mgr is a privileged CephFS client, subvolume APIs should not be + impacted by the subvolume flag snapshot_visibility and client config + client_respect_subvolume_snapshot_visibility. + """ + CLIENTS_REQUIRED = 1 + def _test_snapshot_visibility(self, respect_client_config: bool, + grouped: bool): + """ + Test snapshot visibility with/with subvolumegroup with client config + client_respect_subvolume_snapshot_visibility set/unset. + """ + if respect_client_config: + self.set_client_snapshot_visbility_flag("mgr", "true") + else: + self.set_client_snapshot_visbility_flag("mgr", "false") + + group = "" + if grouped: + group = self._gen_subvol_grp_name() + subvolume = self._gen_subvol_name() + snapshot = self._gen_subvol_snap_name() + + if grouped: + self._fs_cmd_grouped("subvolumegroup", "create", self.volname, group) + self._fs_cmd_grouped("subvolume", "create", self.volname, subvolume, group) + self._fs_cmd_grouped("subvolume", "snapshot", "create", self.volname, + subvolume, snapshot, group) + + # ensure visibility + visibility = self.get_subvolume_snapshot_visibility(self.volname, + subvolume, group) + self.assertEqual(visibility, "1") + snapshotls = json.loads(self._fs_cmd_grouped("subvolume", "snapshot", + "ls", self.volname, + subvolume, group)) + self.assertEqual(snapshotls[0]['name'], snapshot) + + # disable visibility + self.set_subvolume_snapshot_visibility(self.volname, subvolume, + "false", group) + # ceph-mgr runs as a privileged CephFS client and is therefore exempt + # from snapshot visibility restrictions. Snapshot listing is expected + # to succeed. + snapshotls = json.loads(self._fs_cmd_grouped("subvolume", "snapshot", + "ls", self.volname, + subvolume, group)) + self.assertEqual(snapshotls[0]['name'], snapshot) + + # enable visilibity + self.set_subvolume_snapshot_visibility(self.volname, subvolume, + "true", group) + snapshotls = json.loads(self._fs_cmd_grouped("subvolume", "snapshot", + "ls", self.volname, + subvolume, group)) + self.assertEqual(snapshotls[0]['name'], snapshot) + + # cleanup + self._fs_cmd_grouped("subvolume", "snapshot", "rm", self.volname, + subvolume, snapshot, group) + self._fs_cmd_grouped("subvolume", "rm", self.volname, subvolume, group) + if grouped: + self._fs_cmd_grouped("subvolumegroup", "rm", self.volname, group) + # reset to default + if respect_client_config: + self.set_client_snapshot_visbility_flag("mgr", "false") + self._wait_for_trash_empty() + + def _test_snapshot_create(self, respect_client_config: bool, + grouped: bool): + """ + Test that subvolume creation is unhindered by the client config + client_respect_subvolume_snapshot_visibility and subvolume flag + snapshot_visibility. + """ + if respect_client_config: + self.set_client_snapshot_visbility_flag("mgr", "true") + else: + self.set_client_snapshot_visbility_flag("mgr", "false") + + group = "" + if grouped: + group = self._gen_subvol_grp_name() + subvolume = self._gen_subvol_name() + snapshot = self._gen_subvol_snap_name() + snapshot1 = self._gen_subvol_snap_name() + + if grouped: + self._fs_cmd_grouped("subvolumegroup", "create", self.volname, group) + self._fs_cmd_grouped("subvolume", "create", self.volname, subvolume, group) + + # disable snapshot visibility + self.set_subvolume_snapshot_visibility(self.volname, subvolume, + "false", group) + + # snapshot creation should succeed since ceph-mgr, as a privileged + # CephFS client, is exempt from the config value. + self._fs_cmd_grouped("subvolume", "snapshot", "create", + self.volname, subvolume, snapshot, group) + + # enable snapshot visibility + self.set_subvolume_snapshot_visibility(self.volname, subvolume, + "true", group) + # should go through + self._fs_cmd_grouped("subvolume", "snapshot", "create", + self.volname, subvolume, snapshot1, group) + + # cleanup + self._fs_cmd_grouped("subvolume", "snapshot", "rm", self.volname, + subvolume, snapshot, group) + self._fs_cmd_grouped("subvolume", "snapshot", "rm", self.volname, + subvolume, snapshot1, group) + self._fs_cmd_grouped("subvolume", "rm", self.volname, subvolume, group) + if grouped: + self._fs_cmd_grouped("subvolumegroup", "rm", self.volname, group) + # reset to default + if respect_client_config: + self.set_client_snapshot_visbility_flag("mgr", "false") + self._wait_for_trash_empty() + + def _test_snapshot_rm(self, respect_client_config: bool, grouped: bool): + """ + Test that subvolume removal is unhindered by the client config + client_respect_subvolume_snapshot_visibility and subvolume flag + snapshot_visibility. + """ + if respect_client_config: + self.set_client_snapshot_visbility_flag("mgr", "true") + else: + self.set_client_snapshot_visbility_flag("mgr", "false") + + group = "" + if grouped: + group = self._gen_subvol_grp_name() + subvolume = self._gen_subvol_name() + snapshot = self._gen_subvol_snap_name() + + if grouped: + self._fs_cmd_grouped("subvolumegroup", "create", self.volname, group) + self._fs_cmd_grouped("subvolume", "create", self.volname, subvolume, group) + self._fs_cmd_grouped("subvolume", "snapshot", "create", self.volname, + subvolume, snapshot, group) + + # disable snapshot visibility + self.set_subvolume_snapshot_visibility(self.volname, subvolume, + "false", group) + # snapshot removal should succeed since ceph-mgr, as a privileged + # client, does not honor the config value. + self._fs_cmd_grouped("subvolume", "snapshot", "rm", + self.volname, subvolume, snapshot, group) + + # enable snapshot visibility + self.set_subvolume_snapshot_visibility(self.volname, subvolume, + "true", group) + + # add a new snapshot and try removing it + snapshot1 = self._gen_subvol_snap_name() + self._fs_cmd_grouped("subvolume", "snapshot", "create", + self.volname, subvolume, snapshot1, group) + self._fs_cmd_grouped("subvolume", "snapshot", "rm", + self.volname, subvolume, snapshot1, group) + + # cleanup + self._fs_cmd_grouped("subvolume", "rm", self.volname, subvolume, group) + if grouped: + self._fs_cmd_grouped("subvolumegroup", "rm", self.volname, group) + # reset to default + if respect_client_config: + self.set_client_snapshot_visbility_flag("mgr", "false") + self._wait_for_trash_empty() + + def _test_snapshot_clone(self, respect_client_config: bool, + with_group: bool): + """ + Test cloning subvolume snapshot with/without subvolumegroup with + client config client_respect_subvolume_snapshot_visibility + """ + if respect_client_config: + self.set_client_snapshot_visbility_flag("mgr", "true") + else: + self.set_client_snapshot_visbility_flag("mgr", "false") + + group = self._gen_subvol_grp_name() if with_group else "" + subvolume = self._gen_subvol_name() + subvolume_clone_1 = self._gen_subvol_name() + subvolume_clone_2 = self._gen_subvol_name() + subvolume_clone_3 = self._gen_subvol_name() + snapshot = self._gen_subvol_snap_name() + if with_group: + self._fs_cmd_grouped("subvolumegroup", "create", self.volname, + group) + self._fs_cmd_grouped("subvolume", "create", self.volname, subvolume, + group) + self._fs_cmd_grouped("subvolume", "snapshot", "create", self.volname, + subvolume, snapshot, group) + + # ensure visibility + visibility = self.get_subvolume_snapshot_visibility(self.volname, + subvolume, group) + self.assertEqual(visibility, "1") + # ensure clone succeeds + self._fs_cmd_grouped("subvolume", "snapshot", "clone", self.volname, + subvolume, snapshot, subvolume_clone_1, + "--group-name", group) + + subvolume_ls = self._fs_cmd_grouped("subvolume", "ls", self.volname) + self.assertIn(subvolume_clone_1, subvolume_ls) + time.sleep(2) + + # disable visibility + self.set_subvolume_snapshot_visibility(self.volname, subvolume, + "false", group) + # clone should still succeed + self._fs_cmd_grouped("subvolume", "snapshot", "clone", self.volname, + subvolume, snapshot, subvolume_clone_2, + "--group-name", group) + + subvolume_ls = self._fs_cmd_grouped("subvolume", "ls", self.volname) + self.assertIn(subvolume_clone_2, subvolume_ls) + + # enable visilibity + self.set_subvolume_snapshot_visibility(self.volname, subvolume, + "true", group) + # clone should succeed + self._fs_cmd_grouped("subvolume", "snapshot", "clone", self.volname, + subvolume, snapshot, subvolume_clone_3, + "--group-name", group) + + subvolume_ls = self._fs_cmd_grouped("subvolume", "ls", self.volname) + self.assertIn(subvolume_clone_3, subvolume_ls) + + # cleanup + self._fs_cmd_grouped("subvolume", "rm", self.volname, + subvolume_clone_1) + self._fs_cmd_grouped("subvolume", "rm", self.volname, + subvolume_clone_2) + self._fs_cmd_grouped("subvolume", "rm", self.volname, + subvolume_clone_3) + self._fs_cmd_grouped("subvolume", "snapshot", "rm", self.volname, + subvolume, snapshot, group) + self._fs_cmd_grouped("subvolume", "rm", self.volname, subvolume, group) + if with_group: + self._fs_cmd_grouped("subvolumegroup", "rm", self.volname, group) + # reset to default + if respect_client_config: + self.set_client_snapshot_visbility_flag("mgr", "false") + self._wait_for_trash_empty() + + def test_snapshot_visibility_nogroup_no_respect_client_config(self): + """ + that flag snapshot_visibility has no effect on a nongroup subvolume + if client config client_respect_subvolume_snapshot_visibility is false. + """ + self._test_snapshot_visibility(respect_client_config=False, + grouped=False) + + def test_snapshot_visibility_nogroup_respect_client_config(self): + """ + that flag snapshot_visibility has no effect on a nongroup subvolume + if client config client_respect_subvolume_snapshot_visibility is true. + """ + self._test_snapshot_visibility(respect_client_config=True, + grouped=False) + + def test_snapshot_visibility_grouped_no_respect_client_config(self): + """ + that flag snapshot_visibility has no effect on a grouped subvolume + if client config client_respect_subvolume_snapshot_visibility is false + """ + self._test_snapshot_visibility(respect_client_config=False, + grouped=True) + + def test_snapshot_visibility_grouped_respect_client_config(self): + """ + that flag snapshot_visibility has no effect on a grouped subvolume + if client config client_respect_subvolume_snapshot_visibility is true + """ + self._test_snapshot_visibility(respect_client_config=True, + grouped=True) + + def test_snapshot_create_nogroup_no_respect_client_config(self): + """ + that snapshot creation works if client config + client_respect_subvolume_snapshot_visibility is false for a + non-grouped subvolume. + """ + self._test_snapshot_create(respect_client_config=False, grouped=False) + + def test_snapshot_create_nogroup_respect_client_config(self): + """ + that snapshot creation works if client config + client_respect_subvolume_snapshot_visibility is true for a + non-grouped subvolume. + """ + self._test_snapshot_create(respect_client_config=True, grouped=False) + + def test_snapshot_create_group_no_respect_client_config(self): + """ + that snapshot creation works if client config + client_respect_subvolume_snapshot_visibility is false for a grouped + subvolume. + """ + self._test_snapshot_create(respect_client_config=False, grouped=True) + + def test_snapshot_create_group_respect_client_config(self): + """ + that snapshot creation works if client config + client_respect_subvolume_snapshot_visibility is true for a grouped + subvolume. + """ + self._test_snapshot_create(respect_client_config=True, grouped=True) + + def test_snapshot_rm_nogroup_no_respect_client_config(self): + """ + that snapshot rm works if client config + client_respect_subvolume_snapshot_visibility is false for a non-grouped + subvolume. + """ + self._test_snapshot_rm(respect_client_config=False, grouped=False) + + def test_snapshot_rm_nogroup_respect_client_config(self): + """ + that snapshot rm works if client config + client_respect_subvolume_snapshot_visibility is true for a non-grouped + subvolume. + """ + self._test_snapshot_rm(respect_client_config=True, grouped=False) + + def test_snapshot_rm_group_no_respect_client_config(self): + """ + that snapshot rm works if client config + client_respect_subvolume_snapshot_visibility is false for a grouped + subvolume. + """ + self._test_snapshot_rm(respect_client_config=False, grouped=True) + + def test_snapshot_rm_group_respect_client_config(self): + """ + that snapshot rm works if client config + client_respect_subvolume_snapshot_visibility is true for a grouped + subvolume. + """ + self._test_snapshot_rm(respect_client_config=True, grouped=True) + + def test_clones_nogroup_no_respect_client_config(self): + """ + that toggling snapshot visibility doesn't prevent clones creation + for a non-group subvolume with client + config client_respect_subvolume_snapshot_visibility set to false. + """ + self._test_snapshot_clone(respect_client_config=False, + with_group=False) + + def test_clones_nogroup_respect_client_config(self): + """ + that toggling snapshot visibility doesn't prevent clones creation + for a non-group subvolume with client + config client_respect_subvolume_snapshot_visibility set to true. + """ + self._test_snapshot_clone(respect_client_config=True, + with_group=False) + + def test_clones_group_no_respect_client_config(self): + """ + that toggling snapshot visibility doesn't prevent clones creation + for a grouped subvolume with client + config client_respect_subvolume_snapshot_visibility set to false. + """ + self._test_snapshot_clone(respect_client_config=False, + with_group=True) + + def test_clones_group_respect_client_config(self): + """ + that toggling snapshot visibility doesn't prevent clones creation + for a grouped subvolume with client + config client_respect_subvolume_snapshot_visibility set to true. + """ + self._test_snapshot_clone(respect_client_config=True, + with_group=True) + + def test_mgr_ops_with_global_client_config_set(self): + """ + that if the config client_respect_subvolume_snapshot_visibility is set + for all clients, it doesn't impact ceph-mgr's client and hence the + snapshot operation are not impacted. + """ + + self.set_client_snapshot_visbility_flag("client", "true") + + # keeping respect_client_config=False i.e. the default behaviour of + # ceph-mgr's client. + self._test_snapshot_visibility(respect_client_config=False, + grouped=False) + + self._test_snapshot_create(respect_client_config=False, grouped=False) + + self._test_snapshot_rm(respect_client_config=False, grouped=False) + + self._test_snapshot_clone(respect_client_config=False, + with_group=False) + + # reset to default + self.set_client_snapshot_visbility_flag("client", "false") + + class TestMisc(TestVolumesHelper): """Miscellaneous tests related to FS volume, subvolume group, and subvolume operations.""" def test_connection_expiration(self): -- 2.39.5