Merge remote-tracking branch 'remotes/mst/tags/for_upstream' into staging
[qemu/ar7.git] / tests / acceptance / virtiofs_submounts.py
blob46fa65392a15890ee68a4ac2e0d0cef17681f203
1 import logging
2 import re
3 import os
4 import subprocess
5 import time
7 from avocado import skipUnless
8 from avocado_qemu import LinuxTest, BUILD_DIR
9 from avocado_qemu import wait_for_console_pattern
10 from avocado.utils import ssh
13 def run_cmd(args):
14     subp = subprocess.Popen(args,
15                             stdout=subprocess.PIPE,
16                             stderr=subprocess.PIPE,
17                             universal_newlines=True)
18     stdout, stderr = subp.communicate()
19     ret = subp.returncode
21     return (stdout, stderr, ret)
23 def has_cmd(name, args=None):
24     """
25     This function is for use in a @avocado.skipUnless decorator, e.g.:
27         @skipUnless(*has_cmd('sudo -n', ('sudo', '-n', 'true')))
28         def test_something_that_needs_sudo(self):
29             ...
30     """
32     if args is None:
33         args = ('which', name)
35     try:
36         _, stderr, exitcode = run_cmd(args)
37     except Exception as e:
38         exitcode = -1
39         stderr = str(e)
41     if exitcode != 0:
42         cmd_line = ' '.join(args)
43         err = f'{name} required, but "{cmd_line}" failed: {stderr.strip()}'
44         return (False, err)
45     else:
46         return (True, '')
48 def has_cmds(*cmds):
49     """
50     This function is for use in a @avocado.skipUnless decorator and
51     allows checking for the availability of multiple commands, e.g.:
53         @skipUnless(*has_cmds(('cmd1', ('cmd1', '--some-parameter')),
54                               'cmd2', 'cmd3'))
55         def test_something_that_needs_cmd1_and_cmd2(self):
56             ...
57     """
59     for cmd in cmds:
60         if isinstance(cmd, str):
61             cmd = (cmd,)
63         ok, errstr = has_cmd(*cmd)
64         if not ok:
65             return (False, errstr)
67     return (True, '')
70 class VirtiofsSubmountsTest(LinuxTest):
71     """
72     :avocado: tags=arch:x86_64
73     """
75     def get_portfwd(self):
76         port = None
78         res = self.vm.command('human-monitor-command',
79                               command_line='info usernet')
80         for line in res.split('\r\n'):
81             match = \
82                 re.search(r'TCP.HOST_FORWARD.*127\.0\.0\.1\s+(\d+)\s+10\.',
83                           line)
84             if match is not None:
85                 port = int(match[1])
86                 break
88         self.assertIsNotNone(port)
89         self.assertGreater(port, 0)
90         self.log.debug('sshd listening on port: %d', port)
91         return port
93     def ssh_connect(self, username, keyfile):
94         self.ssh_logger = logging.getLogger('ssh')
95         port = self.get_portfwd()
96         self.ssh_session = ssh.Session('127.0.0.1', port=port,
97                                        user=username, key=keyfile)
98         for i in range(10):
99             try:
100                 self.ssh_session.connect()
101                 return
102             except:
103                 time.sleep(4)
104                 pass
105         self.fail('ssh connection timeout')
107     def ssh_command(self, command):
108         self.ssh_logger.info(command)
109         result = self.ssh_session.cmd(command)
110         stdout_lines = [line.rstrip() for line
111                         in result.stdout_text.splitlines()]
112         for line in stdout_lines:
113             self.ssh_logger.info(line)
114         stderr_lines = [line.rstrip() for line
115                         in result.stderr_text.splitlines()]
116         for line in stderr_lines:
117             self.ssh_logger.warning(line)
119         self.assertEqual(result.exit_status, 0,
120                          f'Guest command failed: {command}')
121         return stdout_lines, stderr_lines
123     def run(self, args, ignore_error=False):
124         stdout, stderr, ret = run_cmd(args)
126         if ret != 0:
127             cmdline = ' '.join(args)
128             if not ignore_error:
129                 self.fail(f'{cmdline}: Returned {ret}: {stderr}')
130             else:
131                 self.log.warn(f'{cmdline}: Returned {ret}: {stderr}')
133         return (stdout, stderr, ret)
135     def set_up_shared_dir(self):
136         self.shared_dir = os.path.join(self.workdir, 'virtiofs-shared')
138         os.mkdir(self.shared_dir)
140         self.run(('cp', self.get_data('guest.sh'),
141                  os.path.join(self.shared_dir, 'check.sh')))
143         self.run(('cp', self.get_data('guest-cleanup.sh'),
144                  os.path.join(self.shared_dir, 'cleanup.sh')))
146     def set_up_virtiofs(self):
147         attmp = os.getenv('AVOCADO_TESTS_COMMON_TMPDIR')
148         self.vfsdsock = os.path.join(attmp, 'vfsdsock')
150         self.run(('sudo', '-n', 'rm', '-f', self.vfsdsock), ignore_error=True)
152         self.virtiofsd = \
153             subprocess.Popen(('sudo', '-n',
154                               'tools/virtiofsd/virtiofsd',
155                               f'--socket-path={self.vfsdsock}',
156                               '-o', f'source={self.shared_dir}',
157                               '-o', 'cache=always',
158                               '-o', 'xattr',
159                               '-o', 'announce_submounts',
160                               '-f'),
161                              stdout=subprocess.DEVNULL,
162                              stderr=subprocess.PIPE,
163                              universal_newlines=True)
165         while not os.path.exists(self.vfsdsock):
166             if self.virtiofsd.poll() is not None:
167                 self.fail('virtiofsd exited prematurely: ' +
168                           self.virtiofsd.communicate()[1])
169             time.sleep(0.1)
171         self.run(('sudo', '-n', 'chmod', 'go+rw', self.vfsdsock))
173         self.vm.add_args('-chardev',
174                          f'socket,id=vfsdsock,path={self.vfsdsock}',
175                          '-device',
176                          'vhost-user-fs-pci,queue-size=1024,chardev=vfsdsock' \
177                              ',tag=host',
178                          '-object',
179                          'memory-backend-file,id=mem,size=1G,' \
180                              'mem-path=/dev/shm,share=on',
181                          '-numa',
182                          'node,memdev=mem')
184     def launch_vm(self):
185         self.launch_and_wait()
186         self.ssh_connect('root', self.ssh_key)
188     def set_up_nested_mounts(self):
189         scratch_dir = os.path.join(self.shared_dir, 'scratch')
190         try:
191             os.mkdir(scratch_dir)
192         except FileExistsError:
193             pass
195         args = ['bash', self.get_data('host.sh'), scratch_dir]
196         if self.seed:
197             args += [self.seed]
199         out, _, _ = self.run(args)
200         seed = re.search(r'^Seed: \d+', out)
201         self.log.info(seed[0])
203     def mount_in_guest(self):
204         self.ssh_command('mkdir -p /mnt/host')
205         self.ssh_command('mount -t virtiofs host /mnt/host')
207     def check_in_guest(self):
208         self.ssh_command('bash /mnt/host/check.sh /mnt/host/scratch/share')
210     def live_cleanup(self):
211         self.ssh_command('bash /mnt/host/cleanup.sh /mnt/host/scratch')
213         # It would be nice if the above was sufficient to make virtiofsd clear
214         # all references to the mounted directories (so they can be unmounted
215         # on the host), but unfortunately it is not.  To do so, we have to
216         # resort to a remount.
217         self.ssh_command('mount -o remount /mnt/host')
219         scratch_dir = os.path.join(self.shared_dir, 'scratch')
220         self.run(('bash', self.get_data('cleanup.sh'), scratch_dir))
222     @skipUnless(*has_cmds(('sudo -n', ('sudo', '-n', 'true')),
223                           'ssh-keygen', 'bash', 'losetup', 'mkfs.xfs', 'mount'))
224     def setUp(self):
225         vmlinuz = self.params.get('vmlinuz')
226         if vmlinuz is None:
227             """
228             The Linux kernel supports FUSE auto-submounts only as of 5.10.
229             boot_linux.py currently provides Fedora 31, whose kernel is too
230             old, so this test cannot pass with the on-image kernel (you are
231             welcome to try, hence the option to force such a test with
232             -p vmlinuz='').  Therefore, for now the user must provide a
233             sufficiently new custom kernel, or effectively explicitly
234             request failure with -p vmlinuz=''.
235             Once an image with a sufficiently new kernel is available
236             (probably Fedora 34), we can make -p vmlinuz='' the default, so
237             that this parameter no longer needs to be specified.
238             """
239             self.cancel('vmlinuz parameter not set; you must point it to a '
240                         'Linux kernel binary to test (to run this test with ' \
241                         'the on-image kernel, set it to an empty string)')
243         self.seed = self.params.get('seed')
245         self.ssh_key = os.path.join(self.workdir, 'id_ed25519')
247         self.run(('ssh-keygen', '-N', '', '-t', 'ed25519', '-f', self.ssh_key))
249         pubkey = open(self.ssh_key + '.pub').read()
251         super(VirtiofsSubmountsTest, self).setUp(pubkey)
253         if len(vmlinuz) > 0:
254             self.vm.add_args('-kernel', vmlinuz,
255                              '-append', 'console=ttyS0 root=/dev/sda1')
257         # Allow us to connect to SSH
258         self.vm.add_args('-netdev', 'user,id=vnet,hostfwd=:127.0.0.1:0-:22',
259                          '-device', 'virtio-net,netdev=vnet')
261         self.require_accelerator("kvm")
262         self.vm.add_args('-accel', 'kvm')
264     def tearDown(self):
265         try:
266             self.vm.shutdown()
267         except:
268             pass
270         scratch_dir = os.path.join(self.shared_dir, 'scratch')
271         self.run(('bash', self.get_data('cleanup.sh'), scratch_dir),
272                  ignore_error=True)
274     def test_pre_virtiofsd_set_up(self):
275         self.set_up_shared_dir()
277         self.set_up_nested_mounts()
279         self.set_up_virtiofs()
280         self.launch_vm()
281         self.mount_in_guest()
282         self.check_in_guest()
284     def test_pre_launch_set_up(self):
285         self.set_up_shared_dir()
286         self.set_up_virtiofs()
288         self.set_up_nested_mounts()
290         self.launch_vm()
291         self.mount_in_guest()
292         self.check_in_guest()
294     def test_post_launch_set_up(self):
295         self.set_up_shared_dir()
296         self.set_up_virtiofs()
297         self.launch_vm()
299         self.set_up_nested_mounts()
301         self.mount_in_guest()
302         self.check_in_guest()
304     def test_post_mount_set_up(self):
305         self.set_up_shared_dir()
306         self.set_up_virtiofs()
307         self.launch_vm()
308         self.mount_in_guest()
310         self.set_up_nested_mounts()
312         self.check_in_guest()
314     def test_two_runs(self):
315         self.set_up_shared_dir()
317         self.set_up_nested_mounts()
319         self.set_up_virtiofs()
320         self.launch_vm()
321         self.mount_in_guest()
322         self.check_in_guest()
324         self.live_cleanup()
325         self.set_up_nested_mounts()
327         self.check_in_guest()