diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-08-21 02:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-08-21 02:27:49 +0000 |
commit | 7a34f7a30b4a4e8efeac9ba420466271ccbd41ef (patch) | |
tree | 9dd853fa51de7b429a5e7aa64a2f640d30506378 /qa/tasks | |
parent | Releasing progress-linux version 18.2.3-0progress7.99u1. (diff) | |
download | ceph-7a34f7a30b4a4e8efeac9ba420466271ccbd41ef.tar.xz ceph-7a34f7a30b4a4e8efeac9ba420466271ccbd41ef.zip |
Merging upstream version 18.2.4.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | qa/tasks/ceph_fuse.py | 67 | ||||
-rw-r--r-- | qa/tasks/cephfs/mount.py | 41 | ||||
-rw-r--r-- | qa/tasks/cephfs/test_admin.py | 86 | ||||
-rw-r--r-- | qa/tasks/nvme_loop.py | 30 | ||||
-rw-r--r-- | qa/tasks/qemu.py | 7 |
5 files changed, 201 insertions, 30 deletions
diff --git a/qa/tasks/ceph_fuse.py b/qa/tasks/ceph_fuse.py index 70cf9bf83..706bdd977 100644 --- a/qa/tasks/ceph_fuse.py +++ b/qa/tasks/ceph_fuse.py @@ -4,6 +4,7 @@ Ceph FUSE client task import contextlib import logging +import re from teuthology import misc from tasks.cephfs.fuse_mount import FuseMount @@ -86,6 +87,32 @@ def task(ctx, config): client.1: mount_subvol_num: 1 + Example for client mount with custom client feature set + + tasks: + - ceph: + - ceph-fuse: + client.0: + client_feature_range: 21 # everything including CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK + + OR + + tasks: + - ceph: + - ceph-fuse: + client.0: + client_feature_range: "[0-13],[15-21]" # all features except metric_collect (bit 14) + + OR + + tasks: + - ceph: + - ceph-fuse: + client.0: + client_feature_range: "[0-13],16,19,[20-21]" # all features except metric_collect,alternate_name, op_getvxattr, 32bit_retry_fwd + + client_feature_range can have repetitive and overlapping ranges/values - the parsed feature bits would not have duplicates and is sorted. Decreasing ranges are silently ignored. + :param ctx: Context :param config: Configuration """ @@ -161,12 +188,50 @@ def task(ctx, config): for remote in remotes: FuseMount.cleanup_stale_netnses_and_bridge(remote) + def parse_client_feature_range(client_feature_range): + def intify(val): + try: + return int(val) + except ValueError: + log.warn(f'failed to decode feature bit {val}') + raise + feature_bits = [] + pvalue = re.compile(r'(\d+)') + prange = re.compile(r'\[(\d+)\-(\d+)\]') + if (isinstance(client_feature_range, int)): + # everything upto (and including) this feature bit + feature_bits.extend(range(0, client_feature_range+1)) + elif isinstance(client_feature_range, str): + for feat in client_feature_range.split(','): + m = pvalue.match(feat) + if m: + feature_bits.append(intify(m.group(1))) + continue + m = prange.match(feat) + if m: + feature_bits.extend(range(intify(m.group(1)), intify(m.group(2))+1)) + continue + raise ValueError(f'Invalid feature range or value "{feat}"') + else: + raise TypeError("client_feature_range must be of type int or str") + return sorted(set(feature_bits)) + # Mount any clients we have been asked to (default to mount all) log.info('Mounting ceph-fuse clients...') for info in mounted_by_me.values(): config = info["config"] mount_x = info['mount'] - mount_x.mount(mntopts=config.get('mntopts', []), mntargs=config.get('mntargs', [])) + + # apply custom client feature set + client_features = [] + client_feature_range = config.get("client_feature_range", None) + if client_feature_range is not None: + client_features = ",".join(str(i) for i in parse_client_feature_range(client_feature_range)) + mntargs = config.get('mntargs', []) + if client_features: + mntargs.append(f"--client_debug_inject_features={client_features}") + log.debug(f"passing mntargs={mntargs}") + mount_x.mount(mntopts=config.get('mntopts', []), mntargs=mntargs) for info in mounted_by_me.values(): info["mount"].wait_until_mounted() diff --git a/qa/tasks/cephfs/mount.py b/qa/tasks/cephfs/mount.py index bd92cadaa..f995f7c8b 100644 --- a/qa/tasks/cephfs/mount.py +++ b/qa/tasks/cephfs/mount.py @@ -551,30 +551,21 @@ class CephFSMount(object): raise RuntimeError('value of attributes should be either str ' f'or None. {k} - {v}') - def update_attrs(self, client_id=None, client_keyring_path=None, - client_remote=None, hostfs_mntpt=None, cephfs_name=None, - cephfs_mntpt=None): - if not (client_id or client_keyring_path or client_remote or - cephfs_name or cephfs_mntpt or hostfs_mntpt): - return - - self._verify_attrs(client_id=client_id, - client_keyring_path=client_keyring_path, - hostfs_mntpt=hostfs_mntpt, cephfs_name=cephfs_name, - cephfs_mntpt=cephfs_mntpt) - - if client_id: - self.client_id = client_id - if client_keyring_path: - self.client_keyring_path = client_keyring_path - if client_remote: - self.client_remote = client_remote - if hostfs_mntpt: - self.hostfs_mntpt = hostfs_mntpt - if cephfs_name: - self.cephfs_name = cephfs_name - if cephfs_mntpt: - self.cephfs_mntpt = cephfs_mntpt + def update_attrs(self, **kwargs): + verify_keys = [ + 'client_id', + 'client_keyring_path', + 'hostfs_mntpt', + 'cephfs_name', + 'cephfs_mntpt', + ] + + self._verify_attrs(**{key: kwargs[key] for key in verify_keys if key in kwargs}) + + for k in verify_keys: + v = kwargs.get(k) + if v is not None: + setattr(self, k, v) def remount(self, **kwargs): """ @@ -597,7 +588,7 @@ class CephFSMount(object): self.update_attrs(**kwargs) - retval = self.mount(mntopts=mntopts, check_status=check_status) + retval = self.mount(mntopts=mntopts, check_status=check_status, **kwargs) # avoid this scenario (again): mount command might've failed and # check_status might have silenced the exception, yet we attempt to # wait which might lead to an error. diff --git a/qa/tasks/cephfs/test_admin.py b/qa/tasks/cephfs/test_admin.py index 4f3100bbe..db0e5660a 100644 --- a/qa/tasks/cephfs/test_admin.py +++ b/qa/tasks/cephfs/test_admin.py @@ -1319,6 +1319,92 @@ class TestFsAuthorize(CephFSTestCase): self.captester.conduct_neg_test_for_chown_caps() self.captester.conduct_neg_test_for_truncate_caps() + def test_multifs_rootsquash_nofeature(self): + """ + That having root_squash on one fs doesn't prevent access to others. + """ + + if not isinstance(self.mount_a, FuseMount): + self.skipTest("only FUSE client has CEPHFS_FEATURE_MDS_AUTH_CAPS " + "needed to enforce root_squash MDS caps") + + self.fs1 = self.fs + self.fs2 = self.mds_cluster.newfs('testcephfs2') + + self.mount_a.umount_wait() + + self.run_ceph_cmd(f'auth caps client.{self.mount_a.client_id} ' + f'mon "allow r" ' + f'osd "allow rw tag cephfs data={self.fs1.name}, allow rw tag cephfs data={self.fs2.name}" ' + f'mds "allow rwp fsname={self.fs1.name}, allow rw fsname={self.fs2.name} root_squash"') + + CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK = 21 + # all but CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK + features = ",".join([str(i) for i in range(CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK)]) + mntargs = [f"--client_debug_inject_features={features}"] + + # should succeed + with self.assert_cluster_log("report clients with broken root_squash", present=False): + self.mount_a.remount(mntargs=mntargs, cephfs_name=self.fs1.name) + + def test_rootsquash_nofeature(self): + """ + That having root_squash on an fs without the feature bit raises a HEALTH_ERR warning. + """ + + if not isinstance(self.mount_a, FuseMount): + self.skipTest("only FUSE client has CEPHFS_FEATURE_MDS_AUTH_CAPS " + "needed to enforce root_squash MDS caps") + + self.mount_a.umount_wait() + + FS_AUTH_CAPS = (('/', 'rw', 'root_squash'),) + keyring = self.fs.authorize(self.client_id, FS_AUTH_CAPS) + + CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK = 21 + # all but CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK + features = ",".join([str(i) for i in range(CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK)]) + mntargs = [f"--client_debug_inject_features={features}"] + + # should succeed + with self.assert_cluster_log("with broken root_squash implementation"): + keyring_path = self.mount_a.client_remote.mktemp(data=keyring) + self.mount_a.remount(client_id=self.client_id, client_keyring_path=keyring_path, mntargs=mntargs, cephfs_name=self.fs.name) + self.wait_for_health("MDS_CLIENTS_BROKEN_ROOTSQUASH", 60) + self.assertFalse(self.mount_a.is_blocked()) + + self.mount_a.umount_wait() + self.wait_for_health_clear(60) + + def test_rootsquash_nofeature_evict(self): + """ + That having root_squash on an fs without the feature bit can be evicted. + """ + + if not isinstance(self.mount_a, FuseMount): + self.skipTest("only FUSE client has CEPHFS_FEATURE_MDS_AUTH_CAPS " + "needed to enforce root_squash MDS caps") + + self.mount_a.umount_wait() + + FS_AUTH_CAPS = (('/', 'rw', 'root_squash'),) + keyring = self.fs.authorize(self.client_id, FS_AUTH_CAPS) + + CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK = 21 + # all but CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK + features = ",".join([str(i) for i in range(CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK)]) + mntargs = [f"--client_debug_inject_features={features}"] + + # should succeed + keyring_path = self.mount_a.client_remote.mktemp(data=keyring) + self.mount_a.remount(client_id=self.client_id, client_keyring_path=keyring_path, mntargs=mntargs, cephfs_name=self.fs.name) + self.wait_for_health("MDS_CLIENTS_BROKEN_ROOTSQUASH", 60) + + self.fs.required_client_features("add", "client_mds_auth_caps") + self.wait_for_health_clear(60) + self.assertTrue(self.mount_a.is_blocked()) + + def test_single_path_rootsquash_issue_56067(self): """ That a FS client using root squash MDS caps allows non-root user to write data diff --git a/qa/tasks/nvme_loop.py b/qa/tasks/nvme_loop.py index c9d8f0dc7..5b29c11f0 100644 --- a/qa/tasks/nvme_loop.py +++ b/qa/tasks/nvme_loop.py @@ -1,5 +1,6 @@ import contextlib import logging +import json from io import StringIO from teuthology import misc as teuthology @@ -66,10 +67,33 @@ def task(ctx, config): with contextutil.safe_while(sleep=1, tries=15) as proceed: while proceed(): - p = remote.run(args=['sudo', 'nvme', 'list'], stdout=StringIO()) + p = remote.run(args=['sudo', 'nvme', 'list', '-o', 'json'], stdout=StringIO()) new_devs = [] - for line in p.stdout.getvalue().splitlines(): - dev, _, vendor = line.split()[0:3] + # `nvme list -o json` will return the following output: + '''{ + "Devices" : [ + { + "DevicePath" : "/dev/nvme0n1", + "Firmware" : "8DV101H0", + "Index" : 0, + "ModelNumber" : "INTEL SSDPEDMD400G4", + "ProductName" : "Unknown Device", + "SerialNumber" : "PHFT620400WB400BGN" + }, + { + "DevicePath" : "/dev/nvme1n1", + "Firmware" : "5.15.0-1", + "Index" : 1, + "ModelNumber" : "Linux", + "ProductName" : "Unknown Device", + "SerialNumber" : "7672ce414766ba44a8e5" + } + ] + }''' + nvme_list = json.loads(p.stdout.getvalue()) + for device in nvme_list['Devices']: + dev = device['DevicePath'] + vendor = device['ModelNumber'] if dev.startswith('/dev/') and vendor == 'Linux': new_devs.append(dev) log.info(f'new_devs {new_devs}') diff --git a/qa/tasks/qemu.py b/qa/tasks/qemu.py index 6533026b4..3c0f7c3b5 100644 --- a/qa/tasks/qemu.py +++ b/qa/tasks/qemu.py @@ -8,6 +8,8 @@ import os import yaml import time +from packaging.version import Version + from tasks import rbd from tasks.util.workunit import get_refspec_after_overrides from teuthology import contextutil @@ -492,7 +494,10 @@ def run_qemu(ctx, config): ) nfs_service_name = 'nfs' - if remote.os.name in ['rhel', 'centos'] and float(remote.os.version) >= 8: + if ( + remote.os.name in ['rhel', 'centos'] and + Version(remote.os.version.lower().removesuffix(".stream")) >= Version("8") + ): nfs_service_name = 'nfs-server' # make an nfs mount to use for logging and to |