virtiofs_submounts.py (8713B)
1import logging 2import re 3import os 4import subprocess 5import time 6 7from avocado import skipUnless 8from avocado_qemu import LinuxTest, BUILD_DIR 9from avocado_qemu import wait_for_console_pattern 10from avocado.utils import ssh 11 12 13def 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 20 21 return (stdout, stderr, ret) 22 23def has_cmd(name, args=None): 24 """ 25 This function is for use in a @avocado.skipUnless decorator, e.g.: 26 27 @skipUnless(*has_cmd('sudo -n', ('sudo', '-n', 'true'))) 28 def test_something_that_needs_sudo(self): 29 ... 30 """ 31 32 if args is None: 33 args = ('which', name) 34 35 try: 36 _, stderr, exitcode = run_cmd(args) 37 except Exception as e: 38 exitcode = -1 39 stderr = str(e) 40 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, '') 47 48def 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.: 52 53 @skipUnless(*has_cmds(('cmd1', ('cmd1', '--some-parameter')), 54 'cmd2', 'cmd3')) 55 def test_something_that_needs_cmd1_and_cmd2(self): 56 ... 57 """ 58 59 for cmd in cmds: 60 if isinstance(cmd, str): 61 cmd = (cmd,) 62 63 ok, errstr = has_cmd(*cmd) 64 if not ok: 65 return (False, errstr) 66 67 return (True, '') 68 69 70class VirtiofsSubmountsTest(LinuxTest): 71 """ 72 :avocado: tags=arch:x86_64 73 :avocado: tags=accel:kvm 74 """ 75 76 def run(self, args, ignore_error=False): 77 stdout, stderr, ret = run_cmd(args) 78 79 if ret != 0: 80 cmdline = ' '.join(args) 81 if not ignore_error: 82 self.fail(f'{cmdline}: Returned {ret}: {stderr}') 83 else: 84 self.log.warn(f'{cmdline}: Returned {ret}: {stderr}') 85 86 return (stdout, stderr, ret) 87 88 def set_up_shared_dir(self): 89 self.shared_dir = os.path.join(self.workdir, 'virtiofs-shared') 90 91 os.mkdir(self.shared_dir) 92 93 self.run(('cp', self.get_data('guest.sh'), 94 os.path.join(self.shared_dir, 'check.sh'))) 95 96 self.run(('cp', self.get_data('guest-cleanup.sh'), 97 os.path.join(self.shared_dir, 'cleanup.sh'))) 98 99 def set_up_virtiofs(self): 100 attmp = os.getenv('AVOCADO_TESTS_COMMON_TMPDIR') 101 self.vfsdsock = os.path.join(attmp, 'vfsdsock') 102 103 self.run(('sudo', '-n', 'rm', '-f', self.vfsdsock), ignore_error=True) 104 105 self.virtiofsd = \ 106 subprocess.Popen(('sudo', '-n', 107 'tools/virtiofsd/virtiofsd', 108 f'--socket-path={self.vfsdsock}', 109 '-o', f'source={self.shared_dir}', 110 '-o', 'cache=always', 111 '-o', 'xattr', 112 '-o', 'announce_submounts', 113 '-f'), 114 stdout=subprocess.DEVNULL, 115 stderr=subprocess.PIPE, 116 universal_newlines=True) 117 118 while not os.path.exists(self.vfsdsock): 119 if self.virtiofsd.poll() is not None: 120 self.fail('virtiofsd exited prematurely: ' + 121 self.virtiofsd.communicate()[1]) 122 time.sleep(0.1) 123 124 self.run(('sudo', '-n', 'chmod', 'go+rw', self.vfsdsock)) 125 126 self.vm.add_args('-chardev', 127 f'socket,id=vfsdsock,path={self.vfsdsock}', 128 '-device', 129 'vhost-user-fs-pci,queue-size=1024,chardev=vfsdsock' \ 130 ',tag=host', 131 '-object', 132 'memory-backend-file,id=mem,size=1G,' \ 133 'mem-path=/dev/shm,share=on', 134 '-numa', 135 'node,memdev=mem') 136 137 def set_up_nested_mounts(self): 138 scratch_dir = os.path.join(self.shared_dir, 'scratch') 139 try: 140 os.mkdir(scratch_dir) 141 except FileExistsError: 142 pass 143 144 args = ['bash', self.get_data('host.sh'), scratch_dir] 145 if self.seed: 146 args += [self.seed] 147 148 out, _, _ = self.run(args) 149 seed = re.search(r'^Seed: \d+', out) 150 self.log.info(seed[0]) 151 152 def mount_in_guest(self): 153 self.ssh_command('mkdir -p /mnt/host') 154 self.ssh_command('mount -t virtiofs host /mnt/host') 155 156 def check_in_guest(self): 157 self.ssh_command('bash /mnt/host/check.sh /mnt/host/scratch/share') 158 159 def live_cleanup(self): 160 self.ssh_command('bash /mnt/host/cleanup.sh /mnt/host/scratch') 161 162 # It would be nice if the above was sufficient to make virtiofsd clear 163 # all references to the mounted directories (so they can be unmounted 164 # on the host), but unfortunately it is not. To do so, we have to 165 # resort to a remount. 166 self.ssh_command('mount -o remount /mnt/host') 167 168 scratch_dir = os.path.join(self.shared_dir, 'scratch') 169 self.run(('bash', self.get_data('cleanup.sh'), scratch_dir)) 170 171 @skipUnless(*has_cmds(('sudo -n', ('sudo', '-n', 'true')), 172 'ssh-keygen', 'bash', 'losetup', 'mkfs.xfs', 'mount')) 173 def setUp(self): 174 vmlinuz = self.params.get('vmlinuz') 175 if vmlinuz is None: 176 """ 177 The Linux kernel supports FUSE auto-submounts only as of 5.10. 178 boot_linux.py currently provides Fedora 31, whose kernel is too 179 old, so this test cannot pass with the on-image kernel (you are 180 welcome to try, hence the option to force such a test with 181 -p vmlinuz=''). Therefore, for now the user must provide a 182 sufficiently new custom kernel, or effectively explicitly 183 request failure with -p vmlinuz=''. 184 Once an image with a sufficiently new kernel is available 185 (probably Fedora 34), we can make -p vmlinuz='' the default, so 186 that this parameter no longer needs to be specified. 187 """ 188 self.cancel('vmlinuz parameter not set; you must point it to a ' 189 'Linux kernel binary to test (to run this test with ' \ 190 'the on-image kernel, set it to an empty string)') 191 192 self.seed = self.params.get('seed') 193 194 self.ssh_key = os.path.join(self.workdir, 'id_ed25519') 195 196 self.run(('ssh-keygen', '-N', '', '-t', 'ed25519', '-f', self.ssh_key)) 197 198 pubkey = self.ssh_key + '.pub' 199 200 super(VirtiofsSubmountsTest, self).setUp(pubkey) 201 202 if vmlinuz: 203 self.vm.add_args('-kernel', vmlinuz, 204 '-append', 'console=ttyS0 root=/dev/sda1') 205 206 self.require_accelerator("kvm") 207 self.vm.add_args('-accel', 'kvm') 208 209 def tearDown(self): 210 try: 211 self.vm.shutdown() 212 except: 213 pass 214 215 scratch_dir = os.path.join(self.shared_dir, 'scratch') 216 self.run(('bash', self.get_data('cleanup.sh'), scratch_dir), 217 ignore_error=True) 218 219 def test_pre_virtiofsd_set_up(self): 220 self.set_up_shared_dir() 221 222 self.set_up_nested_mounts() 223 224 self.set_up_virtiofs() 225 self.launch_and_wait() 226 self.mount_in_guest() 227 self.check_in_guest() 228 229 def test_pre_launch_set_up(self): 230 self.set_up_shared_dir() 231 self.set_up_virtiofs() 232 233 self.set_up_nested_mounts() 234 235 self.launch_and_wait() 236 self.mount_in_guest() 237 self.check_in_guest() 238 239 def test_post_launch_set_up(self): 240 self.set_up_shared_dir() 241 self.set_up_virtiofs() 242 self.launch_and_wait() 243 244 self.set_up_nested_mounts() 245 246 self.mount_in_guest() 247 self.check_in_guest() 248 249 def test_post_mount_set_up(self): 250 self.set_up_shared_dir() 251 self.set_up_virtiofs() 252 self.launch_and_wait() 253 self.mount_in_guest() 254 255 self.set_up_nested_mounts() 256 257 self.check_in_guest() 258 259 def test_two_runs(self): 260 self.set_up_shared_dir() 261 262 self.set_up_nested_mounts() 263 264 self.set_up_virtiofs() 265 self.launch_and_wait() 266 self.mount_in_guest() 267 self.check_in_guest() 268 269 self.live_cleanup() 270 self.set_up_nested_mounts() 271 272 self.check_in_guest()