xref: /openbmc/qemu/tests/avocado/avocado_qemu/__init__.py (revision 4b7ea33074450bc6148c8e1545d78f179e64adb4)
1bbbd9b6eSWillian Rampazzo# Test class and utilities for functional tests
2bbbd9b6eSWillian Rampazzo#
3bbbd9b6eSWillian Rampazzo# Copyright (c) 2018 Red Hat, Inc.
4bbbd9b6eSWillian Rampazzo#
5bbbd9b6eSWillian Rampazzo# Author:
6bbbd9b6eSWillian Rampazzo#  Cleber Rosa <crosa@redhat.com>
7bbbd9b6eSWillian Rampazzo#
8bbbd9b6eSWillian Rampazzo# This work is licensed under the terms of the GNU GPL, version 2 or
9bbbd9b6eSWillian Rampazzo# later.  See the COPYING file in the top-level directory.
10bbbd9b6eSWillian Rampazzo
11bbbd9b6eSWillian Rampazzoimport logging
12bbbd9b6eSWillian Rampazzoimport os
130e4b1c94SPhilippe Mathieu-Daudéimport subprocess
14bbbd9b6eSWillian Rampazzoimport sys
15bbbd9b6eSWillian Rampazzoimport tempfile
16bbbd9b6eSWillian Rampazzoimport time
17bbbd9b6eSWillian Rampazzoimport uuid
18bbbd9b6eSWillian Rampazzo
19bbbd9b6eSWillian Rampazzoimport avocado
20816d4201SThomas Huthfrom avocado.utils import ssh
21bbbd9b6eSWillian Rampazzofrom avocado.utils.path import find_command
22bbbd9b6eSWillian Rampazzo
230e7647aaSJohn Snowfrom qemu.machine import QEMUMachine
240e7647aaSJohn Snowfrom qemu.utils import (get_info_usernet_hostfwd_port, kvm_available,
250e7647aaSJohn Snow                        tcg_available)
260e7647aaSJohn Snow
270e7647aaSJohn Snow
28bbbd9b6eSWillian Rampazzo#: The QEMU build root directory.  It may also be the source directory
29bbbd9b6eSWillian Rampazzo#: if building from the source dir, but it's safer to use BUILD_DIR for
30bbbd9b6eSWillian Rampazzo#: that purpose.  Be aware that if this code is moved outside of a source
31bbbd9b6eSWillian Rampazzo#: and build tree, it will not be accurate.
32bbbd9b6eSWillian RampazzoBUILD_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
33bbbd9b6eSWillian Rampazzo
34bbbd9b6eSWillian Rampazzo
350e4b1c94SPhilippe Mathieu-Daudédef has_cmd(name, args=None):
360e4b1c94SPhilippe Mathieu-Daudé    """
370e4b1c94SPhilippe Mathieu-Daudé    This function is for use in a @avocado.skipUnless decorator, e.g.:
380e4b1c94SPhilippe Mathieu-Daudé
390e4b1c94SPhilippe Mathieu-Daudé        @skipUnless(*has_cmd('sudo -n', ('sudo', '-n', 'true')))
400e4b1c94SPhilippe Mathieu-Daudé        def test_something_that_needs_sudo(self):
410e4b1c94SPhilippe Mathieu-Daudé            ...
420e4b1c94SPhilippe Mathieu-Daudé    """
430e4b1c94SPhilippe Mathieu-Daudé
440e4b1c94SPhilippe Mathieu-Daudé    if args is None:
450e4b1c94SPhilippe Mathieu-Daudé        args = ('which', name)
460e4b1c94SPhilippe Mathieu-Daudé
470e4b1c94SPhilippe Mathieu-Daudé    try:
480e4b1c94SPhilippe Mathieu-Daudé        _, stderr, exitcode = run_cmd(args)
490e4b1c94SPhilippe Mathieu-Daudé    except Exception as e:
500e4b1c94SPhilippe Mathieu-Daudé        exitcode = -1
510e4b1c94SPhilippe Mathieu-Daudé        stderr = str(e)
520e4b1c94SPhilippe Mathieu-Daudé
530e4b1c94SPhilippe Mathieu-Daudé    if exitcode != 0:
540e4b1c94SPhilippe Mathieu-Daudé        cmd_line = ' '.join(args)
550e4b1c94SPhilippe Mathieu-Daudé        err = f'{name} required, but "{cmd_line}" failed: {stderr.strip()}'
560e4b1c94SPhilippe Mathieu-Daudé        return (False, err)
570e4b1c94SPhilippe Mathieu-Daudé    else:
580e4b1c94SPhilippe Mathieu-Daudé        return (True, '')
590e4b1c94SPhilippe Mathieu-Daudé
600e4b1c94SPhilippe Mathieu-Daudédef has_cmds(*cmds):
610e4b1c94SPhilippe Mathieu-Daudé    """
620e4b1c94SPhilippe Mathieu-Daudé    This function is for use in a @avocado.skipUnless decorator and
630e4b1c94SPhilippe Mathieu-Daudé    allows checking for the availability of multiple commands, e.g.:
640e4b1c94SPhilippe Mathieu-Daudé
650e4b1c94SPhilippe Mathieu-Daudé        @skipUnless(*has_cmds(('cmd1', ('cmd1', '--some-parameter')),
660e4b1c94SPhilippe Mathieu-Daudé                              'cmd2', 'cmd3'))
670e4b1c94SPhilippe Mathieu-Daudé        def test_something_that_needs_cmd1_and_cmd2(self):
680e4b1c94SPhilippe Mathieu-Daudé            ...
690e4b1c94SPhilippe Mathieu-Daudé    """
700e4b1c94SPhilippe Mathieu-Daudé
710e4b1c94SPhilippe Mathieu-Daudé    for cmd in cmds:
720e4b1c94SPhilippe Mathieu-Daudé        if isinstance(cmd, str):
730e4b1c94SPhilippe Mathieu-Daudé            cmd = (cmd,)
740e4b1c94SPhilippe Mathieu-Daudé
750e4b1c94SPhilippe Mathieu-Daudé        ok, errstr = has_cmd(*cmd)
760e4b1c94SPhilippe Mathieu-Daudé        if not ok:
770e4b1c94SPhilippe Mathieu-Daudé            return (False, errstr)
780e4b1c94SPhilippe Mathieu-Daudé
790e4b1c94SPhilippe Mathieu-Daudé    return (True, '')
800e4b1c94SPhilippe Mathieu-Daudé
810e4b1c94SPhilippe Mathieu-Daudédef run_cmd(args):
820e4b1c94SPhilippe Mathieu-Daudé    subp = subprocess.Popen(args,
830e4b1c94SPhilippe Mathieu-Daudé                            stdout=subprocess.PIPE,
840e4b1c94SPhilippe Mathieu-Daudé                            stderr=subprocess.PIPE,
850e4b1c94SPhilippe Mathieu-Daudé                            universal_newlines=True)
860e4b1c94SPhilippe Mathieu-Daudé    stdout, stderr = subp.communicate()
870e4b1c94SPhilippe Mathieu-Daudé    ret = subp.returncode
880e4b1c94SPhilippe Mathieu-Daudé
890e4b1c94SPhilippe Mathieu-Daudé    return (stdout, stderr, ret)
900e4b1c94SPhilippe Mathieu-Daudé
91bbbd9b6eSWillian Rampazzodef is_readable_executable_file(path):
92bbbd9b6eSWillian Rampazzo    return os.path.isfile(path) and os.access(path, os.R_OK | os.X_OK)
93bbbd9b6eSWillian Rampazzo
94bbbd9b6eSWillian Rampazzo
959112d4fdSPhilippe Mathieu-Daudédef pick_default_qemu_bin(bin_prefix='qemu-system-', arch=None):
96bbbd9b6eSWillian Rampazzo    """
97bbbd9b6eSWillian Rampazzo    Picks the path of a QEMU binary, starting either in the current working
98bbbd9b6eSWillian Rampazzo    directory or in the source tree root directory.
99bbbd9b6eSWillian Rampazzo
100bbbd9b6eSWillian Rampazzo    :param arch: the arch to use when looking for a QEMU binary (the target
101bbbd9b6eSWillian Rampazzo                 will match the arch given).  If None (the default), arch
102bbbd9b6eSWillian Rampazzo                 will be the current host system arch (as given by
103bbbd9b6eSWillian Rampazzo                 :func:`os.uname`).
104bbbd9b6eSWillian Rampazzo    :type arch: str
105bbbd9b6eSWillian Rampazzo    :returns: the path to the default QEMU binary or None if one could not
106bbbd9b6eSWillian Rampazzo              be found
107bbbd9b6eSWillian Rampazzo    :rtype: str or None
108bbbd9b6eSWillian Rampazzo    """
109bbbd9b6eSWillian Rampazzo    if arch is None:
110bbbd9b6eSWillian Rampazzo        arch = os.uname()[4]
111bbbd9b6eSWillian Rampazzo    # qemu binary path does not match arch for powerpc, handle it
112bbbd9b6eSWillian Rampazzo    if 'ppc64le' in arch:
113bbbd9b6eSWillian Rampazzo        arch = 'ppc64'
11450b13d31SPeter Delevoryas    qemu_bin_name = bin_prefix + arch
11550b13d31SPeter Delevoryas    qemu_bin_paths = [
11650b13d31SPeter Delevoryas        os.path.join(".", qemu_bin_name),
11750b13d31SPeter Delevoryas        os.path.join(BUILD_DIR, qemu_bin_name),
11850b13d31SPeter Delevoryas        os.path.join(BUILD_DIR, "build", qemu_bin_name),
11950b13d31SPeter Delevoryas    ]
12050b13d31SPeter Delevoryas    for path in qemu_bin_paths:
12150b13d31SPeter Delevoryas        if is_readable_executable_file(path):
12250b13d31SPeter Delevoryas            return path
123bbbd9b6eSWillian Rampazzo    return None
124bbbd9b6eSWillian Rampazzo
125bbbd9b6eSWillian Rampazzo
126bbbd9b6eSWillian Rampazzodef _console_interaction(test, success_message, failure_message,
127bbbd9b6eSWillian Rampazzo                         send_string, keep_sending=False, vm=None):
128bbbd9b6eSWillian Rampazzo    assert not keep_sending or send_string
129bbbd9b6eSWillian Rampazzo    if vm is None:
130bbbd9b6eSWillian Rampazzo        vm = test.vm
131f0ec14c7SNicholas Piggin    console = vm.console_file
132bbbd9b6eSWillian Rampazzo    console_logger = logging.getLogger('console')
133bbbd9b6eSWillian Rampazzo    while True:
134bbbd9b6eSWillian Rampazzo        if send_string:
135bbbd9b6eSWillian Rampazzo            vm.console_socket.sendall(send_string.encode())
136bbbd9b6eSWillian Rampazzo            if not keep_sending:
137bbbd9b6eSWillian Rampazzo                send_string = None # send only once
138*4a85f231SNicholas Piggin
139*4a85f231SNicholas Piggin        # Only consume console output if waiting for something
140*4a85f231SNicholas Piggin        if success_message is None and failure_message is None:
141*4a85f231SNicholas Piggin            if send_string is None:
142*4a85f231SNicholas Piggin                break
143*4a85f231SNicholas Piggin            continue
144*4a85f231SNicholas Piggin
145bbbd9b6eSWillian Rampazzo        try:
146bbbd9b6eSWillian Rampazzo            msg = console.readline().decode().strip()
147bbbd9b6eSWillian Rampazzo        except UnicodeDecodeError:
148bbbd9b6eSWillian Rampazzo            msg = None
149bbbd9b6eSWillian Rampazzo        if not msg:
150bbbd9b6eSWillian Rampazzo            continue
151bbbd9b6eSWillian Rampazzo        console_logger.debug(msg)
152bbbd9b6eSWillian Rampazzo        if success_message is None or success_message in msg:
153bbbd9b6eSWillian Rampazzo            break
154bbbd9b6eSWillian Rampazzo        if failure_message and failure_message in msg:
155bbbd9b6eSWillian Rampazzo            console.close()
156bbbd9b6eSWillian Rampazzo            fail = 'Failure message found in console: "%s". Expected: "%s"' % \
157bbbd9b6eSWillian Rampazzo                    (failure_message, success_message)
158bbbd9b6eSWillian Rampazzo            test.fail(fail)
159bbbd9b6eSWillian Rampazzo
160bbbd9b6eSWillian Rampazzodef interrupt_interactive_console_until_pattern(test, success_message,
161bbbd9b6eSWillian Rampazzo                                                failure_message=None,
162bbbd9b6eSWillian Rampazzo                                                interrupt_string='\r'):
163bbbd9b6eSWillian Rampazzo    """
164bbbd9b6eSWillian Rampazzo    Keep sending a string to interrupt a console prompt, while logging the
165bbbd9b6eSWillian Rampazzo    console output. Typical use case is to break a boot loader prompt, such:
166bbbd9b6eSWillian Rampazzo
167bbbd9b6eSWillian Rampazzo        Press a key within 5 seconds to interrupt boot process.
168bbbd9b6eSWillian Rampazzo        5
169bbbd9b6eSWillian Rampazzo        4
170bbbd9b6eSWillian Rampazzo        3
171bbbd9b6eSWillian Rampazzo        2
172bbbd9b6eSWillian Rampazzo        1
173bbbd9b6eSWillian Rampazzo        Booting default image...
174bbbd9b6eSWillian Rampazzo
175bbbd9b6eSWillian Rampazzo    :param test: an Avocado test containing a VM that will have its console
176bbbd9b6eSWillian Rampazzo                 read and probed for a success or failure message
1772283b627SPhilippe Mathieu-Daudé    :type test: :class:`avocado_qemu.QemuSystemTest`
178bbbd9b6eSWillian Rampazzo    :param success_message: if this message appears, test succeeds
179bbbd9b6eSWillian Rampazzo    :param failure_message: if this message appears, test fails
180bbbd9b6eSWillian Rampazzo    :param interrupt_string: a string to send to the console before trying
181bbbd9b6eSWillian Rampazzo                             to read a new line
182bbbd9b6eSWillian Rampazzo    """
183bbbd9b6eSWillian Rampazzo    _console_interaction(test, success_message, failure_message,
184bbbd9b6eSWillian Rampazzo                         interrupt_string, True)
185bbbd9b6eSWillian Rampazzo
186bbbd9b6eSWillian Rampazzodef wait_for_console_pattern(test, success_message, failure_message=None,
187bbbd9b6eSWillian Rampazzo                             vm=None):
188bbbd9b6eSWillian Rampazzo    """
189bbbd9b6eSWillian Rampazzo    Waits for messages to appear on the console, while logging the content
190bbbd9b6eSWillian Rampazzo
191bbbd9b6eSWillian Rampazzo    :param test: an Avocado test containing a VM that will have its console
192bbbd9b6eSWillian Rampazzo                 read and probed for a success or failure message
1932283b627SPhilippe Mathieu-Daudé    :type test: :class:`avocado_qemu.QemuSystemTest`
194bbbd9b6eSWillian Rampazzo    :param success_message: if this message appears, test succeeds
195bbbd9b6eSWillian Rampazzo    :param failure_message: if this message appears, test fails
196bbbd9b6eSWillian Rampazzo    """
197bbbd9b6eSWillian Rampazzo    _console_interaction(test, success_message, failure_message, None, vm=vm)
198bbbd9b6eSWillian Rampazzo
199bbbd9b6eSWillian Rampazzodef exec_command(test, command):
200bbbd9b6eSWillian Rampazzo    """
201bbbd9b6eSWillian Rampazzo    Send a command to a console (appending CRLF characters), while logging
202bbbd9b6eSWillian Rampazzo    the content.
203bbbd9b6eSWillian Rampazzo
204bbbd9b6eSWillian Rampazzo    :param test: an Avocado test containing a VM.
2052283b627SPhilippe Mathieu-Daudé    :type test: :class:`avocado_qemu.QemuSystemTest`
206bbbd9b6eSWillian Rampazzo    :param command: the command to send
207bbbd9b6eSWillian Rampazzo    :type command: str
208bbbd9b6eSWillian Rampazzo    """
209bbbd9b6eSWillian Rampazzo    _console_interaction(test, None, None, command + '\r')
210bbbd9b6eSWillian Rampazzo
211bbbd9b6eSWillian Rampazzodef exec_command_and_wait_for_pattern(test, command,
212bbbd9b6eSWillian Rampazzo                                      success_message, failure_message=None):
213bbbd9b6eSWillian Rampazzo    """
214bbbd9b6eSWillian Rampazzo    Send a command to a console (appending CRLF characters), then wait
215bbbd9b6eSWillian Rampazzo    for success_message to appear on the console, while logging the.
216bbbd9b6eSWillian Rampazzo    content. Mark the test as failed if failure_message is found instead.
217bbbd9b6eSWillian Rampazzo
218bbbd9b6eSWillian Rampazzo    :param test: an Avocado test containing a VM that will have its console
219bbbd9b6eSWillian Rampazzo                 read and probed for a success or failure message
2202283b627SPhilippe Mathieu-Daudé    :type test: :class:`avocado_qemu.QemuSystemTest`
221bbbd9b6eSWillian Rampazzo    :param command: the command to send
222bbbd9b6eSWillian Rampazzo    :param success_message: if this message appears, test succeeds
223bbbd9b6eSWillian Rampazzo    :param failure_message: if this message appears, test fails
224bbbd9b6eSWillian Rampazzo    """
225bbbd9b6eSWillian Rampazzo    _console_interaction(test, success_message, failure_message, command + '\r')
226bbbd9b6eSWillian Rampazzo
2273982feb4SPhilippe Mathieu-Daudéclass QemuBaseTest(avocado.Test):
22848acf68cSAlex Bennée
22948acf68cSAlex Bennée    # default timeout for all tests, can be overridden
2308f58f0c7SAlex Bennée    timeout = 120
23148acf68cSAlex Bennée
232bbbd9b6eSWillian Rampazzo    def _get_unique_tag_val(self, tag_name):
233bbbd9b6eSWillian Rampazzo        """
234bbbd9b6eSWillian Rampazzo        Gets a tag value, if unique for a key
235bbbd9b6eSWillian Rampazzo        """
236bbbd9b6eSWillian Rampazzo        vals = self.tags.get(tag_name, [])
237bbbd9b6eSWillian Rampazzo        if len(vals) == 1:
238bbbd9b6eSWillian Rampazzo            return vals.pop()
239bbbd9b6eSWillian Rampazzo        return None
240bbbd9b6eSWillian Rampazzo
2419112d4fdSPhilippe Mathieu-Daudé    def setUp(self, bin_prefix):
2423982feb4SPhilippe Mathieu-Daudé        self.arch = self.params.get('arch',
2433982feb4SPhilippe Mathieu-Daudé                                    default=self._get_unique_tag_val('arch'))
2443982feb4SPhilippe Mathieu-Daudé
2453982feb4SPhilippe Mathieu-Daudé        self.cpu = self.params.get('cpu',
2463982feb4SPhilippe Mathieu-Daudé                                   default=self._get_unique_tag_val('cpu'))
2473982feb4SPhilippe Mathieu-Daudé
2489112d4fdSPhilippe Mathieu-Daudé        default_qemu_bin = pick_default_qemu_bin(bin_prefix, arch=self.arch)
2493982feb4SPhilippe Mathieu-Daudé        self.qemu_bin = self.params.get('qemu_bin',
2503982feb4SPhilippe Mathieu-Daudé                                        default=default_qemu_bin)
2513982feb4SPhilippe Mathieu-Daudé        if self.qemu_bin is None:
2523982feb4SPhilippe Mathieu-Daudé            self.cancel("No QEMU binary defined or found in the build tree")
2533982feb4SPhilippe Mathieu-Daudé
2543982feb4SPhilippe Mathieu-Daudé    def fetch_asset(self, name,
2559d72dd10SPhilippe Mathieu-Daudé                    asset_hash, algorithm=None,
2563982feb4SPhilippe Mathieu-Daudé                    locations=None, expire=None,
2573982feb4SPhilippe Mathieu-Daudé                    find_only=False, cancel_on_missing=True):
2583982feb4SPhilippe Mathieu-Daudé        return super().fetch_asset(name,
2593982feb4SPhilippe Mathieu-Daudé                        asset_hash=asset_hash,
2603982feb4SPhilippe Mathieu-Daudé                        algorithm=algorithm,
2613982feb4SPhilippe Mathieu-Daudé                        locations=locations,
2623982feb4SPhilippe Mathieu-Daudé                        expire=expire,
2633982feb4SPhilippe Mathieu-Daudé                        find_only=find_only,
2643982feb4SPhilippe Mathieu-Daudé                        cancel_on_missing=cancel_on_missing)
2653982feb4SPhilippe Mathieu-Daudé
2663982feb4SPhilippe Mathieu-Daudé
2672283b627SPhilippe Mathieu-Daudéclass QemuSystemTest(QemuBaseTest):
2682283b627SPhilippe Mathieu-Daudé    """Facilitates system emulation tests."""
2693982feb4SPhilippe Mathieu-Daudé
2703982feb4SPhilippe Mathieu-Daudé    def setUp(self):
2713982feb4SPhilippe Mathieu-Daudé        self._vms = {}
2723982feb4SPhilippe Mathieu-Daudé
2739112d4fdSPhilippe Mathieu-Daudé        super().setUp('qemu-system-')
2743982feb4SPhilippe Mathieu-Daudé
2755ad2d7a9SFabiano Rosas        accel_required = self._get_unique_tag_val('accel')
2765ad2d7a9SFabiano Rosas        if accel_required:
2775ad2d7a9SFabiano Rosas            self.require_accelerator(accel_required)
2785ad2d7a9SFabiano Rosas
2793982feb4SPhilippe Mathieu-Daudé        self.machine = self.params.get('machine',
2803982feb4SPhilippe Mathieu-Daudé                                       default=self._get_unique_tag_val('machine'))
2813982feb4SPhilippe Mathieu-Daudé
282bbbd9b6eSWillian Rampazzo    def require_accelerator(self, accelerator):
283bbbd9b6eSWillian Rampazzo        """
284bbbd9b6eSWillian Rampazzo        Requires an accelerator to be available for the test to continue
285bbbd9b6eSWillian Rampazzo
286bbbd9b6eSWillian Rampazzo        It takes into account the currently set qemu binary.
287bbbd9b6eSWillian Rampazzo
288bbbd9b6eSWillian Rampazzo        If the check fails, the test is canceled.  If the check itself
289bbbd9b6eSWillian Rampazzo        for the given accelerator is not available, the test is also
290bbbd9b6eSWillian Rampazzo        canceled.
291bbbd9b6eSWillian Rampazzo
292bbbd9b6eSWillian Rampazzo        :param accelerator: name of the accelerator, such as "kvm" or "tcg"
293bbbd9b6eSWillian Rampazzo        :type accelerator: str
294bbbd9b6eSWillian Rampazzo        """
295bbbd9b6eSWillian Rampazzo        checker = {'tcg': tcg_available,
296bbbd9b6eSWillian Rampazzo                   'kvm': kvm_available}.get(accelerator)
297bbbd9b6eSWillian Rampazzo        if checker is None:
298bbbd9b6eSWillian Rampazzo            self.cancel("Don't know how to check for the presence "
299bbbd9b6eSWillian Rampazzo                        "of accelerator %s" % accelerator)
300bbbd9b6eSWillian Rampazzo        if not checker(qemu_bin=self.qemu_bin):
301bbbd9b6eSWillian Rampazzo            self.cancel("%s accelerator does not seem to be "
302bbbd9b6eSWillian Rampazzo                        "available" % accelerator)
303bbbd9b6eSWillian Rampazzo
3040fc389feSThomas Huth    def require_netdev(self, netdevname):
3050fc389feSThomas Huth        netdevhelp = run_cmd([self.qemu_bin,
3060fc389feSThomas Huth                             '-M', 'none', '-netdev', 'help'])[0];
3070fc389feSThomas Huth        if netdevhelp.find('\n' + netdevname + '\n') < 0:
3080fc389feSThomas Huth            self.cancel('no support for user networking')
3090fc389feSThomas Huth
310bbbd9b6eSWillian Rampazzo    def _new_vm(self, name, *args):
311f9922937SPeter Delevoryas        self._sd = tempfile.TemporaryDirectory(prefix="qemu_")
312bbbd9b6eSWillian Rampazzo        vm = QEMUMachine(self.qemu_bin, base_temp_dir=self.workdir,
31346d4747aSJohn Snow                         log_dir=self.logdir)
314bbbd9b6eSWillian Rampazzo        self.log.debug('QEMUMachine "%s" created', name)
315bbbd9b6eSWillian Rampazzo        self.log.debug('QEMUMachine "%s" temp_dir: %s', name, vm.temp_dir)
316bbbd9b6eSWillian Rampazzo        self.log.debug('QEMUMachine "%s" log_dir: %s', name, vm.log_dir)
317bbbd9b6eSWillian Rampazzo        if args:
318bbbd9b6eSWillian Rampazzo            vm.add_args(*args)
319bbbd9b6eSWillian Rampazzo        return vm
320bbbd9b6eSWillian Rampazzo
321ab8eff7cSKautuk Consul    def get_qemu_img(self):
322ab8eff7cSKautuk Consul        self.log.debug('Looking for and selecting a qemu-img binary')
323ab8eff7cSKautuk Consul
324ab8eff7cSKautuk Consul        # If qemu-img has been built, use it, otherwise the system wide one
325ab8eff7cSKautuk Consul        # will be used.
326ab8eff7cSKautuk Consul        qemu_img = os.path.join(BUILD_DIR, 'qemu-img')
327ab8eff7cSKautuk Consul        if not os.path.exists(qemu_img):
328ab8eff7cSKautuk Consul            qemu_img = find_command('qemu-img', False)
329ab8eff7cSKautuk Consul        if qemu_img is False:
330ab8eff7cSKautuk Consul            self.cancel('Could not find "qemu-img"')
331ab8eff7cSKautuk Consul
332ab8eff7cSKautuk Consul        return qemu_img
333ab8eff7cSKautuk Consul
334bbbd9b6eSWillian Rampazzo    @property
335bbbd9b6eSWillian Rampazzo    def vm(self):
336bbbd9b6eSWillian Rampazzo        return self.get_vm(name='default')
337bbbd9b6eSWillian Rampazzo
338bbbd9b6eSWillian Rampazzo    def get_vm(self, *args, name=None):
339bbbd9b6eSWillian Rampazzo        if not name:
340bbbd9b6eSWillian Rampazzo            name = str(uuid.uuid4())
341bbbd9b6eSWillian Rampazzo        if self._vms.get(name) is None:
342bbbd9b6eSWillian Rampazzo            self._vms[name] = self._new_vm(name, *args)
343bbbd9b6eSWillian Rampazzo            if self.cpu is not None:
344bbbd9b6eSWillian Rampazzo                self._vms[name].add_args('-cpu', self.cpu)
345bbbd9b6eSWillian Rampazzo            if self.machine is not None:
346bbbd9b6eSWillian Rampazzo                self._vms[name].set_machine(self.machine)
347bbbd9b6eSWillian Rampazzo        return self._vms[name]
348bbbd9b6eSWillian Rampazzo
349bbbd9b6eSWillian Rampazzo    def set_vm_arg(self, arg, value):
350bbbd9b6eSWillian Rampazzo        """
351bbbd9b6eSWillian Rampazzo        Set an argument to list of extra arguments to be given to the QEMU
352bbbd9b6eSWillian Rampazzo        binary. If the argument already exists then its value is replaced.
353bbbd9b6eSWillian Rampazzo
354bbbd9b6eSWillian Rampazzo        :param arg: the QEMU argument, such as "-cpu" in "-cpu host"
355bbbd9b6eSWillian Rampazzo        :type arg: str
356bbbd9b6eSWillian Rampazzo        :param value: the argument value, such as "host" in "-cpu host"
357bbbd9b6eSWillian Rampazzo        :type value: str
358bbbd9b6eSWillian Rampazzo        """
359bbbd9b6eSWillian Rampazzo        if not arg or not value:
360bbbd9b6eSWillian Rampazzo            return
361bbbd9b6eSWillian Rampazzo        if arg not in self.vm.args:
362bbbd9b6eSWillian Rampazzo            self.vm.args.extend([arg, value])
363bbbd9b6eSWillian Rampazzo        else:
364bbbd9b6eSWillian Rampazzo            idx = self.vm.args.index(arg) + 1
365bbbd9b6eSWillian Rampazzo            if idx < len(self.vm.args):
366bbbd9b6eSWillian Rampazzo                self.vm.args[idx] = value
367bbbd9b6eSWillian Rampazzo            else:
368bbbd9b6eSWillian Rampazzo                self.vm.args.append(value)
369bbbd9b6eSWillian Rampazzo
370bbbd9b6eSWillian Rampazzo    def tearDown(self):
371bbbd9b6eSWillian Rampazzo        for vm in self._vms.values():
372bbbd9b6eSWillian Rampazzo            vm.shutdown()
373bbbd9b6eSWillian Rampazzo        self._sd = None
374bbbd9b6eSWillian Rampazzo        super().tearDown()
375bbbd9b6eSWillian Rampazzo
376bbbd9b6eSWillian Rampazzo
377bbbd9b6eSWillian Rampazzoclass LinuxSSHMixIn:
378bbbd9b6eSWillian Rampazzo    """Contains utility methods for interacting with a guest via SSH."""
379bbbd9b6eSWillian Rampazzo
380bbbd9b6eSWillian Rampazzo    def ssh_connect(self, username, credential, credential_is_key=True):
381bbbd9b6eSWillian Rampazzo        self.ssh_logger = logging.getLogger('ssh')
382684750abSVladimir Sementsov-Ogievskiy        res = self.vm.cmd('human-monitor-command',
383bbbd9b6eSWillian Rampazzo                          command_line='info usernet')
384bbbd9b6eSWillian Rampazzo        port = get_info_usernet_hostfwd_port(res)
385bbbd9b6eSWillian Rampazzo        self.assertIsNotNone(port)
386bbbd9b6eSWillian Rampazzo        self.assertGreater(port, 0)
387bbbd9b6eSWillian Rampazzo        self.log.debug('sshd listening on port: %d', port)
388bbbd9b6eSWillian Rampazzo        if credential_is_key:
389bbbd9b6eSWillian Rampazzo            self.ssh_session = ssh.Session('127.0.0.1', port=port,
390bbbd9b6eSWillian Rampazzo                                           user=username, key=credential)
391bbbd9b6eSWillian Rampazzo        else:
392bbbd9b6eSWillian Rampazzo            self.ssh_session = ssh.Session('127.0.0.1', port=port,
393bbbd9b6eSWillian Rampazzo                                           user=username, password=credential)
394bbbd9b6eSWillian Rampazzo        for i in range(10):
395bbbd9b6eSWillian Rampazzo            try:
396bbbd9b6eSWillian Rampazzo                self.ssh_session.connect()
397bbbd9b6eSWillian Rampazzo                return
398bbbd9b6eSWillian Rampazzo            except:
399bbbd9b6eSWillian Rampazzo                time.sleep(i)
400bbbd9b6eSWillian Rampazzo        self.fail('ssh connection timeout')
401bbbd9b6eSWillian Rampazzo
402bbbd9b6eSWillian Rampazzo    def ssh_command(self, command):
403bbbd9b6eSWillian Rampazzo        self.ssh_logger.info(command)
404bbbd9b6eSWillian Rampazzo        result = self.ssh_session.cmd(command)
405bbbd9b6eSWillian Rampazzo        stdout_lines = [line.rstrip() for line
406bbbd9b6eSWillian Rampazzo                        in result.stdout_text.splitlines()]
407bbbd9b6eSWillian Rampazzo        for line in stdout_lines:
408bbbd9b6eSWillian Rampazzo            self.ssh_logger.info(line)
409bbbd9b6eSWillian Rampazzo        stderr_lines = [line.rstrip() for line
410bbbd9b6eSWillian Rampazzo                        in result.stderr_text.splitlines()]
411bbbd9b6eSWillian Rampazzo        for line in stderr_lines:
412bbbd9b6eSWillian Rampazzo            self.ssh_logger.warning(line)
413bbbd9b6eSWillian Rampazzo
414bbbd9b6eSWillian Rampazzo        self.assertEqual(result.exit_status, 0,
415bbbd9b6eSWillian Rampazzo                         f'Guest command failed: {command}')
416bbbd9b6eSWillian Rampazzo        return stdout_lines, stderr_lines
417bbbd9b6eSWillian Rampazzo
418ca3b0dc3SThomas Huth    def ssh_command_output_contains(self, cmd, exp):
419ca3b0dc3SThomas Huth        stdout, _ = self.ssh_command(cmd)
420ca3b0dc3SThomas Huth        for line in stdout:
421ca3b0dc3SThomas Huth            if exp in line:
422ca3b0dc3SThomas Huth                break
423ca3b0dc3SThomas Huth        else:
424ca3b0dc3SThomas Huth            self.fail('"%s" output does not contain "%s"' % (cmd, exp))
425