xref: /openbmc/qemu/tests/vm/basevm.py (revision 89adc5b918c5ed42eaa03997360010d3d940e342)
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