xref: /openbmc/qemu/tests/vm/basevm.py (revision 8dd3833410c29dab1a2f63af2ab71f71f8ccccfc)
1ff2ebff0SFam Zheng#!/usr/bin/env python
2ff2ebff0SFam Zheng#
3ff2ebff0SFam Zheng# VM testing base class
4ff2ebff0SFam Zheng#
5*8dd38334SGerd Hoffmann# Copyright 2017-2019 Red Hat Inc.
6ff2ebff0SFam Zheng#
7ff2ebff0SFam Zheng# Authors:
8ff2ebff0SFam Zheng#  Fam Zheng <famz@redhat.com>
9*8dd38334SGerd 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
17*8dd38334SGerd Hoffmannimport re
18ff2ebff0SFam Zhengimport sys
19*8dd38334SGerd 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",
86*8dd38334SGerd 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)
168*8dd38334SGerd Hoffmann        guest.set_machine('pc')
169*8dd38334SGerd 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
192*8dd38334SGerd Hoffmann    def console_init(self, timeout = 120):
193*8dd38334SGerd Hoffmann        vm = self._guest
194*8dd38334SGerd Hoffmann        vm.console_socket.settimeout(timeout)
195*8dd38334SGerd Hoffmann
196*8dd38334SGerd Hoffmann    def console_log(self, text):
197*8dd38334SGerd Hoffmann        for line in re.split("[\r\n]", text):
198*8dd38334SGerd Hoffmann            # filter out terminal escape sequences
199*8dd38334SGerd Hoffmann            line = re.sub("\x1b\[[0-9;?]*[a-zA-Z]", "", line)
200*8dd38334SGerd Hoffmann            line = re.sub("\x1b\([0-9;?]*[a-zA-Z]", "", line)
201*8dd38334SGerd Hoffmann            # replace unprintable chars
202*8dd38334SGerd Hoffmann            line = re.sub("\x1b", "<esc>", line)
203*8dd38334SGerd Hoffmann            line = re.sub("[\x00-\x1f]", ".", line)
204*8dd38334SGerd Hoffmann            line = re.sub("[\x80-\xff]", ".", line)
205*8dd38334SGerd Hoffmann            if line == "":
206*8dd38334SGerd Hoffmann                continue
207*8dd38334SGerd Hoffmann            # log console line
208*8dd38334SGerd Hoffmann            sys.stderr.write("con recv: %s\n" % line)
209*8dd38334SGerd Hoffmann
210*8dd38334SGerd Hoffmann    def console_wait(self, expect):
211*8dd38334SGerd Hoffmann        vm = self._guest
212*8dd38334SGerd Hoffmann        output = ""
213*8dd38334SGerd Hoffmann        while True:
214*8dd38334SGerd Hoffmann            try:
215*8dd38334SGerd Hoffmann                chars = vm.console_socket.recv(1)
216*8dd38334SGerd Hoffmann            except socket.timeout:
217*8dd38334SGerd Hoffmann                sys.stderr.write("console: *** read timeout ***\n")
218*8dd38334SGerd Hoffmann                sys.stderr.write("console: waiting for: '%s'\n" % expect)
219*8dd38334SGerd Hoffmann                sys.stderr.write("console: line buffer:\n")
220*8dd38334SGerd Hoffmann                sys.stderr.write("\n")
221*8dd38334SGerd Hoffmann                self.console_log(output.rstrip())
222*8dd38334SGerd Hoffmann                sys.stderr.write("\n")
223*8dd38334SGerd Hoffmann                raise
224*8dd38334SGerd Hoffmann            output += chars.decode("latin1")
225*8dd38334SGerd Hoffmann            if expect in output:
226*8dd38334SGerd Hoffmann                break
227*8dd38334SGerd Hoffmann            if "\r" in output or "\n" in output:
228*8dd38334SGerd Hoffmann                lines = re.split("[\r\n]", output)
229*8dd38334SGerd Hoffmann                output = lines.pop()
230*8dd38334SGerd Hoffmann                if self.debug:
231*8dd38334SGerd Hoffmann                    self.console_log("\n".join(lines))
232*8dd38334SGerd Hoffmann        if self.debug:
233*8dd38334SGerd Hoffmann            self.console_log(output)
234*8dd38334SGerd Hoffmann
235*8dd38334SGerd Hoffmann    def console_send(self, command):
236*8dd38334SGerd Hoffmann        vm = self._guest
237*8dd38334SGerd Hoffmann        if self.debug:
238*8dd38334SGerd Hoffmann            logline = re.sub("\n", "<enter>", command)
239*8dd38334SGerd Hoffmann            logline = re.sub("[\x00-\x1f]", ".", logline)
240*8dd38334SGerd Hoffmann            sys.stderr.write("con send: %s\n" % logline)
241*8dd38334SGerd Hoffmann        for char in list(command):
242*8dd38334SGerd Hoffmann            vm.console_socket.send(char.encode("utf-8"))
243*8dd38334SGerd Hoffmann            time.sleep(0.01)
244*8dd38334SGerd Hoffmann
245*8dd38334SGerd Hoffmann    def console_wait_send(self, wait, command):
246*8dd38334SGerd Hoffmann        self.console_wait(wait)
247*8dd38334SGerd Hoffmann        self.console_send(command)
248*8dd38334SGerd Hoffmann
249*8dd38334SGerd Hoffmann    def console_ssh_init(self, prompt, user, pw):
250*8dd38334SGerd Hoffmann        sshkey_cmd = "echo '%s' > .ssh/authorized_keys\n" % SSH_PUB_KEY.rstrip()
251*8dd38334SGerd Hoffmann        self.console_wait_send("login:",    "%s\n" % user)
252*8dd38334SGerd Hoffmann        self.console_wait_send("Password:", "%s\n" % pw)
253*8dd38334SGerd Hoffmann        self.console_wait_send(prompt,      "mkdir .ssh\n")
254*8dd38334SGerd Hoffmann        self.console_wait_send(prompt,      sshkey_cmd)
255*8dd38334SGerd Hoffmann        self.console_wait_send(prompt,      "chmod 755 .ssh\n")
256*8dd38334SGerd Hoffmann        self.console_wait_send(prompt,      "chmod 644 .ssh/authorized_keys\n")
257*8dd38334SGerd Hoffmann
258*8dd38334SGerd Hoffmann    def console_sshd_config(self, prompt):
259*8dd38334SGerd Hoffmann        self.console_wait(prompt)
260*8dd38334SGerd Hoffmann        self.console_send("echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config\n")
261*8dd38334SGerd Hoffmann        for var in self.envvars:
262*8dd38334SGerd Hoffmann            self.console_wait(prompt)
263*8dd38334SGerd Hoffmann            self.console_send("echo 'AcceptEnv %s' >> /etc/ssh/sshd_config\n" % var)
264*8dd38334SGerd Hoffmann
265*8dd38334SGerd Hoffmann    def print_step(self, text):
266*8dd38334SGerd Hoffmann        sys.stderr.write("### %s ...\n" % text)
267*8dd38334SGerd Hoffmann
2686b699ae1SPeter Maydell    def wait_ssh(self, seconds=300):
269ff2ebff0SFam Zheng        starttime = datetime.datetime.now()
270f5d3d218SPhilippe Mathieu-Daudé        endtime = starttime + datetime.timedelta(seconds=seconds)
271ff2ebff0SFam Zheng        guest_up = False
272f5d3d218SPhilippe Mathieu-Daudé        while datetime.datetime.now() < endtime:
273ff2ebff0SFam Zheng            if self.ssh("exit 0") == 0:
274ff2ebff0SFam Zheng                guest_up = True
275ff2ebff0SFam Zheng                break
276f5d3d218SPhilippe Mathieu-Daudé            seconds = (endtime - datetime.datetime.now()).total_seconds()
277f5d3d218SPhilippe Mathieu-Daudé            logging.debug("%ds before timeout", seconds)
278ff2ebff0SFam Zheng            time.sleep(1)
279ff2ebff0SFam Zheng        if not guest_up:
280ff2ebff0SFam Zheng            raise Exception("Timeout while waiting for guest ssh")
281ff2ebff0SFam Zheng
282ff2ebff0SFam Zheng    def shutdown(self):
283ff2ebff0SFam Zheng        self._guest.shutdown()
284ff2ebff0SFam Zheng
285ff2ebff0SFam Zheng    def wait(self):
286ff2ebff0SFam Zheng        self._guest.wait()
287ff2ebff0SFam Zheng
288b3f94b2fSGerd Hoffmann    def graceful_shutdown(self):
289b3f94b2fSGerd Hoffmann        self.ssh_root(self.poweroff)
290b3f94b2fSGerd Hoffmann        self._guest.wait()
291b3f94b2fSGerd Hoffmann
292ff2ebff0SFam Zheng    def qmp(self, *args, **kwargs):
293ff2ebff0SFam Zheng        return self._guest.qmp(*args, **kwargs)
294ff2ebff0SFam Zheng
29563a24c5eSPhilippe Mathieu-Daudédef parse_args(vmcls):
2968a6e007eSPhilippe Mathieu-Daudé
2978a6e007eSPhilippe Mathieu-Daudé    def get_default_jobs():
29863a24c5eSPhilippe Mathieu-Daudé        if kvm_available(vmcls.arch):
2993ad3e36eSWainer dos Santos Moschetta            return multiprocessing.cpu_count() // 2
3008a6e007eSPhilippe Mathieu-Daudé        else:
3018a6e007eSPhilippe Mathieu-Daudé            return 1
3028a6e007eSPhilippe Mathieu-Daudé
303ff2ebff0SFam Zheng    parser = optparse.OptionParser(
304ff2ebff0SFam Zheng        description="VM test utility.  Exit codes: "
305ff2ebff0SFam Zheng                    "0 = success, "
306ff2ebff0SFam Zheng                    "1 = command line error, "
307ff2ebff0SFam Zheng                    "2 = environment initialization failed, "
308ff2ebff0SFam Zheng                    "3 = test command failed")
309ff2ebff0SFam Zheng    parser.add_option("--debug", "-D", action="store_true",
310ff2ebff0SFam Zheng                      help="enable debug output")
31163a24c5eSPhilippe Mathieu-Daudé    parser.add_option("--image", "-i", default="%s.img" % vmcls.name,
312ff2ebff0SFam Zheng                      help="image file name")
313ff2ebff0SFam Zheng    parser.add_option("--force", "-f", action="store_true",
314ff2ebff0SFam Zheng                      help="force build image even if image exists")
3158a6e007eSPhilippe Mathieu-Daudé    parser.add_option("--jobs", type=int, default=get_default_jobs(),
316ff2ebff0SFam Zheng                      help="number of virtual CPUs")
31741e3340aSPeter Maydell    parser.add_option("--verbose", "-V", action="store_true",
31841e3340aSPeter Maydell                      help="Pass V=1 to builds within the guest")
319ff2ebff0SFam Zheng    parser.add_option("--build-image", "-b", action="store_true",
320ff2ebff0SFam Zheng                      help="build image")
321ff2ebff0SFam Zheng    parser.add_option("--build-qemu",
322ff2ebff0SFam Zheng                      help="build QEMU from source in guest")
3235c2ec9b6SAlex Bennée    parser.add_option("--build-target",
3245c2ec9b6SAlex Bennée                      help="QEMU build target", default="check")
325ff2ebff0SFam Zheng    parser.add_option("--interactive", "-I", action="store_true",
326ff2ebff0SFam Zheng                      help="Interactively run command")
327983c2a77SFam Zheng    parser.add_option("--snapshot", "-s", action="store_true",
328983c2a77SFam Zheng                      help="run tests with a snapshot")
329ff2ebff0SFam Zheng    parser.disable_interspersed_args()
330ff2ebff0SFam Zheng    return parser.parse_args()
331ff2ebff0SFam Zheng
332ff2ebff0SFam Zhengdef main(vmcls):
333ff2ebff0SFam Zheng    try:
33463a24c5eSPhilippe Mathieu-Daudé        args, argv = parse_args(vmcls)
335ff2ebff0SFam Zheng        if not argv and not args.build_qemu and not args.build_image:
336f03868bdSEduardo Habkost            print("Nothing to do?")
337ff2ebff0SFam Zheng            return 1
338fb3b4e6dSEduardo Habkost        logging.basicConfig(level=(logging.DEBUG if args.debug
339fb3b4e6dSEduardo Habkost                                   else logging.WARN))
340ff2ebff0SFam Zheng        vm = vmcls(debug=args.debug, vcpus=args.jobs)
341ff2ebff0SFam Zheng        if args.build_image:
342ff2ebff0SFam Zheng            if os.path.exists(args.image) and not args.force:
343ff2ebff0SFam Zheng                sys.stderr.writelines(["Image file exists: %s\n" % args.image,
344ff2ebff0SFam Zheng                                      "Use --force option to overwrite\n"])
345ff2ebff0SFam Zheng                return 1
346ff2ebff0SFam Zheng            return vm.build_image(args.image)
347ff2ebff0SFam Zheng        if args.build_qemu:
348ff2ebff0SFam Zheng            vm.add_source_dir(args.build_qemu)
349ff2ebff0SFam Zheng            cmd = [vm.BUILD_SCRIPT.format(
350ff2ebff0SFam Zheng                   configure_opts = " ".join(argv),
3513ace9be6SGerd Hoffmann                   jobs=int(args.jobs),
3525c2ec9b6SAlex Bennée                   target=args.build_target,
35341e3340aSPeter Maydell                   verbose = "V=1" if args.verbose else "")]
354ff2ebff0SFam Zheng        else:
355ff2ebff0SFam Zheng            cmd = argv
356983c2a77SFam Zheng        img = args.image
357983c2a77SFam Zheng        if args.snapshot:
358983c2a77SFam Zheng            img += ",snapshot=on"
359983c2a77SFam Zheng        vm.boot(img)
360ff2ebff0SFam Zheng        vm.wait_ssh()
361ff2ebff0SFam Zheng    except Exception as e:
362ff2ebff0SFam Zheng        if isinstance(e, SystemExit) and e.code == 0:
363ff2ebff0SFam Zheng            return 0
364ff2ebff0SFam Zheng        sys.stderr.write("Failed to prepare guest environment\n")
365ff2ebff0SFam Zheng        traceback.print_exc()
366ff2ebff0SFam Zheng        return 2
367ff2ebff0SFam Zheng
368b3f94b2fSGerd Hoffmann    exitcode = 0
369ff2ebff0SFam Zheng    if vm.ssh(*cmd) != 0:
370b3f94b2fSGerd Hoffmann        exitcode = 3
371b3f94b2fSGerd Hoffmann    if exitcode != 0 and args.interactive:
372b3f94b2fSGerd Hoffmann        vm.ssh()
373b3f94b2fSGerd Hoffmann
374b3f94b2fSGerd Hoffmann    if not args.snapshot:
375b3f94b2fSGerd Hoffmann        vm.graceful_shutdown()
376b3f94b2fSGerd Hoffmann
377b3f94b2fSGerd Hoffmann    return exitcode
378