xref: /openbmc/qemu/tests/vm/basevm.py (revision 6c4f0416be5805962a77af240a41fae5eaffda8b)
1ff2ebff0SFam Zheng#!/usr/bin/env python
2ff2ebff0SFam Zheng#
3ff2ebff0SFam Zheng# VM testing base class
4ff2ebff0SFam Zheng#
58dd38334SGerd Hoffmann# Copyright 2017-2019 Red Hat Inc.
6ff2ebff0SFam Zheng#
7ff2ebff0SFam Zheng# Authors:
8ff2ebff0SFam Zheng#  Fam Zheng <famz@redhat.com>
98dd38334SGerd Hoffmann#  Gerd Hoffmann <kraxel@redhat.com>
10ff2ebff0SFam Zheng#
11ff2ebff0SFam Zheng# This code is licensed under the GPL version 2 or later.  See
12ff2ebff0SFam Zheng# the COPYING file in the top-level directory.
13ff2ebff0SFam Zheng#
14ff2ebff0SFam Zheng
15f03868bdSEduardo Habkostfrom __future__ import print_function
16ff2ebff0SFam Zhengimport os
178dd38334SGerd Hoffmannimport re
18ff2ebff0SFam Zhengimport sys
198dd38334SGerd Hoffmannimport socket
20ff2ebff0SFam Zhengimport logging
21ff2ebff0SFam Zhengimport time
22ff2ebff0SFam Zhengimport datetime
238f8fd9edSCleber Rosasys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
24abf0bf99SJohn Snowfrom qemu import kvm_available
25abf0bf99SJohn Snowfrom qemu.machine import QEMUMachine
26ff2ebff0SFam Zhengimport subprocess
27ff2ebff0SFam Zhengimport hashlib
28ff2ebff0SFam Zhengimport optparse
29ff2ebff0SFam Zhengimport atexit
30ff2ebff0SFam Zhengimport tempfile
31ff2ebff0SFam Zhengimport shutil
32ff2ebff0SFam Zhengimport multiprocessing
33ff2ebff0SFam Zhengimport traceback
34ff2ebff0SFam Zheng
35ff2ebff0SFam ZhengSSH_KEY = open(os.path.join(os.path.dirname(__file__),
36ff2ebff0SFam Zheng               "..", "keys", "id_rsa")).read()
37ff2ebff0SFam ZhengSSH_PUB_KEY = open(os.path.join(os.path.dirname(__file__),
38ff2ebff0SFam Zheng                   "..", "keys", "id_rsa.pub")).read()
39ff2ebff0SFam Zheng
40ff2ebff0SFam Zhengclass BaseVM(object):
41ff2ebff0SFam Zheng    GUEST_USER = "qemu"
42ff2ebff0SFam Zheng    GUEST_PASS = "qemupass"
43ff2ebff0SFam Zheng    ROOT_PASS = "qemupass"
44ff2ebff0SFam Zheng
45b08ba163SGerd Hoffmann    envvars = [
46b08ba163SGerd Hoffmann        "https_proxy",
47b08ba163SGerd Hoffmann        "http_proxy",
48b08ba163SGerd Hoffmann        "ftp_proxy",
49b08ba163SGerd Hoffmann        "no_proxy",
50b08ba163SGerd Hoffmann    ]
51b08ba163SGerd Hoffmann
52ff2ebff0SFam Zheng    # The script to run in the guest that builds QEMU
53ff2ebff0SFam Zheng    BUILD_SCRIPT = ""
54ff2ebff0SFam Zheng    # The guest name, to be overridden by subclasses
55ff2ebff0SFam Zheng    name = "#base"
5631719c37SPhilippe Mathieu-Daudé    # The guest architecture, to be overridden by subclasses
5731719c37SPhilippe Mathieu-Daudé    arch = "#arch"
58b3f94b2fSGerd Hoffmann    # command to halt the guest, can be overridden by subclasses
59b3f94b2fSGerd Hoffmann    poweroff = "poweroff"
605b790481SEduardo Habkost    # enable IPv6 networking
615b790481SEduardo Habkost    ipv6 = True
62ff2ebff0SFam Zheng    def __init__(self, debug=False, vcpus=None):
63ff2ebff0SFam Zheng        self._guest = None
64ff2ebff0SFam Zheng        self._tmpdir = os.path.realpath(tempfile.mkdtemp(prefix="vm-test-",
65ff2ebff0SFam Zheng                                                         suffix=".tmp",
66ff2ebff0SFam Zheng                                                         dir="."))
67ff2ebff0SFam Zheng        atexit.register(shutil.rmtree, self._tmpdir)
68ff2ebff0SFam Zheng
69ff2ebff0SFam Zheng        self._ssh_key_file = os.path.join(self._tmpdir, "id_rsa")
70ff2ebff0SFam Zheng        open(self._ssh_key_file, "w").write(SSH_KEY)
71ff2ebff0SFam Zheng        subprocess.check_call(["chmod", "600", self._ssh_key_file])
72ff2ebff0SFam Zheng
73ff2ebff0SFam Zheng        self._ssh_pub_key_file = os.path.join(self._tmpdir, "id_rsa.pub")
74ff2ebff0SFam Zheng        open(self._ssh_pub_key_file, "w").write(SSH_PUB_KEY)
75ff2ebff0SFam Zheng
76ff2ebff0SFam Zheng        self.debug = debug
77ff2ebff0SFam Zheng        self._stderr = sys.stderr
78ff2ebff0SFam Zheng        self._devnull = open(os.devnull, "w")
79ff2ebff0SFam Zheng        if self.debug:
80ff2ebff0SFam Zheng            self._stdout = sys.stdout
81ff2ebff0SFam Zheng        else:
82ff2ebff0SFam Zheng            self._stdout = self._devnull
83ff2ebff0SFam Zheng        self._args = [ \
84eb2712f5SPeter Maydell            "-nodefaults", "-m", "4G",
85b33bd859SPeter Maydell            "-cpu", "max",
865b790481SEduardo Habkost            "-netdev", "user,id=vnet,hostfwd=:127.0.0.1:0-:22" +
875b790481SEduardo Habkost                       (",ipv6=no" if not self.ipv6 else ""),
88ff2ebff0SFam Zheng            "-device", "virtio-net-pci,netdev=vnet",
898dd38334SGerd Hoffmann            "-vnc", "127.0.0.1:0,to=20"]
90071cf5a4SPhilippe Mathieu-Daudé        if vcpus and vcpus > 1:
913ace9be6SGerd Hoffmann            self._args += ["-smp", "%d" % vcpus]
9271531bb5SPhilippe Mathieu-Daudé        if kvm_available(self.arch):
93ff2ebff0SFam Zheng            self._args += ["-enable-kvm"]
94ff2ebff0SFam Zheng        else:
95ff2ebff0SFam Zheng            logging.info("KVM not available, not using -enable-kvm")
96ff2ebff0SFam Zheng        self._data_args = []
97ff2ebff0SFam Zheng
98ff2ebff0SFam Zheng    def _download_with_cache(self, url, sha256sum=None):
99ff2ebff0SFam Zheng        def check_sha256sum(fname):
100ff2ebff0SFam Zheng            if not sha256sum:
101ff2ebff0SFam Zheng                return True
102ff2ebff0SFam Zheng            checksum = subprocess.check_output(["sha256sum", fname]).split()[0]
1033ace9be6SGerd Hoffmann            return sha256sum == checksum.decode("utf-8")
104ff2ebff0SFam Zheng
105ff2ebff0SFam Zheng        cache_dir = os.path.expanduser("~/.cache/qemu-vm/download")
106ff2ebff0SFam Zheng        if not os.path.exists(cache_dir):
107ff2ebff0SFam Zheng            os.makedirs(cache_dir)
1083ace9be6SGerd Hoffmann        fname = os.path.join(cache_dir,
1093ace9be6SGerd Hoffmann                             hashlib.sha1(url.encode("utf-8")).hexdigest())
110ff2ebff0SFam Zheng        if os.path.exists(fname) and check_sha256sum(fname):
111ff2ebff0SFam Zheng            return fname
112ff2ebff0SFam Zheng        logging.debug("Downloading %s to %s...", url, fname)
113ff2ebff0SFam Zheng        subprocess.check_call(["wget", "-c", url, "-O", fname + ".download"],
114ff2ebff0SFam Zheng                              stdout=self._stdout, stderr=self._stderr)
115ff2ebff0SFam Zheng        os.rename(fname + ".download", fname)
116ff2ebff0SFam Zheng        return fname
117ff2ebff0SFam Zheng
118796471e9SGerd Hoffmann    def _ssh_do(self, user, cmd, check):
119796471e9SGerd Hoffmann        ssh_cmd = ["ssh", "-q", "-t",
120ff2ebff0SFam Zheng                   "-o", "StrictHostKeyChecking=no",
121ff2ebff0SFam Zheng                   "-o", "UserKnownHostsFile=" + os.devnull,
122ff2ebff0SFam Zheng                   "-o", "ConnectTimeout=1",
123ff2ebff0SFam Zheng                   "-p", self.ssh_port, "-i", self._ssh_key_file]
124b08ba163SGerd Hoffmann        for var in self.envvars:
125b08ba163SGerd Hoffmann            ssh_cmd += ['-o', "SendEnv=%s" % var ]
126ff2ebff0SFam Zheng        assert not isinstance(cmd, str)
127ff2ebff0SFam Zheng        ssh_cmd += ["%s@127.0.0.1" % user] + list(cmd)
128ff2ebff0SFam Zheng        logging.debug("ssh_cmd: %s", " ".join(ssh_cmd))
129726c9a3bSFam Zheng        r = subprocess.call(ssh_cmd)
130ff2ebff0SFam Zheng        if check and r != 0:
131ff2ebff0SFam Zheng            raise Exception("SSH command failed: %s" % cmd)
132ff2ebff0SFam Zheng        return r
133ff2ebff0SFam Zheng
134ff2ebff0SFam Zheng    def ssh(self, *cmd):
135ff2ebff0SFam Zheng        return self._ssh_do(self.GUEST_USER, cmd, False)
136ff2ebff0SFam Zheng
137ff2ebff0SFam Zheng    def ssh_root(self, *cmd):
138ff2ebff0SFam Zheng        return self._ssh_do("root", cmd, False)
139ff2ebff0SFam Zheng
140ff2ebff0SFam Zheng    def ssh_check(self, *cmd):
141ff2ebff0SFam Zheng        self._ssh_do(self.GUEST_USER, cmd, True)
142ff2ebff0SFam Zheng
143ff2ebff0SFam Zheng    def ssh_root_check(self, *cmd):
144ff2ebff0SFam Zheng        self._ssh_do("root", cmd, True)
145ff2ebff0SFam Zheng
146ff2ebff0SFam Zheng    def build_image(self, img):
147ff2ebff0SFam Zheng        raise NotImplementedError
148ff2ebff0SFam Zheng
149ff2ebff0SFam Zheng    def add_source_dir(self, src_dir):
1503ace9be6SGerd Hoffmann        name = "data-" + hashlib.sha1(src_dir.encode("utf-8")).hexdigest()[:5]
151ff2ebff0SFam Zheng        tarfile = os.path.join(self._tmpdir, name + ".tar")
152ff2ebff0SFam Zheng        logging.debug("Creating archive %s for src_dir dir: %s", tarfile, src_dir)
153ff2ebff0SFam Zheng        subprocess.check_call(["./scripts/archive-source.sh", tarfile],
154ff2ebff0SFam Zheng                              cwd=src_dir, stdin=self._devnull,
155ff2ebff0SFam Zheng                              stdout=self._stdout, stderr=self._stderr)
156ff2ebff0SFam Zheng        self._data_args += ["-drive",
157ff2ebff0SFam Zheng                            "file=%s,if=none,id=%s,cache=writeback,format=raw" % \
158ff2ebff0SFam Zheng                                    (tarfile, name),
159ff2ebff0SFam Zheng                            "-device",
160ff2ebff0SFam Zheng                            "virtio-blk,drive=%s,serial=%s,bootindex=1" % (name, name)]
161ff2ebff0SFam Zheng
162ff2ebff0SFam Zheng    def boot(self, img, extra_args=[]):
163ff2ebff0SFam Zheng        args = self._args + [
164ff2ebff0SFam Zheng            "-device", "VGA",
165ff2ebff0SFam Zheng            "-drive", "file=%s,if=none,id=drive0,cache=writeback" % img,
166ff2ebff0SFam Zheng            "-device", "virtio-blk,drive=drive0,bootindex=0"]
167ff2ebff0SFam Zheng        args += self._data_args + extra_args
168ff2ebff0SFam Zheng        logging.debug("QEMU args: %s", " ".join(args))
16931719c37SPhilippe Mathieu-Daudé        qemu_bin = os.environ.get("QEMU", "qemu-system-" + self.arch)
170ff2ebff0SFam Zheng        guest = QEMUMachine(binary=qemu_bin, args=args)
1718dd38334SGerd Hoffmann        guest.set_machine('pc')
1728dd38334SGerd Hoffmann        guest.set_console()
173ff2ebff0SFam Zheng        try:
174ff2ebff0SFam Zheng            guest.launch()
175ff2ebff0SFam Zheng        except:
176ff2ebff0SFam Zheng            logging.error("Failed to launch QEMU, command line:")
177ff2ebff0SFam Zheng            logging.error(" ".join([qemu_bin] + args))
178ff2ebff0SFam Zheng            logging.error("Log:")
179ff2ebff0SFam Zheng            logging.error(guest.get_log())
180ff2ebff0SFam Zheng            logging.error("QEMU version >= 2.10 is required")
181ff2ebff0SFam Zheng            raise
182ff2ebff0SFam Zheng        atexit.register(self.shutdown)
183ff2ebff0SFam Zheng        self._guest = guest
184ff2ebff0SFam Zheng        usernet_info = guest.qmp("human-monitor-command",
185ff2ebff0SFam Zheng                                 command_line="info usernet")
186ff2ebff0SFam Zheng        self.ssh_port = None
187ff2ebff0SFam Zheng        for l in usernet_info["return"].splitlines():
188ff2ebff0SFam Zheng            fields = l.split()
189ff2ebff0SFam Zheng            if "TCP[HOST_FORWARD]" in fields and "22" in fields:
190ff2ebff0SFam Zheng                self.ssh_port = l.split()[3]
191ff2ebff0SFam Zheng        if not self.ssh_port:
192ff2ebff0SFam Zheng            raise Exception("Cannot find ssh port from 'info usernet':\n%s" % \
193ff2ebff0SFam Zheng                            usernet_info)
194ff2ebff0SFam Zheng
1958dd38334SGerd Hoffmann    def console_init(self, timeout = 120):
1968dd38334SGerd Hoffmann        vm = self._guest
1978dd38334SGerd Hoffmann        vm.console_socket.settimeout(timeout)
1988dd38334SGerd Hoffmann
1998dd38334SGerd Hoffmann    def console_log(self, text):
2008dd38334SGerd Hoffmann        for line in re.split("[\r\n]", text):
2018dd38334SGerd Hoffmann            # filter out terminal escape sequences
2028dd38334SGerd Hoffmann            line = re.sub("\x1b\[[0-9;?]*[a-zA-Z]", "", line)
2038dd38334SGerd Hoffmann            line = re.sub("\x1b\([0-9;?]*[a-zA-Z]", "", line)
2048dd38334SGerd Hoffmann            # replace unprintable chars
2058dd38334SGerd Hoffmann            line = re.sub("\x1b", "<esc>", line)
2068dd38334SGerd Hoffmann            line = re.sub("[\x00-\x1f]", ".", line)
2078dd38334SGerd Hoffmann            line = re.sub("[\x80-\xff]", ".", line)
2088dd38334SGerd Hoffmann            if line == "":
2098dd38334SGerd Hoffmann                continue
2108dd38334SGerd Hoffmann            # log console line
2118dd38334SGerd Hoffmann            sys.stderr.write("con recv: %s\n" % line)
2128dd38334SGerd Hoffmann
21360136e06SGerd Hoffmann    def console_wait(self, expect, expectalt = None):
2148dd38334SGerd Hoffmann        vm = self._guest
2158dd38334SGerd Hoffmann        output = ""
2168dd38334SGerd Hoffmann        while True:
2178dd38334SGerd Hoffmann            try:
2188dd38334SGerd Hoffmann                chars = vm.console_socket.recv(1)
2198dd38334SGerd Hoffmann            except socket.timeout:
2208dd38334SGerd Hoffmann                sys.stderr.write("console: *** read timeout ***\n")
2218dd38334SGerd Hoffmann                sys.stderr.write("console: waiting for: '%s'\n" % expect)
22260136e06SGerd Hoffmann                if not expectalt is None:
22360136e06SGerd Hoffmann                    sys.stderr.write("console: waiting for: '%s' (alt)\n" % expectalt)
2248dd38334SGerd Hoffmann                sys.stderr.write("console: line buffer:\n")
2258dd38334SGerd Hoffmann                sys.stderr.write("\n")
2268dd38334SGerd Hoffmann                self.console_log(output.rstrip())
2278dd38334SGerd Hoffmann                sys.stderr.write("\n")
2288dd38334SGerd Hoffmann                raise
2298dd38334SGerd Hoffmann            output += chars.decode("latin1")
2308dd38334SGerd Hoffmann            if expect in output:
2318dd38334SGerd Hoffmann                break
23260136e06SGerd Hoffmann            if not expectalt is None and expectalt in output:
23360136e06SGerd Hoffmann                break
2348dd38334SGerd Hoffmann            if "\r" in output or "\n" in output:
2358dd38334SGerd Hoffmann                lines = re.split("[\r\n]", output)
2368dd38334SGerd Hoffmann                output = lines.pop()
2378dd38334SGerd Hoffmann                if self.debug:
2388dd38334SGerd Hoffmann                    self.console_log("\n".join(lines))
2398dd38334SGerd Hoffmann        if self.debug:
2408dd38334SGerd Hoffmann            self.console_log(output)
24160136e06SGerd Hoffmann        if not expectalt is None and expectalt in output:
24260136e06SGerd Hoffmann            return False
24360136e06SGerd Hoffmann        return True
2448dd38334SGerd Hoffmann
245*6c4f0416SGerd Hoffmann    def console_consume(self):
246*6c4f0416SGerd Hoffmann        vm = self._guest
247*6c4f0416SGerd Hoffmann        output = ""
248*6c4f0416SGerd Hoffmann        vm.console_socket.setblocking(0)
249*6c4f0416SGerd Hoffmann        while True:
250*6c4f0416SGerd Hoffmann            try:
251*6c4f0416SGerd Hoffmann                chars = vm.console_socket.recv(1)
252*6c4f0416SGerd Hoffmann            except:
253*6c4f0416SGerd Hoffmann                break
254*6c4f0416SGerd Hoffmann            output += chars.decode("latin1")
255*6c4f0416SGerd Hoffmann            if "\r" in output or "\n" in output:
256*6c4f0416SGerd Hoffmann                lines = re.split("[\r\n]", output)
257*6c4f0416SGerd Hoffmann                output = lines.pop()
258*6c4f0416SGerd Hoffmann                if self.debug:
259*6c4f0416SGerd Hoffmann                    self.console_log("\n".join(lines))
260*6c4f0416SGerd Hoffmann        if self.debug:
261*6c4f0416SGerd Hoffmann            self.console_log(output)
262*6c4f0416SGerd Hoffmann        vm.console_socket.setblocking(1)
263*6c4f0416SGerd Hoffmann
2648dd38334SGerd Hoffmann    def console_send(self, command):
2658dd38334SGerd Hoffmann        vm = self._guest
2668dd38334SGerd Hoffmann        if self.debug:
2678dd38334SGerd Hoffmann            logline = re.sub("\n", "<enter>", command)
2688dd38334SGerd Hoffmann            logline = re.sub("[\x00-\x1f]", ".", logline)
2698dd38334SGerd Hoffmann            sys.stderr.write("con send: %s\n" % logline)
2708dd38334SGerd Hoffmann        for char in list(command):
2718dd38334SGerd Hoffmann            vm.console_socket.send(char.encode("utf-8"))
2728dd38334SGerd Hoffmann            time.sleep(0.01)
2738dd38334SGerd Hoffmann
2748dd38334SGerd Hoffmann    def console_wait_send(self, wait, command):
2758dd38334SGerd Hoffmann        self.console_wait(wait)
2768dd38334SGerd Hoffmann        self.console_send(command)
2778dd38334SGerd Hoffmann
2788dd38334SGerd Hoffmann    def console_ssh_init(self, prompt, user, pw):
2798dd38334SGerd Hoffmann        sshkey_cmd = "echo '%s' > .ssh/authorized_keys\n" % SSH_PUB_KEY.rstrip()
2808dd38334SGerd Hoffmann        self.console_wait_send("login:",    "%s\n" % user)
2818dd38334SGerd Hoffmann        self.console_wait_send("Password:", "%s\n" % pw)
2828dd38334SGerd Hoffmann        self.console_wait_send(prompt,      "mkdir .ssh\n")
2838dd38334SGerd Hoffmann        self.console_wait_send(prompt,      sshkey_cmd)
2848dd38334SGerd Hoffmann        self.console_wait_send(prompt,      "chmod 755 .ssh\n")
2858dd38334SGerd Hoffmann        self.console_wait_send(prompt,      "chmod 644 .ssh/authorized_keys\n")
2868dd38334SGerd Hoffmann
2878dd38334SGerd Hoffmann    def console_sshd_config(self, prompt):
2888dd38334SGerd Hoffmann        self.console_wait(prompt)
2898dd38334SGerd Hoffmann        self.console_send("echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config\n")
2908dd38334SGerd Hoffmann        for var in self.envvars:
2918dd38334SGerd Hoffmann            self.console_wait(prompt)
2928dd38334SGerd Hoffmann            self.console_send("echo 'AcceptEnv %s' >> /etc/ssh/sshd_config\n" % var)
2938dd38334SGerd Hoffmann
2948dd38334SGerd Hoffmann    def print_step(self, text):
2958dd38334SGerd Hoffmann        sys.stderr.write("### %s ...\n" % text)
2968dd38334SGerd Hoffmann
2976b699ae1SPeter Maydell    def wait_ssh(self, seconds=300):
298ff2ebff0SFam Zheng        starttime = datetime.datetime.now()
299f5d3d218SPhilippe Mathieu-Daudé        endtime = starttime + datetime.timedelta(seconds=seconds)
300ff2ebff0SFam Zheng        guest_up = False
301f5d3d218SPhilippe Mathieu-Daudé        while datetime.datetime.now() < endtime:
302ff2ebff0SFam Zheng            if self.ssh("exit 0") == 0:
303ff2ebff0SFam Zheng                guest_up = True
304ff2ebff0SFam Zheng                break
305f5d3d218SPhilippe Mathieu-Daudé            seconds = (endtime - datetime.datetime.now()).total_seconds()
306f5d3d218SPhilippe Mathieu-Daudé            logging.debug("%ds before timeout", seconds)
307ff2ebff0SFam Zheng            time.sleep(1)
308ff2ebff0SFam Zheng        if not guest_up:
309ff2ebff0SFam Zheng            raise Exception("Timeout while waiting for guest ssh")
310ff2ebff0SFam Zheng
311ff2ebff0SFam Zheng    def shutdown(self):
312ff2ebff0SFam Zheng        self._guest.shutdown()
313ff2ebff0SFam Zheng
314ff2ebff0SFam Zheng    def wait(self):
315ff2ebff0SFam Zheng        self._guest.wait()
316ff2ebff0SFam Zheng
317b3f94b2fSGerd Hoffmann    def graceful_shutdown(self):
318b3f94b2fSGerd Hoffmann        self.ssh_root(self.poweroff)
319b3f94b2fSGerd Hoffmann        self._guest.wait()
320b3f94b2fSGerd Hoffmann
321ff2ebff0SFam Zheng    def qmp(self, *args, **kwargs):
322ff2ebff0SFam Zheng        return self._guest.qmp(*args, **kwargs)
323ff2ebff0SFam Zheng
32463a24c5eSPhilippe Mathieu-Daudédef parse_args(vmcls):
3258a6e007eSPhilippe Mathieu-Daudé
3268a6e007eSPhilippe Mathieu-Daudé    def get_default_jobs():
32763a24c5eSPhilippe Mathieu-Daudé        if kvm_available(vmcls.arch):
3283ad3e36eSWainer dos Santos Moschetta            return multiprocessing.cpu_count() // 2
3298a6e007eSPhilippe Mathieu-Daudé        else:
3308a6e007eSPhilippe Mathieu-Daudé            return 1
3318a6e007eSPhilippe Mathieu-Daudé
332ff2ebff0SFam Zheng    parser = optparse.OptionParser(
333ff2ebff0SFam Zheng        description="VM test utility.  Exit codes: "
334ff2ebff0SFam Zheng                    "0 = success, "
335ff2ebff0SFam Zheng                    "1 = command line error, "
336ff2ebff0SFam Zheng                    "2 = environment initialization failed, "
337ff2ebff0SFam Zheng                    "3 = test command failed")
338ff2ebff0SFam Zheng    parser.add_option("--debug", "-D", action="store_true",
339ff2ebff0SFam Zheng                      help="enable debug output")
34063a24c5eSPhilippe Mathieu-Daudé    parser.add_option("--image", "-i", default="%s.img" % vmcls.name,
341ff2ebff0SFam Zheng                      help="image file name")
342ff2ebff0SFam Zheng    parser.add_option("--force", "-f", action="store_true",
343ff2ebff0SFam Zheng                      help="force build image even if image exists")
3448a6e007eSPhilippe Mathieu-Daudé    parser.add_option("--jobs", type=int, default=get_default_jobs(),
345ff2ebff0SFam Zheng                      help="number of virtual CPUs")
34641e3340aSPeter Maydell    parser.add_option("--verbose", "-V", action="store_true",
34741e3340aSPeter Maydell                      help="Pass V=1 to builds within the guest")
348ff2ebff0SFam Zheng    parser.add_option("--build-image", "-b", action="store_true",
349ff2ebff0SFam Zheng                      help="build image")
350ff2ebff0SFam Zheng    parser.add_option("--build-qemu",
351ff2ebff0SFam Zheng                      help="build QEMU from source in guest")
3525c2ec9b6SAlex Bennée    parser.add_option("--build-target",
3535c2ec9b6SAlex Bennée                      help="QEMU build target", default="check")
354ff2ebff0SFam Zheng    parser.add_option("--interactive", "-I", action="store_true",
355ff2ebff0SFam Zheng                      help="Interactively run command")
356983c2a77SFam Zheng    parser.add_option("--snapshot", "-s", action="store_true",
357983c2a77SFam Zheng                      help="run tests with a snapshot")
358ff2ebff0SFam Zheng    parser.disable_interspersed_args()
359ff2ebff0SFam Zheng    return parser.parse_args()
360ff2ebff0SFam Zheng
361ff2ebff0SFam Zhengdef main(vmcls):
362ff2ebff0SFam Zheng    try:
36363a24c5eSPhilippe Mathieu-Daudé        args, argv = parse_args(vmcls)
364ff2ebff0SFam Zheng        if not argv and not args.build_qemu and not args.build_image:
365f03868bdSEduardo Habkost            print("Nothing to do?")
366ff2ebff0SFam Zheng            return 1
367fb3b4e6dSEduardo Habkost        logging.basicConfig(level=(logging.DEBUG if args.debug
368fb3b4e6dSEduardo Habkost                                   else logging.WARN))
369ff2ebff0SFam Zheng        vm = vmcls(debug=args.debug, vcpus=args.jobs)
370ff2ebff0SFam Zheng        if args.build_image:
371ff2ebff0SFam Zheng            if os.path.exists(args.image) and not args.force:
372ff2ebff0SFam Zheng                sys.stderr.writelines(["Image file exists: %s\n" % args.image,
373ff2ebff0SFam Zheng                                      "Use --force option to overwrite\n"])
374ff2ebff0SFam Zheng                return 1
375ff2ebff0SFam Zheng            return vm.build_image(args.image)
376ff2ebff0SFam Zheng        if args.build_qemu:
377ff2ebff0SFam Zheng            vm.add_source_dir(args.build_qemu)
378ff2ebff0SFam Zheng            cmd = [vm.BUILD_SCRIPT.format(
379ff2ebff0SFam Zheng                   configure_opts = " ".join(argv),
3803ace9be6SGerd Hoffmann                   jobs=int(args.jobs),
3815c2ec9b6SAlex Bennée                   target=args.build_target,
38241e3340aSPeter Maydell                   verbose = "V=1" if args.verbose else "")]
383ff2ebff0SFam Zheng        else:
384ff2ebff0SFam Zheng            cmd = argv
385983c2a77SFam Zheng        img = args.image
386983c2a77SFam Zheng        if args.snapshot:
387983c2a77SFam Zheng            img += ",snapshot=on"
388983c2a77SFam Zheng        vm.boot(img)
389ff2ebff0SFam Zheng        vm.wait_ssh()
390ff2ebff0SFam Zheng    except Exception as e:
391ff2ebff0SFam Zheng        if isinstance(e, SystemExit) and e.code == 0:
392ff2ebff0SFam Zheng            return 0
393ff2ebff0SFam Zheng        sys.stderr.write("Failed to prepare guest environment\n")
394ff2ebff0SFam Zheng        traceback.print_exc()
395ff2ebff0SFam Zheng        return 2
396ff2ebff0SFam Zheng
397b3f94b2fSGerd Hoffmann    exitcode = 0
398ff2ebff0SFam Zheng    if vm.ssh(*cmd) != 0:
399b3f94b2fSGerd Hoffmann        exitcode = 3
400b3f94b2fSGerd Hoffmann    if exitcode != 0 and args.interactive:
401b3f94b2fSGerd Hoffmann        vm.ssh()
402b3f94b2fSGerd Hoffmann
403b3f94b2fSGerd Hoffmann    if not args.snapshot:
404b3f94b2fSGerd Hoffmann        vm.graceful_shutdown()
405b3f94b2fSGerd Hoffmann
406b3f94b2fSGerd Hoffmann    return exitcode
407