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
14 subp
= subprocess
.Popen(args
,
15 stdout
=subprocess
.PIPE
,
16 stderr
=subprocess
.PIPE
,
17 universal_newlines
=True)
18 stdout
, stderr
= subp
.communicate()
21 return (stdout
, stderr
, ret
)
23 def has_cmd(name
, args
=None):
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):
33 args
= ('which', name
)
36 _
, stderr
, exitcode
= run_cmd(args
)
37 except Exception as e
:
42 cmd_line
= ' '.join(args
)
43 err
= f
'{name} required, but "{cmd_line}" failed: {stderr.strip()}'
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')),
55 def test_something_that_needs_cmd1_and_cmd2(self):
60 if isinstance(cmd
, str):
63 ok
, errstr
= has_cmd(*cmd
)
65 return (False, errstr
)
70 class VirtiofsSubmountsTest(LinuxTest
):
72 :avocado: tags=arch:x86_64
75 def get_portfwd(self
):
78 res
= self
.vm
.command('human-monitor-command',
79 command_line
='info usernet')
80 for line
in res
.split('\r\n'):
82 re
.search(r
'TCP.HOST_FORWARD.*127\.0\.0\.1\s+(\d+)\s+10\.',
88 self
.assertIsNotNone(port
)
89 self
.assertGreater(port
, 0)
90 self
.log
.debug('sshd listening on port: %d', 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
)
100 self
.ssh_session
.connect()
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
)
127 cmdline
= ' '.join(args
)
129 self
.fail(f
'{cmdline}: Returned {ret}: {stderr}')
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)
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',
159 '-o', 'announce_submounts',
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])
171 self
.run(('sudo', '-n', 'chmod', 'go+rw', self
.vfsdsock
))
173 self
.vm
.add_args('-chardev',
174 f
'socket,id=vfsdsock,path={self.vfsdsock}',
176 'vhost-user-fs-pci,queue-size=1024,chardev=vfsdsock' \
179 'memory-backend-file,id=mem,size=1G,' \
180 'mem-path=/dev/shm,share=on',
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')
191 os
.mkdir(scratch_dir
)
192 except FileExistsError
:
195 args
= ['bash', self
.get_data('host.sh'), scratch_dir
]
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'))
225 vmlinuz
= self
.params
.get('vmlinuz')
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.
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
)
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')
270 scratch_dir
= os
.path
.join(self
.shared_dir
, 'scratch')
271 self
.run(('bash', self
.get_data('cleanup.sh'), scratch_dir
),
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()
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()
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()
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()
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()
321 self
.mount_in_guest()
322 self
.check_in_guest()
325 self
.set_up_nested_mounts()
327 self
.check_in_guest()