1ff2ebff0SFam Zheng# 2ff2ebff0SFam Zheng# VM testing base class 3ff2ebff0SFam Zheng# 48dd38334SGerd Hoffmann# Copyright 2017-2019 Red Hat Inc. 5ff2ebff0SFam Zheng# 6ff2ebff0SFam Zheng# Authors: 7ff2ebff0SFam Zheng# Fam Zheng <famz@redhat.com> 88dd38334SGerd Hoffmann# Gerd Hoffmann <kraxel@redhat.com> 9ff2ebff0SFam Zheng# 10ff2ebff0SFam Zheng# This code is licensed under the GPL version 2 or later. See 11ff2ebff0SFam Zheng# the COPYING file in the top-level directory. 12ff2ebff0SFam Zheng# 13ff2ebff0SFam Zheng 14ff2ebff0SFam Zhengimport os 158dd38334SGerd Hoffmannimport re 16ff2ebff0SFam Zhengimport sys 178dd38334SGerd Hoffmannimport socket 18ff2ebff0SFam Zhengimport logging 19ff2ebff0SFam Zhengimport time 20ff2ebff0SFam Zhengimport datetime 218f8fd9edSCleber Rosasys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) 228b272e00SWainer dos Santos Moschettafrom qemu.accel import kvm_available 23abf0bf99SJohn Snowfrom qemu.machine import QEMUMachine 24ff2ebff0SFam Zhengimport subprocess 25ff2ebff0SFam Zhengimport hashlib 26ff2ebff0SFam Zhengimport optparse 27ff2ebff0SFam Zhengimport atexit 28ff2ebff0SFam Zhengimport tempfile 29ff2ebff0SFam Zhengimport shutil 30ff2ebff0SFam Zhengimport multiprocessing 31ff2ebff0SFam Zhengimport traceback 32ff2ebff0SFam Zheng 33ff2ebff0SFam ZhengSSH_KEY = open(os.path.join(os.path.dirname(__file__), 34ff2ebff0SFam Zheng "..", "keys", "id_rsa")).read() 35ff2ebff0SFam ZhengSSH_PUB_KEY = open(os.path.join(os.path.dirname(__file__), 36ff2ebff0SFam Zheng "..", "keys", "id_rsa.pub")).read() 37ff2ebff0SFam Zheng 38ff2ebff0SFam Zhengclass BaseVM(object): 39ff2ebff0SFam Zheng GUEST_USER = "qemu" 40ff2ebff0SFam Zheng GUEST_PASS = "qemupass" 41ff2ebff0SFam Zheng ROOT_PASS = "qemupass" 42ff2ebff0SFam Zheng 43b08ba163SGerd Hoffmann envvars = [ 44b08ba163SGerd Hoffmann "https_proxy", 45b08ba163SGerd Hoffmann "http_proxy", 46b08ba163SGerd Hoffmann "ftp_proxy", 47b08ba163SGerd Hoffmann "no_proxy", 48b08ba163SGerd Hoffmann ] 49b08ba163SGerd Hoffmann 50ff2ebff0SFam Zheng # The script to run in the guest that builds QEMU 51ff2ebff0SFam Zheng BUILD_SCRIPT = "" 52ff2ebff0SFam Zheng # The guest name, to be overridden by subclasses 53ff2ebff0SFam Zheng name = "#base" 5431719c37SPhilippe Mathieu-Daudé # The guest architecture, to be overridden by subclasses 5531719c37SPhilippe Mathieu-Daudé arch = "#arch" 56b3f94b2fSGerd Hoffmann # command to halt the guest, can be overridden by subclasses 57b3f94b2fSGerd Hoffmann poweroff = "poweroff" 585b790481SEduardo Habkost # enable IPv6 networking 595b790481SEduardo Habkost ipv6 = True 60ff2ebff0SFam Zheng def __init__(self, debug=False, vcpus=None): 61ff2ebff0SFam Zheng self._guest = None 62ff2ebff0SFam Zheng self._tmpdir = os.path.realpath(tempfile.mkdtemp(prefix="vm-test-", 63ff2ebff0SFam Zheng suffix=".tmp", 64ff2ebff0SFam Zheng dir=".")) 65ff2ebff0SFam Zheng atexit.register(shutil.rmtree, self._tmpdir) 66ff2ebff0SFam Zheng 67ff2ebff0SFam Zheng self._ssh_key_file = os.path.join(self._tmpdir, "id_rsa") 68ff2ebff0SFam Zheng open(self._ssh_key_file, "w").write(SSH_KEY) 69ff2ebff0SFam Zheng subprocess.check_call(["chmod", "600", self._ssh_key_file]) 70ff2ebff0SFam Zheng 71ff2ebff0SFam Zheng self._ssh_pub_key_file = os.path.join(self._tmpdir, "id_rsa.pub") 72ff2ebff0SFam Zheng open(self._ssh_pub_key_file, "w").write(SSH_PUB_KEY) 73ff2ebff0SFam Zheng 74ff2ebff0SFam Zheng self.debug = debug 75ff2ebff0SFam Zheng self._stderr = sys.stderr 76ff2ebff0SFam Zheng self._devnull = open(os.devnull, "w") 77ff2ebff0SFam Zheng if self.debug: 78ff2ebff0SFam Zheng self._stdout = sys.stdout 79ff2ebff0SFam Zheng else: 80ff2ebff0SFam Zheng self._stdout = self._devnull 81ff2ebff0SFam Zheng self._args = [ \ 82eb2712f5SPeter Maydell "-nodefaults", "-m", "4G", 83b33bd859SPeter Maydell "-cpu", "max", 845b790481SEduardo Habkost "-netdev", "user,id=vnet,hostfwd=:127.0.0.1:0-:22" + 855b790481SEduardo Habkost (",ipv6=no" if not self.ipv6 else ""), 86ff2ebff0SFam Zheng "-device", "virtio-net-pci,netdev=vnet", 878dd38334SGerd Hoffmann "-vnc", "127.0.0.1:0,to=20"] 88071cf5a4SPhilippe Mathieu-Daudé if vcpus and vcpus > 1: 893ace9be6SGerd Hoffmann self._args += ["-smp", "%d" % vcpus] 9071531bb5SPhilippe Mathieu-Daudé if kvm_available(self.arch): 91ff2ebff0SFam Zheng self._args += ["-enable-kvm"] 92ff2ebff0SFam Zheng else: 93ff2ebff0SFam Zheng logging.info("KVM not available, not using -enable-kvm") 94ff2ebff0SFam Zheng self._data_args = [] 95ff2ebff0SFam Zheng 965b4b4865SAlex Bennée def _download_with_cache(self, url, sha256sum=None, sha512sum=None): 97ff2ebff0SFam Zheng def check_sha256sum(fname): 98ff2ebff0SFam Zheng if not sha256sum: 99ff2ebff0SFam Zheng return True 100ff2ebff0SFam Zheng checksum = subprocess.check_output(["sha256sum", fname]).split()[0] 1013ace9be6SGerd Hoffmann return sha256sum == checksum.decode("utf-8") 102ff2ebff0SFam Zheng 1035b4b4865SAlex Bennée def check_sha512sum(fname): 1045b4b4865SAlex Bennée if not sha512sum: 1055b4b4865SAlex Bennée return True 1065b4b4865SAlex Bennée checksum = subprocess.check_output(["sha512sum", fname]).split()[0] 1075b4b4865SAlex Bennée return sha512sum == checksum.decode("utf-8") 1085b4b4865SAlex Bennée 109ff2ebff0SFam Zheng cache_dir = os.path.expanduser("~/.cache/qemu-vm/download") 110ff2ebff0SFam Zheng if not os.path.exists(cache_dir): 111ff2ebff0SFam Zheng os.makedirs(cache_dir) 1123ace9be6SGerd Hoffmann fname = os.path.join(cache_dir, 1133ace9be6SGerd Hoffmann hashlib.sha1(url.encode("utf-8")).hexdigest()) 1145b4b4865SAlex Bennée if os.path.exists(fname) and check_sha256sum(fname) and check_sha512sum(fname): 115ff2ebff0SFam Zheng return fname 116ff2ebff0SFam Zheng logging.debug("Downloading %s to %s...", url, fname) 117ff2ebff0SFam Zheng subprocess.check_call(["wget", "-c", url, "-O", fname + ".download"], 118ff2ebff0SFam Zheng stdout=self._stdout, stderr=self._stderr) 119ff2ebff0SFam Zheng os.rename(fname + ".download", fname) 120ff2ebff0SFam Zheng return fname 121ff2ebff0SFam Zheng 122796471e9SGerd Hoffmann def _ssh_do(self, user, cmd, check): 123*89adc5b9SRobert Foley ssh_cmd = ["ssh", 124*89adc5b9SRobert Foley "-t", 125ff2ebff0SFam Zheng "-o", "StrictHostKeyChecking=no", 126ff2ebff0SFam Zheng "-o", "UserKnownHostsFile=" + os.devnull, 127ff2ebff0SFam Zheng "-o", "ConnectTimeout=1", 128ff2ebff0SFam Zheng "-p", self.ssh_port, "-i", self._ssh_key_file] 129*89adc5b9SRobert Foley # If not in debug mode, set ssh to quiet mode to 130*89adc5b9SRobert Foley # avoid printing the results of commands. 131*89adc5b9SRobert Foley if not self.debug: 132*89adc5b9SRobert Foley ssh_cmd.append("-q") 133b08ba163SGerd Hoffmann for var in self.envvars: 134b08ba163SGerd Hoffmann ssh_cmd += ['-o', "SendEnv=%s" % var ] 135ff2ebff0SFam Zheng assert not isinstance(cmd, str) 136ff2ebff0SFam Zheng ssh_cmd += ["%s@127.0.0.1" % user] + list(cmd) 137ff2ebff0SFam Zheng logging.debug("ssh_cmd: %s", " ".join(ssh_cmd)) 138726c9a3bSFam Zheng r = subprocess.call(ssh_cmd) 139ff2ebff0SFam Zheng if check and r != 0: 140ff2ebff0SFam Zheng raise Exception("SSH command failed: %s" % cmd) 141ff2ebff0SFam Zheng return r 142ff2ebff0SFam Zheng 143ff2ebff0SFam Zheng def ssh(self, *cmd): 144ff2ebff0SFam Zheng return self._ssh_do(self.GUEST_USER, cmd, False) 145ff2ebff0SFam Zheng 146ff2ebff0SFam Zheng def ssh_root(self, *cmd): 147ff2ebff0SFam Zheng return self._ssh_do("root", cmd, False) 148ff2ebff0SFam Zheng 149ff2ebff0SFam Zheng def ssh_check(self, *cmd): 150ff2ebff0SFam Zheng self._ssh_do(self.GUEST_USER, cmd, True) 151ff2ebff0SFam Zheng 152ff2ebff0SFam Zheng def ssh_root_check(self, *cmd): 153ff2ebff0SFam Zheng self._ssh_do("root", cmd, True) 154ff2ebff0SFam Zheng 155ff2ebff0SFam Zheng def build_image(self, img): 156ff2ebff0SFam Zheng raise NotImplementedError 157ff2ebff0SFam Zheng 1581e48931cSWainer dos Santos Moschetta def exec_qemu_img(self, *args): 1591e48931cSWainer dos Santos Moschetta cmd = [os.environ.get("QEMU_IMG", "qemu-img")] 1601e48931cSWainer dos Santos Moschetta cmd.extend(list(args)) 1611e48931cSWainer dos Santos Moschetta subprocess.check_call(cmd) 1621e48931cSWainer dos Santos Moschetta 163ff2ebff0SFam Zheng def add_source_dir(self, src_dir): 1643ace9be6SGerd Hoffmann name = "data-" + hashlib.sha1(src_dir.encode("utf-8")).hexdigest()[:5] 165ff2ebff0SFam Zheng tarfile = os.path.join(self._tmpdir, name + ".tar") 166ff2ebff0SFam Zheng logging.debug("Creating archive %s for src_dir dir: %s", tarfile, src_dir) 167ff2ebff0SFam Zheng subprocess.check_call(["./scripts/archive-source.sh", tarfile], 168ff2ebff0SFam Zheng cwd=src_dir, stdin=self._devnull, 169ff2ebff0SFam Zheng stdout=self._stdout, stderr=self._stderr) 170ff2ebff0SFam Zheng self._data_args += ["-drive", 171ff2ebff0SFam Zheng "file=%s,if=none,id=%s,cache=writeback,format=raw" % \ 172ff2ebff0SFam Zheng (tarfile, name), 173ff2ebff0SFam Zheng "-device", 174ff2ebff0SFam Zheng "virtio-blk,drive=%s,serial=%s,bootindex=1" % (name, name)] 175ff2ebff0SFam Zheng 176ff2ebff0SFam Zheng def boot(self, img, extra_args=[]): 177ff2ebff0SFam Zheng args = self._args + [ 178ff2ebff0SFam Zheng "-device", "VGA", 179ff2ebff0SFam Zheng "-drive", "file=%s,if=none,id=drive0,cache=writeback" % img, 180ff2ebff0SFam Zheng "-device", "virtio-blk,drive=drive0,bootindex=0"] 181ff2ebff0SFam Zheng args += self._data_args + extra_args 182ff2ebff0SFam Zheng logging.debug("QEMU args: %s", " ".join(args)) 18331719c37SPhilippe Mathieu-Daudé qemu_bin = os.environ.get("QEMU", "qemu-system-" + self.arch) 184ff2ebff0SFam Zheng guest = QEMUMachine(binary=qemu_bin, args=args) 1858dd38334SGerd Hoffmann guest.set_machine('pc') 1868dd38334SGerd Hoffmann guest.set_console() 187ff2ebff0SFam Zheng try: 188ff2ebff0SFam Zheng guest.launch() 189ff2ebff0SFam Zheng except: 190ff2ebff0SFam Zheng logging.error("Failed to launch QEMU, command line:") 191ff2ebff0SFam Zheng logging.error(" ".join([qemu_bin] + args)) 192ff2ebff0SFam Zheng logging.error("Log:") 193ff2ebff0SFam Zheng logging.error(guest.get_log()) 194ff2ebff0SFam Zheng logging.error("QEMU version >= 2.10 is required") 195ff2ebff0SFam Zheng raise 196ff2ebff0SFam Zheng atexit.register(self.shutdown) 197ff2ebff0SFam Zheng self._guest = guest 198ff2ebff0SFam Zheng usernet_info = guest.qmp("human-monitor-command", 199ff2ebff0SFam Zheng command_line="info usernet") 200ff2ebff0SFam Zheng self.ssh_port = None 201ff2ebff0SFam Zheng for l in usernet_info["return"].splitlines(): 202ff2ebff0SFam Zheng fields = l.split() 203ff2ebff0SFam Zheng if "TCP[HOST_FORWARD]" in fields and "22" in fields: 204ff2ebff0SFam Zheng self.ssh_port = l.split()[3] 205ff2ebff0SFam Zheng if not self.ssh_port: 206ff2ebff0SFam Zheng raise Exception("Cannot find ssh port from 'info usernet':\n%s" % \ 207ff2ebff0SFam Zheng usernet_info) 208ff2ebff0SFam Zheng 2098dd38334SGerd Hoffmann def console_init(self, timeout = 120): 2108dd38334SGerd Hoffmann vm = self._guest 2118dd38334SGerd Hoffmann vm.console_socket.settimeout(timeout) 2128dd38334SGerd Hoffmann 2138dd38334SGerd Hoffmann def console_log(self, text): 2148dd38334SGerd Hoffmann for line in re.split("[\r\n]", text): 2158dd38334SGerd Hoffmann # filter out terminal escape sequences 2168dd38334SGerd Hoffmann line = re.sub("\x1b\[[0-9;?]*[a-zA-Z]", "", line) 2178dd38334SGerd Hoffmann line = re.sub("\x1b\([0-9;?]*[a-zA-Z]", "", line) 2188dd38334SGerd Hoffmann # replace unprintable chars 2198dd38334SGerd Hoffmann line = re.sub("\x1b", "<esc>", line) 2208dd38334SGerd Hoffmann line = re.sub("[\x00-\x1f]", ".", line) 2218dd38334SGerd Hoffmann line = re.sub("[\x80-\xff]", ".", line) 2228dd38334SGerd Hoffmann if line == "": 2238dd38334SGerd Hoffmann continue 2248dd38334SGerd Hoffmann # log console line 2258dd38334SGerd Hoffmann sys.stderr.write("con recv: %s\n" % line) 2268dd38334SGerd Hoffmann 22760136e06SGerd Hoffmann def console_wait(self, expect, expectalt = None): 2288dd38334SGerd Hoffmann vm = self._guest 2298dd38334SGerd Hoffmann output = "" 2308dd38334SGerd Hoffmann while True: 2318dd38334SGerd Hoffmann try: 2328dd38334SGerd Hoffmann chars = vm.console_socket.recv(1) 2338dd38334SGerd Hoffmann except socket.timeout: 2348dd38334SGerd Hoffmann sys.stderr.write("console: *** read timeout ***\n") 2358dd38334SGerd Hoffmann sys.stderr.write("console: waiting for: '%s'\n" % expect) 23660136e06SGerd Hoffmann if not expectalt is None: 23760136e06SGerd Hoffmann sys.stderr.write("console: waiting for: '%s' (alt)\n" % expectalt) 2388dd38334SGerd Hoffmann sys.stderr.write("console: line buffer:\n") 2398dd38334SGerd Hoffmann sys.stderr.write("\n") 2408dd38334SGerd Hoffmann self.console_log(output.rstrip()) 2418dd38334SGerd Hoffmann sys.stderr.write("\n") 2428dd38334SGerd Hoffmann raise 2438dd38334SGerd Hoffmann output += chars.decode("latin1") 2448dd38334SGerd Hoffmann if expect in output: 2458dd38334SGerd Hoffmann break 24660136e06SGerd Hoffmann if not expectalt is None and expectalt in output: 24760136e06SGerd Hoffmann break 2488dd38334SGerd Hoffmann if "\r" in output or "\n" in output: 2498dd38334SGerd Hoffmann lines = re.split("[\r\n]", output) 2508dd38334SGerd Hoffmann output = lines.pop() 2518dd38334SGerd Hoffmann if self.debug: 2528dd38334SGerd Hoffmann self.console_log("\n".join(lines)) 2538dd38334SGerd Hoffmann if self.debug: 2548dd38334SGerd Hoffmann self.console_log(output) 25560136e06SGerd Hoffmann if not expectalt is None and expectalt in output: 25660136e06SGerd Hoffmann return False 25760136e06SGerd Hoffmann return True 2588dd38334SGerd Hoffmann 2596c4f0416SGerd Hoffmann def console_consume(self): 2606c4f0416SGerd Hoffmann vm = self._guest 2616c4f0416SGerd Hoffmann output = "" 2626c4f0416SGerd Hoffmann vm.console_socket.setblocking(0) 2636c4f0416SGerd Hoffmann while True: 2646c4f0416SGerd Hoffmann try: 2656c4f0416SGerd Hoffmann chars = vm.console_socket.recv(1) 2666c4f0416SGerd Hoffmann except: 2676c4f0416SGerd Hoffmann break 2686c4f0416SGerd Hoffmann output += chars.decode("latin1") 2696c4f0416SGerd Hoffmann if "\r" in output or "\n" in output: 2706c4f0416SGerd Hoffmann lines = re.split("[\r\n]", output) 2716c4f0416SGerd Hoffmann output = lines.pop() 2726c4f0416SGerd Hoffmann if self.debug: 2736c4f0416SGerd Hoffmann self.console_log("\n".join(lines)) 2746c4f0416SGerd Hoffmann if self.debug: 2756c4f0416SGerd Hoffmann self.console_log(output) 2766c4f0416SGerd Hoffmann vm.console_socket.setblocking(1) 2776c4f0416SGerd Hoffmann 2788dd38334SGerd Hoffmann def console_send(self, command): 2798dd38334SGerd Hoffmann vm = self._guest 2808dd38334SGerd Hoffmann if self.debug: 2818dd38334SGerd Hoffmann logline = re.sub("\n", "<enter>", command) 2828dd38334SGerd Hoffmann logline = re.sub("[\x00-\x1f]", ".", logline) 2838dd38334SGerd Hoffmann sys.stderr.write("con send: %s\n" % logline) 2848dd38334SGerd Hoffmann for char in list(command): 2858dd38334SGerd Hoffmann vm.console_socket.send(char.encode("utf-8")) 2868dd38334SGerd Hoffmann time.sleep(0.01) 2878dd38334SGerd Hoffmann 2888dd38334SGerd Hoffmann def console_wait_send(self, wait, command): 2898dd38334SGerd Hoffmann self.console_wait(wait) 2908dd38334SGerd Hoffmann self.console_send(command) 2918dd38334SGerd Hoffmann 2928dd38334SGerd Hoffmann def console_ssh_init(self, prompt, user, pw): 2938dd38334SGerd Hoffmann sshkey_cmd = "echo '%s' > .ssh/authorized_keys\n" % SSH_PUB_KEY.rstrip() 2948dd38334SGerd Hoffmann self.console_wait_send("login:", "%s\n" % user) 2958dd38334SGerd Hoffmann self.console_wait_send("Password:", "%s\n" % pw) 2968dd38334SGerd Hoffmann self.console_wait_send(prompt, "mkdir .ssh\n") 2978dd38334SGerd Hoffmann self.console_wait_send(prompt, sshkey_cmd) 2988dd38334SGerd Hoffmann self.console_wait_send(prompt, "chmod 755 .ssh\n") 2998dd38334SGerd Hoffmann self.console_wait_send(prompt, "chmod 644 .ssh/authorized_keys\n") 3008dd38334SGerd Hoffmann 3018dd38334SGerd Hoffmann def console_sshd_config(self, prompt): 3028dd38334SGerd Hoffmann self.console_wait(prompt) 3038dd38334SGerd Hoffmann self.console_send("echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config\n") 3048dd38334SGerd Hoffmann for var in self.envvars: 3058dd38334SGerd Hoffmann self.console_wait(prompt) 3068dd38334SGerd Hoffmann self.console_send("echo 'AcceptEnv %s' >> /etc/ssh/sshd_config\n" % var) 3078dd38334SGerd Hoffmann 3088dd38334SGerd Hoffmann def print_step(self, text): 3098dd38334SGerd Hoffmann sys.stderr.write("### %s ...\n" % text) 3108dd38334SGerd Hoffmann 3116b699ae1SPeter Maydell def wait_ssh(self, seconds=300): 312ff2ebff0SFam Zheng starttime = datetime.datetime.now() 313f5d3d218SPhilippe Mathieu-Daudé endtime = starttime + datetime.timedelta(seconds=seconds) 314ff2ebff0SFam Zheng guest_up = False 315f5d3d218SPhilippe Mathieu-Daudé while datetime.datetime.now() < endtime: 316ff2ebff0SFam Zheng if self.ssh("exit 0") == 0: 317ff2ebff0SFam Zheng guest_up = True 318ff2ebff0SFam Zheng break 319f5d3d218SPhilippe Mathieu-Daudé seconds = (endtime - datetime.datetime.now()).total_seconds() 320f5d3d218SPhilippe Mathieu-Daudé logging.debug("%ds before timeout", seconds) 321ff2ebff0SFam Zheng time.sleep(1) 322ff2ebff0SFam Zheng if not guest_up: 323ff2ebff0SFam Zheng raise Exception("Timeout while waiting for guest ssh") 324ff2ebff0SFam Zheng 325ff2ebff0SFam Zheng def shutdown(self): 326ff2ebff0SFam Zheng self._guest.shutdown() 327ff2ebff0SFam Zheng 328ff2ebff0SFam Zheng def wait(self): 329ff2ebff0SFam Zheng self._guest.wait() 330ff2ebff0SFam Zheng 331b3f94b2fSGerd Hoffmann def graceful_shutdown(self): 332b3f94b2fSGerd Hoffmann self.ssh_root(self.poweroff) 333b3f94b2fSGerd Hoffmann self._guest.wait() 334b3f94b2fSGerd Hoffmann 335ff2ebff0SFam Zheng def qmp(self, *args, **kwargs): 336ff2ebff0SFam Zheng return self._guest.qmp(*args, **kwargs) 337ff2ebff0SFam Zheng 33863a24c5eSPhilippe Mathieu-Daudédef parse_args(vmcls): 3398a6e007eSPhilippe Mathieu-Daudé 3408a6e007eSPhilippe Mathieu-Daudé def get_default_jobs(): 34163a24c5eSPhilippe Mathieu-Daudé if kvm_available(vmcls.arch): 3423ad3e36eSWainer dos Santos Moschetta return multiprocessing.cpu_count() // 2 3438a6e007eSPhilippe Mathieu-Daudé else: 3448a6e007eSPhilippe Mathieu-Daudé return 1 3458a6e007eSPhilippe Mathieu-Daudé 346ff2ebff0SFam Zheng parser = optparse.OptionParser( 347ff2ebff0SFam Zheng description="VM test utility. Exit codes: " 348ff2ebff0SFam Zheng "0 = success, " 349ff2ebff0SFam Zheng "1 = command line error, " 350ff2ebff0SFam Zheng "2 = environment initialization failed, " 351ff2ebff0SFam Zheng "3 = test command failed") 352ff2ebff0SFam Zheng parser.add_option("--debug", "-D", action="store_true", 353ff2ebff0SFam Zheng help="enable debug output") 35463a24c5eSPhilippe Mathieu-Daudé parser.add_option("--image", "-i", default="%s.img" % vmcls.name, 355ff2ebff0SFam Zheng help="image file name") 356ff2ebff0SFam Zheng parser.add_option("--force", "-f", action="store_true", 357ff2ebff0SFam Zheng help="force build image even if image exists") 3588a6e007eSPhilippe Mathieu-Daudé parser.add_option("--jobs", type=int, default=get_default_jobs(), 359ff2ebff0SFam Zheng help="number of virtual CPUs") 36041e3340aSPeter Maydell parser.add_option("--verbose", "-V", action="store_true", 36141e3340aSPeter Maydell help="Pass V=1 to builds within the guest") 362ff2ebff0SFam Zheng parser.add_option("--build-image", "-b", action="store_true", 363ff2ebff0SFam Zheng help="build image") 364ff2ebff0SFam Zheng parser.add_option("--build-qemu", 365ff2ebff0SFam Zheng help="build QEMU from source in guest") 3665c2ec9b6SAlex Bennée parser.add_option("--build-target", 3675c2ec9b6SAlex Bennée help="QEMU build target", default="check") 368ff2ebff0SFam Zheng parser.add_option("--interactive", "-I", action="store_true", 369ff2ebff0SFam Zheng help="Interactively run command") 370983c2a77SFam Zheng parser.add_option("--snapshot", "-s", action="store_true", 371983c2a77SFam Zheng help="run tests with a snapshot") 372ff2ebff0SFam Zheng parser.disable_interspersed_args() 373ff2ebff0SFam Zheng return parser.parse_args() 374ff2ebff0SFam Zheng 375ff2ebff0SFam Zhengdef main(vmcls): 376ff2ebff0SFam Zheng try: 37763a24c5eSPhilippe Mathieu-Daudé args, argv = parse_args(vmcls) 378ff2ebff0SFam Zheng if not argv and not args.build_qemu and not args.build_image: 379f03868bdSEduardo Habkost print("Nothing to do?") 380ff2ebff0SFam Zheng return 1 381fb3b4e6dSEduardo Habkost logging.basicConfig(level=(logging.DEBUG if args.debug 382fb3b4e6dSEduardo Habkost else logging.WARN)) 383ff2ebff0SFam Zheng vm = vmcls(debug=args.debug, vcpus=args.jobs) 384ff2ebff0SFam Zheng if args.build_image: 385ff2ebff0SFam Zheng if os.path.exists(args.image) and not args.force: 386ff2ebff0SFam Zheng sys.stderr.writelines(["Image file exists: %s\n" % args.image, 387ff2ebff0SFam Zheng "Use --force option to overwrite\n"]) 388ff2ebff0SFam Zheng return 1 389ff2ebff0SFam Zheng return vm.build_image(args.image) 390ff2ebff0SFam Zheng if args.build_qemu: 391ff2ebff0SFam Zheng vm.add_source_dir(args.build_qemu) 392ff2ebff0SFam Zheng cmd = [vm.BUILD_SCRIPT.format( 393ff2ebff0SFam Zheng configure_opts = " ".join(argv), 3943ace9be6SGerd Hoffmann jobs=int(args.jobs), 3955c2ec9b6SAlex Bennée target=args.build_target, 39641e3340aSPeter Maydell verbose = "V=1" if args.verbose else "")] 397ff2ebff0SFam Zheng else: 398ff2ebff0SFam Zheng cmd = argv 399983c2a77SFam Zheng img = args.image 400983c2a77SFam Zheng if args.snapshot: 401983c2a77SFam Zheng img += ",snapshot=on" 402983c2a77SFam Zheng vm.boot(img) 403ff2ebff0SFam Zheng vm.wait_ssh() 404ff2ebff0SFam Zheng except Exception as e: 405ff2ebff0SFam Zheng if isinstance(e, SystemExit) and e.code == 0: 406ff2ebff0SFam Zheng return 0 407ff2ebff0SFam Zheng sys.stderr.write("Failed to prepare guest environment\n") 408ff2ebff0SFam Zheng traceback.print_exc() 409ff2ebff0SFam Zheng return 2 410ff2ebff0SFam Zheng 411b3f94b2fSGerd Hoffmann exitcode = 0 412ff2ebff0SFam Zheng if vm.ssh(*cmd) != 0: 413b3f94b2fSGerd Hoffmann exitcode = 3 414bcc388dfSAlex Bennée if args.interactive: 415b3f94b2fSGerd Hoffmann vm.ssh() 416b3f94b2fSGerd Hoffmann 417b3f94b2fSGerd Hoffmann if not args.snapshot: 418b3f94b2fSGerd Hoffmann vm.graceful_shutdown() 419b3f94b2fSGerd Hoffmann 420b3f94b2fSGerd Hoffmann return exitcode 421