xref: /openbmc/qemu/tests/vm/basevm.py (revision 60136e06ea5fcbae228921841a07882f080294c3)
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"
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",
84ff2ebff0SFam Zheng            "-netdev", "user,id=vnet,hostfwd=:127.0.0.1:0-:22",
85ff2ebff0SFam Zheng            "-device", "virtio-net-pci,netdev=vnet",
868dd38334SGerd Hoffmann            "-vnc", "127.0.0.1:0,to=20"]
87071cf5a4SPhilippe Mathieu-Daudé        if vcpus and vcpus > 1:
883ace9be6SGerd Hoffmann            self._args += ["-smp", "%d" % vcpus]
8971531bb5SPhilippe Mathieu-Daudé        if kvm_available(self.arch):
90ff2ebff0SFam Zheng            self._args += ["-enable-kvm"]
91ff2ebff0SFam Zheng        else:
92ff2ebff0SFam Zheng            logging.info("KVM not available, not using -enable-kvm")
93ff2ebff0SFam Zheng        self._data_args = []
94ff2ebff0SFam Zheng
95ff2ebff0SFam Zheng    def _download_with_cache(self, url, sha256sum=None):
96ff2ebff0SFam Zheng        def check_sha256sum(fname):
97ff2ebff0SFam Zheng            if not sha256sum:
98ff2ebff0SFam Zheng                return True
99ff2ebff0SFam Zheng            checksum = subprocess.check_output(["sha256sum", fname]).split()[0]
1003ace9be6SGerd Hoffmann            return sha256sum == checksum.decode("utf-8")
101ff2ebff0SFam Zheng
102ff2ebff0SFam Zheng        cache_dir = os.path.expanduser("~/.cache/qemu-vm/download")
103ff2ebff0SFam Zheng        if not os.path.exists(cache_dir):
104ff2ebff0SFam Zheng            os.makedirs(cache_dir)
1053ace9be6SGerd Hoffmann        fname = os.path.join(cache_dir,
1063ace9be6SGerd Hoffmann                             hashlib.sha1(url.encode("utf-8")).hexdigest())
107ff2ebff0SFam Zheng        if os.path.exists(fname) and check_sha256sum(fname):
108ff2ebff0SFam Zheng            return fname
109ff2ebff0SFam Zheng        logging.debug("Downloading %s to %s...", url, fname)
110ff2ebff0SFam Zheng        subprocess.check_call(["wget", "-c", url, "-O", fname + ".download"],
111ff2ebff0SFam Zheng                              stdout=self._stdout, stderr=self._stderr)
112ff2ebff0SFam Zheng        os.rename(fname + ".download", fname)
113ff2ebff0SFam Zheng        return fname
114ff2ebff0SFam Zheng
115796471e9SGerd Hoffmann    def _ssh_do(self, user, cmd, check):
116796471e9SGerd Hoffmann        ssh_cmd = ["ssh", "-q", "-t",
117ff2ebff0SFam Zheng                   "-o", "StrictHostKeyChecking=no",
118ff2ebff0SFam Zheng                   "-o", "UserKnownHostsFile=" + os.devnull,
119ff2ebff0SFam Zheng                   "-o", "ConnectTimeout=1",
120ff2ebff0SFam Zheng                   "-p", self.ssh_port, "-i", self._ssh_key_file]
121b08ba163SGerd Hoffmann        for var in self.envvars:
122b08ba163SGerd Hoffmann            ssh_cmd += ['-o', "SendEnv=%s" % var ]
123ff2ebff0SFam Zheng        assert not isinstance(cmd, str)
124ff2ebff0SFam Zheng        ssh_cmd += ["%s@127.0.0.1" % user] + list(cmd)
125ff2ebff0SFam Zheng        logging.debug("ssh_cmd: %s", " ".join(ssh_cmd))
126726c9a3bSFam Zheng        r = subprocess.call(ssh_cmd)
127ff2ebff0SFam Zheng        if check and r != 0:
128ff2ebff0SFam Zheng            raise Exception("SSH command failed: %s" % cmd)
129ff2ebff0SFam Zheng        return r
130ff2ebff0SFam Zheng
131ff2ebff0SFam Zheng    def ssh(self, *cmd):
132ff2ebff0SFam Zheng        return self._ssh_do(self.GUEST_USER, cmd, False)
133ff2ebff0SFam Zheng
134ff2ebff0SFam Zheng    def ssh_root(self, *cmd):
135ff2ebff0SFam Zheng        return self._ssh_do("root", cmd, False)
136ff2ebff0SFam Zheng
137ff2ebff0SFam Zheng    def ssh_check(self, *cmd):
138ff2ebff0SFam Zheng        self._ssh_do(self.GUEST_USER, cmd, True)
139ff2ebff0SFam Zheng
140ff2ebff0SFam Zheng    def ssh_root_check(self, *cmd):
141ff2ebff0SFam Zheng        self._ssh_do("root", cmd, True)
142ff2ebff0SFam Zheng
143ff2ebff0SFam Zheng    def build_image(self, img):
144ff2ebff0SFam Zheng        raise NotImplementedError
145ff2ebff0SFam Zheng
146ff2ebff0SFam Zheng    def add_source_dir(self, src_dir):
1473ace9be6SGerd Hoffmann        name = "data-" + hashlib.sha1(src_dir.encode("utf-8")).hexdigest()[:5]
148ff2ebff0SFam Zheng        tarfile = os.path.join(self._tmpdir, name + ".tar")
149ff2ebff0SFam Zheng        logging.debug("Creating archive %s for src_dir dir: %s", tarfile, src_dir)
150ff2ebff0SFam Zheng        subprocess.check_call(["./scripts/archive-source.sh", tarfile],
151ff2ebff0SFam Zheng                              cwd=src_dir, stdin=self._devnull,
152ff2ebff0SFam Zheng                              stdout=self._stdout, stderr=self._stderr)
153ff2ebff0SFam Zheng        self._data_args += ["-drive",
154ff2ebff0SFam Zheng                            "file=%s,if=none,id=%s,cache=writeback,format=raw" % \
155ff2ebff0SFam Zheng                                    (tarfile, name),
156ff2ebff0SFam Zheng                            "-device",
157ff2ebff0SFam Zheng                            "virtio-blk,drive=%s,serial=%s,bootindex=1" % (name, name)]
158ff2ebff0SFam Zheng
159ff2ebff0SFam Zheng    def boot(self, img, extra_args=[]):
160ff2ebff0SFam Zheng        args = self._args + [
161ff2ebff0SFam Zheng            "-device", "VGA",
162ff2ebff0SFam Zheng            "-drive", "file=%s,if=none,id=drive0,cache=writeback" % img,
163ff2ebff0SFam Zheng            "-device", "virtio-blk,drive=drive0,bootindex=0"]
164ff2ebff0SFam Zheng        args += self._data_args + extra_args
165ff2ebff0SFam Zheng        logging.debug("QEMU args: %s", " ".join(args))
16631719c37SPhilippe Mathieu-Daudé        qemu_bin = os.environ.get("QEMU", "qemu-system-" + self.arch)
167ff2ebff0SFam Zheng        guest = QEMUMachine(binary=qemu_bin, args=args)
1688dd38334SGerd Hoffmann        guest.set_machine('pc')
1698dd38334SGerd Hoffmann        guest.set_console()
170ff2ebff0SFam Zheng        try:
171ff2ebff0SFam Zheng            guest.launch()
172ff2ebff0SFam Zheng        except:
173ff2ebff0SFam Zheng            logging.error("Failed to launch QEMU, command line:")
174ff2ebff0SFam Zheng            logging.error(" ".join([qemu_bin] + args))
175ff2ebff0SFam Zheng            logging.error("Log:")
176ff2ebff0SFam Zheng            logging.error(guest.get_log())
177ff2ebff0SFam Zheng            logging.error("QEMU version >= 2.10 is required")
178ff2ebff0SFam Zheng            raise
179ff2ebff0SFam Zheng        atexit.register(self.shutdown)
180ff2ebff0SFam Zheng        self._guest = guest
181ff2ebff0SFam Zheng        usernet_info = guest.qmp("human-monitor-command",
182ff2ebff0SFam Zheng                                 command_line="info usernet")
183ff2ebff0SFam Zheng        self.ssh_port = None
184ff2ebff0SFam Zheng        for l in usernet_info["return"].splitlines():
185ff2ebff0SFam Zheng            fields = l.split()
186ff2ebff0SFam Zheng            if "TCP[HOST_FORWARD]" in fields and "22" in fields:
187ff2ebff0SFam Zheng                self.ssh_port = l.split()[3]
188ff2ebff0SFam Zheng        if not self.ssh_port:
189ff2ebff0SFam Zheng            raise Exception("Cannot find ssh port from 'info usernet':\n%s" % \
190ff2ebff0SFam Zheng                            usernet_info)
191ff2ebff0SFam Zheng
1928dd38334SGerd Hoffmann    def console_init(self, timeout = 120):
1938dd38334SGerd Hoffmann        vm = self._guest
1948dd38334SGerd Hoffmann        vm.console_socket.settimeout(timeout)
1958dd38334SGerd Hoffmann
1968dd38334SGerd Hoffmann    def console_log(self, text):
1978dd38334SGerd Hoffmann        for line in re.split("[\r\n]", text):
1988dd38334SGerd Hoffmann            # filter out terminal escape sequences
1998dd38334SGerd Hoffmann            line = re.sub("\x1b\[[0-9;?]*[a-zA-Z]", "", line)
2008dd38334SGerd Hoffmann            line = re.sub("\x1b\([0-9;?]*[a-zA-Z]", "", line)
2018dd38334SGerd Hoffmann            # replace unprintable chars
2028dd38334SGerd Hoffmann            line = re.sub("\x1b", "<esc>", line)
2038dd38334SGerd Hoffmann            line = re.sub("[\x00-\x1f]", ".", line)
2048dd38334SGerd Hoffmann            line = re.sub("[\x80-\xff]", ".", line)
2058dd38334SGerd Hoffmann            if line == "":
2068dd38334SGerd Hoffmann                continue
2078dd38334SGerd Hoffmann            # log console line
2088dd38334SGerd Hoffmann            sys.stderr.write("con recv: %s\n" % line)
2098dd38334SGerd Hoffmann
210*60136e06SGerd Hoffmann    def console_wait(self, expect, expectalt = None):
2118dd38334SGerd Hoffmann        vm = self._guest
2128dd38334SGerd Hoffmann        output = ""
2138dd38334SGerd Hoffmann        while True:
2148dd38334SGerd Hoffmann            try:
2158dd38334SGerd Hoffmann                chars = vm.console_socket.recv(1)
2168dd38334SGerd Hoffmann            except socket.timeout:
2178dd38334SGerd Hoffmann                sys.stderr.write("console: *** read timeout ***\n")
2188dd38334SGerd Hoffmann                sys.stderr.write("console: waiting for: '%s'\n" % expect)
219*60136e06SGerd Hoffmann                if not expectalt is None:
220*60136e06SGerd Hoffmann                    sys.stderr.write("console: waiting for: '%s' (alt)\n" % expectalt)
2218dd38334SGerd Hoffmann                sys.stderr.write("console: line buffer:\n")
2228dd38334SGerd Hoffmann                sys.stderr.write("\n")
2238dd38334SGerd Hoffmann                self.console_log(output.rstrip())
2248dd38334SGerd Hoffmann                sys.stderr.write("\n")
2258dd38334SGerd Hoffmann                raise
2268dd38334SGerd Hoffmann            output += chars.decode("latin1")
2278dd38334SGerd Hoffmann            if expect in output:
2288dd38334SGerd Hoffmann                break
229*60136e06SGerd Hoffmann            if not expectalt is None and expectalt in output:
230*60136e06SGerd Hoffmann                break
2318dd38334SGerd Hoffmann            if "\r" in output or "\n" in output:
2328dd38334SGerd Hoffmann                lines = re.split("[\r\n]", output)
2338dd38334SGerd Hoffmann                output = lines.pop()
2348dd38334SGerd Hoffmann                if self.debug:
2358dd38334SGerd Hoffmann                    self.console_log("\n".join(lines))
2368dd38334SGerd Hoffmann        if self.debug:
2378dd38334SGerd Hoffmann            self.console_log(output)
238*60136e06SGerd Hoffmann        if not expectalt is None and expectalt in output:
239*60136e06SGerd Hoffmann            return False
240*60136e06SGerd Hoffmann        return True
2418dd38334SGerd Hoffmann
2428dd38334SGerd Hoffmann    def console_send(self, command):
2438dd38334SGerd Hoffmann        vm = self._guest
2448dd38334SGerd Hoffmann        if self.debug:
2458dd38334SGerd Hoffmann            logline = re.sub("\n", "<enter>", command)
2468dd38334SGerd Hoffmann            logline = re.sub("[\x00-\x1f]", ".", logline)
2478dd38334SGerd Hoffmann            sys.stderr.write("con send: %s\n" % logline)
2488dd38334SGerd Hoffmann        for char in list(command):
2498dd38334SGerd Hoffmann            vm.console_socket.send(char.encode("utf-8"))
2508dd38334SGerd Hoffmann            time.sleep(0.01)
2518dd38334SGerd Hoffmann
2528dd38334SGerd Hoffmann    def console_wait_send(self, wait, command):
2538dd38334SGerd Hoffmann        self.console_wait(wait)
2548dd38334SGerd Hoffmann        self.console_send(command)
2558dd38334SGerd Hoffmann
2568dd38334SGerd Hoffmann    def console_ssh_init(self, prompt, user, pw):
2578dd38334SGerd Hoffmann        sshkey_cmd = "echo '%s' > .ssh/authorized_keys\n" % SSH_PUB_KEY.rstrip()
2588dd38334SGerd Hoffmann        self.console_wait_send("login:",    "%s\n" % user)
2598dd38334SGerd Hoffmann        self.console_wait_send("Password:", "%s\n" % pw)
2608dd38334SGerd Hoffmann        self.console_wait_send(prompt,      "mkdir .ssh\n")
2618dd38334SGerd Hoffmann        self.console_wait_send(prompt,      sshkey_cmd)
2628dd38334SGerd Hoffmann        self.console_wait_send(prompt,      "chmod 755 .ssh\n")
2638dd38334SGerd Hoffmann        self.console_wait_send(prompt,      "chmod 644 .ssh/authorized_keys\n")
2648dd38334SGerd Hoffmann
2658dd38334SGerd Hoffmann    def console_sshd_config(self, prompt):
2668dd38334SGerd Hoffmann        self.console_wait(prompt)
2678dd38334SGerd Hoffmann        self.console_send("echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config\n")
2688dd38334SGerd Hoffmann        for var in self.envvars:
2698dd38334SGerd Hoffmann            self.console_wait(prompt)
2708dd38334SGerd Hoffmann            self.console_send("echo 'AcceptEnv %s' >> /etc/ssh/sshd_config\n" % var)
2718dd38334SGerd Hoffmann
2728dd38334SGerd Hoffmann    def print_step(self, text):
2738dd38334SGerd Hoffmann        sys.stderr.write("### %s ...\n" % text)
2748dd38334SGerd Hoffmann
2756b699ae1SPeter Maydell    def wait_ssh(self, seconds=300):
276ff2ebff0SFam Zheng        starttime = datetime.datetime.now()
277f5d3d218SPhilippe Mathieu-Daudé        endtime = starttime + datetime.timedelta(seconds=seconds)
278ff2ebff0SFam Zheng        guest_up = False
279f5d3d218SPhilippe Mathieu-Daudé        while datetime.datetime.now() < endtime:
280ff2ebff0SFam Zheng            if self.ssh("exit 0") == 0:
281ff2ebff0SFam Zheng                guest_up = True
282ff2ebff0SFam Zheng                break
283f5d3d218SPhilippe Mathieu-Daudé            seconds = (endtime - datetime.datetime.now()).total_seconds()
284f5d3d218SPhilippe Mathieu-Daudé            logging.debug("%ds before timeout", seconds)
285ff2ebff0SFam Zheng            time.sleep(1)
286ff2ebff0SFam Zheng        if not guest_up:
287ff2ebff0SFam Zheng            raise Exception("Timeout while waiting for guest ssh")
288ff2ebff0SFam Zheng
289ff2ebff0SFam Zheng    def shutdown(self):
290ff2ebff0SFam Zheng        self._guest.shutdown()
291ff2ebff0SFam Zheng
292ff2ebff0SFam Zheng    def wait(self):
293ff2ebff0SFam Zheng        self._guest.wait()
294ff2ebff0SFam Zheng
295b3f94b2fSGerd Hoffmann    def graceful_shutdown(self):
296b3f94b2fSGerd Hoffmann        self.ssh_root(self.poweroff)
297b3f94b2fSGerd Hoffmann        self._guest.wait()
298b3f94b2fSGerd Hoffmann
299ff2ebff0SFam Zheng    def qmp(self, *args, **kwargs):
300ff2ebff0SFam Zheng        return self._guest.qmp(*args, **kwargs)
301ff2ebff0SFam Zheng
30263a24c5eSPhilippe Mathieu-Daudédef parse_args(vmcls):
3038a6e007eSPhilippe Mathieu-Daudé
3048a6e007eSPhilippe Mathieu-Daudé    def get_default_jobs():
30563a24c5eSPhilippe Mathieu-Daudé        if kvm_available(vmcls.arch):
3063ad3e36eSWainer dos Santos Moschetta            return multiprocessing.cpu_count() // 2
3078a6e007eSPhilippe Mathieu-Daudé        else:
3088a6e007eSPhilippe Mathieu-Daudé            return 1
3098a6e007eSPhilippe Mathieu-Daudé
310ff2ebff0SFam Zheng    parser = optparse.OptionParser(
311ff2ebff0SFam Zheng        description="VM test utility.  Exit codes: "
312ff2ebff0SFam Zheng                    "0 = success, "
313ff2ebff0SFam Zheng                    "1 = command line error, "
314ff2ebff0SFam Zheng                    "2 = environment initialization failed, "
315ff2ebff0SFam Zheng                    "3 = test command failed")
316ff2ebff0SFam Zheng    parser.add_option("--debug", "-D", action="store_true",
317ff2ebff0SFam Zheng                      help="enable debug output")
31863a24c5eSPhilippe Mathieu-Daudé    parser.add_option("--image", "-i", default="%s.img" % vmcls.name,
319ff2ebff0SFam Zheng                      help="image file name")
320ff2ebff0SFam Zheng    parser.add_option("--force", "-f", action="store_true",
321ff2ebff0SFam Zheng                      help="force build image even if image exists")
3228a6e007eSPhilippe Mathieu-Daudé    parser.add_option("--jobs", type=int, default=get_default_jobs(),
323ff2ebff0SFam Zheng                      help="number of virtual CPUs")
32441e3340aSPeter Maydell    parser.add_option("--verbose", "-V", action="store_true",
32541e3340aSPeter Maydell                      help="Pass V=1 to builds within the guest")
326ff2ebff0SFam Zheng    parser.add_option("--build-image", "-b", action="store_true",
327ff2ebff0SFam Zheng                      help="build image")
328ff2ebff0SFam Zheng    parser.add_option("--build-qemu",
329ff2ebff0SFam Zheng                      help="build QEMU from source in guest")
3305c2ec9b6SAlex Bennée    parser.add_option("--build-target",
3315c2ec9b6SAlex Bennée                      help="QEMU build target", default="check")
332ff2ebff0SFam Zheng    parser.add_option("--interactive", "-I", action="store_true",
333ff2ebff0SFam Zheng                      help="Interactively run command")
334983c2a77SFam Zheng    parser.add_option("--snapshot", "-s", action="store_true",
335983c2a77SFam Zheng                      help="run tests with a snapshot")
336ff2ebff0SFam Zheng    parser.disable_interspersed_args()
337ff2ebff0SFam Zheng    return parser.parse_args()
338ff2ebff0SFam Zheng
339ff2ebff0SFam Zhengdef main(vmcls):
340ff2ebff0SFam Zheng    try:
34163a24c5eSPhilippe Mathieu-Daudé        args, argv = parse_args(vmcls)
342ff2ebff0SFam Zheng        if not argv and not args.build_qemu and not args.build_image:
343f03868bdSEduardo Habkost            print("Nothing to do?")
344ff2ebff0SFam Zheng            return 1
345fb3b4e6dSEduardo Habkost        logging.basicConfig(level=(logging.DEBUG if args.debug
346fb3b4e6dSEduardo Habkost                                   else logging.WARN))
347ff2ebff0SFam Zheng        vm = vmcls(debug=args.debug, vcpus=args.jobs)
348ff2ebff0SFam Zheng        if args.build_image:
349ff2ebff0SFam Zheng            if os.path.exists(args.image) and not args.force:
350ff2ebff0SFam Zheng                sys.stderr.writelines(["Image file exists: %s\n" % args.image,
351ff2ebff0SFam Zheng                                      "Use --force option to overwrite\n"])
352ff2ebff0SFam Zheng                return 1
353ff2ebff0SFam Zheng            return vm.build_image(args.image)
354ff2ebff0SFam Zheng        if args.build_qemu:
355ff2ebff0SFam Zheng            vm.add_source_dir(args.build_qemu)
356ff2ebff0SFam Zheng            cmd = [vm.BUILD_SCRIPT.format(
357ff2ebff0SFam Zheng                   configure_opts = " ".join(argv),
3583ace9be6SGerd Hoffmann                   jobs=int(args.jobs),
3595c2ec9b6SAlex Bennée                   target=args.build_target,
36041e3340aSPeter Maydell                   verbose = "V=1" if args.verbose else "")]
361ff2ebff0SFam Zheng        else:
362ff2ebff0SFam Zheng            cmd = argv
363983c2a77SFam Zheng        img = args.image
364983c2a77SFam Zheng        if args.snapshot:
365983c2a77SFam Zheng            img += ",snapshot=on"
366983c2a77SFam Zheng        vm.boot(img)
367ff2ebff0SFam Zheng        vm.wait_ssh()
368ff2ebff0SFam Zheng    except Exception as e:
369ff2ebff0SFam Zheng        if isinstance(e, SystemExit) and e.code == 0:
370ff2ebff0SFam Zheng            return 0
371ff2ebff0SFam Zheng        sys.stderr.write("Failed to prepare guest environment\n")
372ff2ebff0SFam Zheng        traceback.print_exc()
373ff2ebff0SFam Zheng        return 2
374ff2ebff0SFam Zheng
375b3f94b2fSGerd Hoffmann    exitcode = 0
376ff2ebff0SFam Zheng    if vm.ssh(*cmd) != 0:
377b3f94b2fSGerd Hoffmann        exitcode = 3
378b3f94b2fSGerd Hoffmann    if exitcode != 0 and args.interactive:
379b3f94b2fSGerd Hoffmann        vm.ssh()
380b3f94b2fSGerd Hoffmann
381b3f94b2fSGerd Hoffmann    if not args.snapshot:
382b3f94b2fSGerd Hoffmann        vm.graceful_shutdown()
383b3f94b2fSGerd Hoffmann
384b3f94b2fSGerd Hoffmann    return exitcode
385