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