| import logging |
| import re |
| import os |
| import subprocess |
| import time |
| |
| from avocado import skipUnless |
| from avocado_qemu import Test, BUILD_DIR |
| from avocado_qemu import wait_for_console_pattern |
| from avocado.utils import ssh |
| |
| from qemu.accel import kvm_available |
| |
| from boot_linux import BootLinux |
| |
| |
| def run_cmd(args): |
| subp = subprocess.Popen(args, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE, |
| universal_newlines=True) |
| stdout, stderr = subp.communicate() |
| ret = subp.returncode |
| |
| return (stdout, stderr, ret) |
| |
| def has_cmd(name, args=None): |
| """ |
| This function is for use in a @avocado.skipUnless decorator, e.g.: |
| |
| @skipUnless(*has_cmd('sudo -n', ('sudo', '-n', 'true'))) |
| def test_something_that_needs_sudo(self): |
| ... |
| """ |
| |
| if args is None: |
| args = ('which', name) |
| |
| try: |
| _, stderr, exitcode = run_cmd(args) |
| except Exception as e: |
| exitcode = -1 |
| stderr = str(e) |
| |
| if exitcode != 0: |
| cmd_line = ' '.join(args) |
| err = f'{name} required, but "{cmd_line}" failed: {stderr.strip()}' |
| return (False, err) |
| else: |
| return (True, '') |
| |
| def has_cmds(*cmds): |
| """ |
| This function is for use in a @avocado.skipUnless decorator and |
| allows checking for the availability of multiple commands, e.g.: |
| |
| @skipUnless(*has_cmds(('cmd1', ('cmd1', '--some-parameter')), |
| 'cmd2', 'cmd3')) |
| def test_something_that_needs_cmd1_and_cmd2(self): |
| ... |
| """ |
| |
| for cmd in cmds: |
| if isinstance(cmd, str): |
| cmd = (cmd,) |
| |
| ok, errstr = has_cmd(*cmd) |
| if not ok: |
| return (False, errstr) |
| |
| return (True, '') |
| |
| |
| class VirtiofsSubmountsTest(BootLinux): |
| """ |
| :avocado: tags=arch:x86_64 |
| """ |
| |
| def get_portfwd(self): |
| port = None |
| |
| res = self.vm.command('human-monitor-command', |
| command_line='info usernet') |
| for line in res.split('\r\n'): |
| match = \ |
| re.search(r'TCP.HOST_FORWARD.*127\.0\.0\.1\s*(\d+)\s+10\.', |
| line) |
| if match is not None: |
| port = match[1] |
| break |
| |
| self.assertIsNotNone(port) |
| self.log.debug('sshd listening on port: ' + port) |
| return port |
| |
| def ssh_connect(self, username, keyfile): |
| self.ssh_logger = logging.getLogger('ssh') |
| port = self.get_portfwd() |
| self.ssh_session = ssh.Session('127.0.0.1', port=int(port), |
| user=username, key=keyfile) |
| for i in range(10): |
| try: |
| self.ssh_session.connect() |
| return |
| except: |
| time.sleep(4) |
| pass |
| self.fail('sshd timeout') |
| |
| def ssh_command(self, command): |
| self.ssh_logger.info(command) |
| result = self.ssh_session.cmd(command) |
| stdout_lines = [line.rstrip() for line |
| in result.stdout_text.splitlines()] |
| for line in stdout_lines: |
| self.ssh_logger.info(line) |
| stderr_lines = [line.rstrip() for line |
| in result.stderr_text.splitlines()] |
| for line in stderr_lines: |
| self.ssh_logger.warning(line) |
| |
| self.assertEqual(result.exit_status, 0, |
| f'Guest command failed: {command}') |
| return stdout_lines, stderr_lines |
| |
| def run(self, args, ignore_error=False): |
| stdout, stderr, ret = run_cmd(args) |
| |
| if ret != 0: |
| cmdline = ' '.join(args) |
| if not ignore_error: |
| self.fail(f'{cmdline}: Returned {ret}: {stderr}') |
| else: |
| self.log.warn(f'{cmdline}: Returned {ret}: {stderr}') |
| |
| return (stdout, stderr, ret) |
| |
| def set_up_shared_dir(self): |
| atwd = os.getenv('AVOCADO_TEST_WORKDIR') |
| self.shared_dir = os.path.join(atwd, 'virtiofs-shared') |
| |
| os.mkdir(self.shared_dir) |
| |
| self.run(('cp', self.get_data('guest.sh'), |
| os.path.join(self.shared_dir, 'check.sh'))) |
| |
| self.run(('cp', self.get_data('guest-cleanup.sh'), |
| os.path.join(self.shared_dir, 'cleanup.sh'))) |
| |
| def set_up_virtiofs(self): |
| attmp = os.getenv('AVOCADO_TESTS_COMMON_TMPDIR') |
| self.vfsdsock = os.path.join(attmp, 'vfsdsock') |
| |
| self.run(('sudo', '-n', 'rm', '-f', self.vfsdsock), ignore_error=True) |
| |
| self.virtiofsd = \ |
| subprocess.Popen(('sudo', '-n', |
| 'tools/virtiofsd/virtiofsd', |
| f'--socket-path={self.vfsdsock}', |
| '-o', f'source={self.shared_dir}', |
| '-o', 'cache=always', |
| '-o', 'xattr', |
| '-o', 'announce_submounts', |
| '-f'), |
| stdout=subprocess.DEVNULL, |
| stderr=subprocess.PIPE, |
| universal_newlines=True) |
| |
| while not os.path.exists(self.vfsdsock): |
| if self.virtiofsd.poll() is not None: |
| self.fail('virtiofsd exited prematurely: ' + |
| self.virtiofsd.communicate()[1]) |
| time.sleep(0.1) |
| |
| self.run(('sudo', '-n', 'chmod', 'go+rw', self.vfsdsock)) |
| |
| self.vm.add_args('-chardev', |
| f'socket,id=vfsdsock,path={self.vfsdsock}', |
| '-device', |
| 'vhost-user-fs-pci,queue-size=1024,chardev=vfsdsock' \ |
| ',tag=host', |
| '-object', |
| 'memory-backend-file,id=mem,size=1G,' \ |
| 'mem-path=/dev/shm,share=on', |
| '-numa', |
| 'node,memdev=mem') |
| |
| def launch_vm(self): |
| self.launch_and_wait() |
| self.ssh_connect('root', self.ssh_key) |
| |
| def set_up_nested_mounts(self): |
| scratch_dir = os.path.join(self.shared_dir, 'scratch') |
| try: |
| os.mkdir(scratch_dir) |
| except FileExistsError: |
| pass |
| |
| args = ['bash', self.get_data('host.sh'), scratch_dir] |
| if self.seed: |
| args += [self.seed] |
| |
| out, _, _ = self.run(args) |
| seed = re.search(r'^Seed: \d+', out) |
| self.log.info(seed[0]) |
| |
| def mount_in_guest(self): |
| self.ssh_command('mkdir -p /mnt/host') |
| self.ssh_command('mount -t virtiofs host /mnt/host') |
| |
| def check_in_guest(self): |
| self.ssh_command('bash /mnt/host/check.sh /mnt/host/scratch/share') |
| |
| def live_cleanup(self): |
| self.ssh_command('bash /mnt/host/cleanup.sh /mnt/host/scratch') |
| |
| # It would be nice if the above was sufficient to make virtiofsd clear |
| # all references to the mounted directories (so they can be unmounted |
| # on the host), but unfortunately it is not. To do so, we have to |
| # resort to a remount. |
| self.ssh_command('mount -o remount /mnt/host') |
| |
| scratch_dir = os.path.join(self.shared_dir, 'scratch') |
| self.run(('bash', self.get_data('cleanup.sh'), scratch_dir)) |
| |
| @skipUnless(*has_cmds(('sudo -n', ('sudo', '-n', 'true')), |
| 'ssh-keygen', 'bash', 'losetup', 'mkfs.xfs', 'mount')) |
| def setUp(self): |
| vmlinuz = self.params.get('vmlinuz') |
| if vmlinuz is None: |
| self.cancel('vmlinuz parameter not set; you must point it to a ' |
| 'Linux kernel binary to test (to run this test with ' \ |
| 'the on-image kernel, set it to an empty string)') |
| |
| self.seed = self.params.get('seed') |
| |
| atwd = os.getenv('AVOCADO_TEST_WORKDIR') |
| self.ssh_key = os.path.join(atwd, 'id_ed25519') |
| |
| self.run(('ssh-keygen', '-t', 'ed25519', '-f', self.ssh_key)) |
| |
| pubkey = open(self.ssh_key + '.pub').read() |
| |
| super(VirtiofsSubmountsTest, self).setUp(pubkey) |
| |
| if len(vmlinuz) > 0: |
| self.vm.add_args('-kernel', vmlinuz, |
| '-append', 'console=ttyS0 root=/dev/sda1') |
| |
| # Allow us to connect to SSH |
| self.vm.add_args('-netdev', 'user,id=vnet,hostfwd=:127.0.0.1:0-:22', |
| '-device', 'e1000,netdev=vnet') |
| |
| if not kvm_available(self.arch, self.qemu_bin): |
| self.cancel(KVM_NOT_AVAILABLE) |
| self.vm.add_args('-accel', 'kvm') |
| |
| def tearDown(self): |
| try: |
| self.vm.shutdown() |
| except: |
| pass |
| |
| scratch_dir = os.path.join(self.shared_dir, 'scratch') |
| self.run(('bash', self.get_data('cleanup.sh'), scratch_dir), |
| ignore_error=True) |
| |
| def test_pre_virtiofsd_set_up(self): |
| self.set_up_shared_dir() |
| |
| self.set_up_nested_mounts() |
| |
| self.set_up_virtiofs() |
| self.launch_vm() |
| self.mount_in_guest() |
| self.check_in_guest() |
| |
| def test_pre_launch_set_up(self): |
| self.set_up_shared_dir() |
| self.set_up_virtiofs() |
| |
| self.set_up_nested_mounts() |
| |
| self.launch_vm() |
| self.mount_in_guest() |
| self.check_in_guest() |
| |
| def test_post_launch_set_up(self): |
| self.set_up_shared_dir() |
| self.set_up_virtiofs() |
| self.launch_vm() |
| |
| self.set_up_nested_mounts() |
| |
| self.mount_in_guest() |
| self.check_in_guest() |
| |
| def test_post_mount_set_up(self): |
| self.set_up_shared_dir() |
| self.set_up_virtiofs() |
| self.launch_vm() |
| self.mount_in_guest() |
| |
| self.set_up_nested_mounts() |
| |
| self.check_in_guest() |
| |
| def test_two_runs(self): |
| self.set_up_shared_dir() |
| |
| self.set_up_nested_mounts() |
| |
| self.set_up_virtiofs() |
| self.launch_vm() |
| self.mount_in_guest() |
| self.check_in_guest() |
| |
| self.live_cleanup() |
| self.set_up_nested_mounts() |
| |
| self.check_in_guest() |