ceph/qa/tasks/cephfs/test_fscrypt.py
Patrick Donnelly ac092f63bf
qa: add test to verify recovery of alternate_name from journal
Test without the fix:

    2024-05-16T22:34:21.781 DEBUG:teuthology.orchestra.run.smithi044:> sudo adjust-ulimits ceph-coverage /home/ubuntu/cephtest/archive/coverage timeout 300 ceph --cluster ceph --admin-daemon /var/run/ceph/ceph-mds.a.asok --format=json dump tree /dir 0
    ...
    [
      {
        "accounted_rstat": {
          "rbytes": 4096,
          "rctime": "2024-05-16T22:34:15.251864+0000",
          "rfiles": 1,
          "rsnaps": 0,
          "rsubdirs": 1,
          "version": 0
        },
        "atime": "2024-05-16T22:34:14.498880+0000",
        "auth_pins": 0,
        "auth_state": {
          "replicas": {}
        },
        "authlock": {},
        "backtrace_version": 14,
        "btime": "2024-05-16T22:34:14.498880+0000",
        "change_attr": 3,
        "client_caps": [
          {
            "client_id": 5184,
            "issued": "pAsLsXsFsx",
            "last_sent": 4,
            "pending": "pAsLsXsFsx",
            "wanted": "pAsLsXsFsxcral"
          }
        ],
        "client_ranges": [],
        "ctime": "2024-05-16T22:34:15.249864+0000",
        "damage_flags": 0,
        "dir_layout": {
          "dir_hash": 2,
          "unused1": 0,
          "unused2": 0,
          "unused3": 0
        },
        "dirfrags": [
          {
            "auth_pins": 0,
            "auth_state": {
              "replicas": {}
            },
            "committed_version": "0",
            "committing_version": "0",
            "dentries": [
              {
                "alternate_name": "bUHUwH9E8uiVaf8xZ+zONcB1CToj53x5aUUnKdnNj5U37zbh28l1AaWwHhbOT3HyzqKjmSKKW1o4odQJc7nF9xrKIB8D3b4qqb2Cs6s7t2106hHhQk5/YV7DtpeNPZnorcTqxPM/hExtWHSS4P+S+Dpwj62hMyh/77sGhiW1Filvv1gQjV+sN/GozPNwHgfleadkUs1OkRkYtgWrCjbKP0MayRtiOLrVTRuYyOp/Qt3+XCIyiS87B9bUcOFjWratF+yR0kpJ0RYriix7NKVkBJ0kGWYSCY+PYjiLeMYJBMQcCxW/nwfVku+m6fgFJvb6pjEFxIk9zT5cunSImsjr",
                "auth_pins": 0,
                "auth_state": {
                  "replicas": {}
                },
                "inode": 1099511628283,
                "is_auth": true,
                "is_freezing": false,
                "is_frozen": false,
                "is_new": false,
                "is_null": false,
                "is_primary": true,
                "is_remote": false,
                "lock": {},
                "nref": 2,
                "path": "dir/bUHUwH9E8uiVaf8xZ+zONcB1CToj53x5aUUnKdnNj5U37zbh28l1AaWwHhbOT3HyzqKjmSKKW1o4odQJc7nF9xrKIB8D3b4qqb2Cs6s7t2106hHhQk5,YV7DtpeNPZnorcTqxPM,hExtWHSS4P+S+Dpwj62hMyh,77sGhiW1Filvv1gQjV+sN,GozPNwHgfleadkUuZ+PMLCaKQXhuid9WvmHanxJnaabYDLj4VEz+EX2WsG",
    ...
    # fail + journal recovery
    2024-05-16T22:35:31.077 DEBUG:teuthology.orchestra.run.smithi044:> sudo adjust-ulimits ceph-coverage /home/ubuntu/cephtest/archive/coverage timeout 300 ceph --cluster ceph --admin-daemon /var/run/ceph/ceph-mds.a.asok --format=json dump tree /dir 0
    ...
    [
      {
        "accounted_rstat": {
          "rbytes": 4096,
          "rctime": "2024-05-16T22:34:15.251864+0000",
          "rfiles": 1,
          "rsnaps": 0,
          "rsubdirs": 1,
          "version": 0
        },
        "atime": "2024-05-16T22:34:14.498880+0000",
        "auth_pins": 0,
        "auth_state": {
          "replicas": {}
        },
        "authlock": {},
        "backtrace_version": 14,
        "btime": "2024-05-16T22:34:14.498880+0000",
        "change_attr": 3,
        "client_caps": [],
        "client_ranges": [],
        "ctime": "2024-05-16T22:34:15.249864+0000",
        "damage_flags": 0,
        "dir_layout": {
          "dir_hash": 2,
          "unused1": 0,
          "unused2": 0,
          "unused3": 0
        },
        "dirfrags": [
          {
            "auth_pins": 0,
            "auth_state": {
              "replicas": {}
            },
            "committed_version": "5",
            "committing_version": "5",
            "dentries": [
              {
                "alternate_name": "",
                "auth_pins": 0,
                "auth_state": {
                  "replicas": {}
                },
                "inode": 1099511628283,
                "is_auth": true,
                "is_freezing": false,
                "is_frozen": false,
                "is_new": false,
                "is_null": false,
                "is_primary": true,
                "is_remote": false,
                "lock": {},
                "nref": 2,
                "path": "dir/bUHUwH9E8uiVaf8xZ+zONcB1CToj53x5aUUnKdnNj5U37zbh28l1AaWwHhbOT3HyzqKjmSKKW1o4odQJc7nF9xrKIB8D3b4qqb2Cs6s7t2106hHhQk5,YV7DtpeNPZnorcTqxPM,hExtWHSS4P+S+Dpwj62hMyh,77sGhiW1Filvv1gQjV+sN,GozPNwHgfleadkUuZ+PMLCaKQXhuid9WvmHanxJnaabYDLj4VEz+EX2WsG",

Signed-off-by: Patrick Donnelly <pdonnell@redhat.com>
2024-06-10 12:56:00 -04:00

143 lines
6.0 KiB
Python

from io import StringIO
from os.path import basename
import random
import string
from logging import getLogger
from tasks.cephfs.cephfs_test_case import CephFSTestCase
from tasks.cephfs.xfstests_dev import XFSTestsDev
log = getLogger(__name__)
class FSCryptTestCase(CephFSTestCase):
CLIENTS_REQUIRED = 1
def setUp(self):
super().setUp()
self.protector = ''.join(random.choice(string.ascii_letters) for _ in range(8))
self.key_file = "/tmp/key"
self.path = "dir/"
self.mount_a.run_shell_payload("sudo fscrypt --help")
self.mount_a.run_shell_payload("sudo fscrypt setup --help")
self.mount_a.run_shell_payload("sudo fscrypt setup --force --quiet")
self.mount_a.run_shell_payload("sudo fscrypt status")
self.mount_a.run_shell_payload(f"sudo fscrypt setup --quiet {self.mount_a.hostfs_mntpt}")
self.mount_a.run_shell_payload("sudo fscrypt status")
self.mount_a.run_shell_payload(f"sudo dd if=/dev/urandom of={self.key_file} bs=32 count=1")
self.mount_a.run_shell_payload(f"mkdir -p {self.path}")
self.mount_a.run_shell_payload(f"sudo fscrypt encrypt --quiet --source=raw_key --name={self.protector} --no-recovery --skip-unlock --key={self.key_file} {self.path}")
self.mount_a.run_shell_payload(f"sudo fscrypt unlock --quiet --key=/tmp/key {self.path}")
def tearDown(self):
self.mount_a.run_shell_payload(f"sudo fscrypt purge --force --quiet {self.mount_a.hostfs_mntpt}")
super().tearDown()
class TestFSCrypt(FSCryptTestCase):
def test_fscrypt_basic_mount(self):
"""
That fscrypt can be setup and ingest files.
"""
self.mount_a.run_shell_payload(f"cp -av /usr/include {self.path}/")
class TestFSCryptRecovery(FSCryptTestCase):
def test_fscrypt_journal_recovery(self):
"""
That alternate_name can be recovered from the journal.
"""
file = ''.join(random.choice(string.ascii_letters) for _ in range(255))
self.mount_a.run_shell_payload(f"cd {self.path} && dd if=/dev/urandom of={file} bs=512 count=1 oflag=sync && sync . && stat {file}")
def verify_alternate_name():
J = self.fs.read_cache("/dir", depth=0)
self.assertEqual(len(J), 1)
inode = J[0]
dirfrags = inode['dirfrags']
self.assertEqual(len(dirfrags), 1)
dirfrag = dirfrags[0]
dentries = dirfrag['dentries']
self.assertEqual(len(dentries), 1)
# we don't know it's encrypted name, so we cannot verify that it's {file}
dentry = dentries[0]
name = basename(dentry['path'])
# https://github.com/ceph/ceph-client/blob/fec50db7033ea478773b159e0e2efb135270e3b7/fs/ceph/crypto.h#L65-L90
self.assertEqual(len(name), 240)
alternate_name = dentry['alternate_name']
self.assertGreater(len(alternate_name), 240)
verify_alternate_name()
self.fs.fail()
self.fs.journal_tool(['event', 'recover_dentries', 'list'], 0)
self.fs.journal_tool(['journal', 'reset', '--yes-i-really-really-mean-it'], 0)
self.fs.set_joinable()
self.fs.wait_for_daemons()
verify_alternate_name()
self.mount_a.run_shell_payload(f"cd {self.path} && find")
self.mount_a.run_shell_payload(f"cd {self.path} && stat {file}")
class TestFSCryptXFS(XFSTestsDev):
def setup_xfsprogs_devs(self):
self.install_xfsprogs = True
def test_fscrypt_encrypt(self):
# XXX: check_status is set to False so that we can check for command's
# failure on our own (since this command doesn't set right error code
# and error message in some cases) and print custom log messages
# accordingly.
proc = self.mount_a.client_remote.run(args=['sudo', 'env', 'DIFF_LENGTH=0',
'./check', '-g', 'encrypt'], cwd=self.xfstests_repo_path, stdout=StringIO(),
stderr=StringIO(), timeout=900, check_status=False, omit_sudo=False,
label='running tests for encrypt from xfstests-dev')
if proc.returncode != 0:
log.info('Command failed.')
log.info(f'Command return value: {proc.returncode}')
stdout, stderr = proc.stdout.getvalue(), proc.stderr.getvalue()
log.info(f'Command stdout -\n{stdout}')
log.info(f'Command stderr -\n{stderr}')
# Currently only the 395,396,397,421,429,435,440,580,593,595 and 598
# of the 26 test cases will be actually ran, all the others will be
# skipped for now because of not supporting features in kernel or kceph.
self.assertEqual(proc.returncode, 0)
self.assertIn('Passed all 26 tests', stdout)
def test_fscrypt_dummy_encryption_with_quick_group(self):
self.write_local_config('test_dummy_encryption')
# XXX: check_status is set to False so that we can check for command's
# failure on our own (since this command doesn't set right error code
# and error message in some cases) and print custom log messages
# accordingly. This will take a long time and set the timeout to 3 hours.
proc = self.mount_a.client_remote.run(args=['sudo', 'env', 'DIFF_LENGTH=0',
'./check', '-g', 'quick', '-E', './ceph.exclude'], cwd=self.xfstests_repo_path,
stdout=StringIO(), stderr=StringIO(), timeout=10800, check_status=False,
omit_sudo=False, label='running tests for dummy_encryption from xfstests-dev')
if proc.returncode != 0:
log.info('Command failed.')
log.info(f'Command return value: {proc.returncode}')
stdout, stderr = proc.stdout.getvalue(), proc.stderr.getvalue()
log.info(f'Command stdout -\n{stdout}')
log.info(f'Command stderr -\n{stderr}')
# Currently, many test cases will be skipped due to unsupported features,
# but still will be marked as successful.
self.assertEqual(proc.returncode, 0)
self.assertIn('Passed all ', stdout)