summaryrefslogtreecommitdiffstats
path: root/qa/tasks
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--qa/tasks/ceph_fuse.py67
-rw-r--r--qa/tasks/cephfs/mount.py41
-rw-r--r--qa/tasks/cephfs/test_admin.py86
-rw-r--r--qa/tasks/nvme_loop.py30
-rw-r--r--qa/tasks/qemu.py7
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