xref: /openbmc/qemu/tests/vm/basevm.py (revision c9de39355a54dfc7aaebddace364f14159e5607e)
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
60*c9de3935SRobert Foley    # Scale up some timeouts under TCG.
61*c9de3935SRobert Foley    # 4 is arbitrary, but greater than 2,
62*c9de3935SRobert Foley    # since we found we need to wait more than twice as long.
63*c9de3935SRobert Foley    tcg_ssh_timeout_multiplier = 4
64ff2ebff0SFam Zheng    def __init__(self, debug=False, vcpus=None):
65ff2ebff0SFam Zheng        self._guest = None
66ff2ebff0SFam Zheng        self._tmpdir = os.path.realpath(tempfile.mkdtemp(prefix="vm-test-",
67ff2ebff0SFam Zheng                                                         suffix=".tmp",
68ff2ebff0SFam Zheng                                                         dir="."))
69ff2ebff0SFam Zheng        atexit.register(shutil.rmtree, self._tmpdir)
70ff2ebff0SFam Zheng
71ff2ebff0SFam Zheng        self._ssh_key_file = os.path.join(self._tmpdir, "id_rsa")
72ff2ebff0SFam Zheng        open(self._ssh_key_file, "w").write(SSH_KEY)
73ff2ebff0SFam Zheng        subprocess.check_call(["chmod", "600", self._ssh_key_file])
74ff2ebff0SFam Zheng
75ff2ebff0SFam Zheng        self._ssh_pub_key_file = os.path.join(self._tmpdir, "id_rsa.pub")
76ff2ebff0SFam Zheng        open(self._ssh_pub_key_file, "w").write(SSH_PUB_KEY)
77ff2ebff0SFam Zheng
78ff2ebff0SFam Zheng        self.debug = debug
79ff2ebff0SFam Zheng        self._stderr = sys.stderr
80ff2ebff0SFam Zheng        self._devnull = open(os.devnull, "w")
81ff2ebff0SFam Zheng        if self.debug:
82ff2ebff0SFam Zheng            self._stdout = sys.stdout
83ff2ebff0SFam Zheng        else:
84ff2ebff0SFam Zheng            self._stdout = self._devnull
85ff2ebff0SFam Zheng        self._args = [ \
86eb2712f5SPeter Maydell            "-nodefaults", "-m", "4G",
87b33bd859SPeter Maydell            "-cpu", "max",
885b790481SEduardo Habkost            "-netdev", "user,id=vnet,hostfwd=:127.0.0.1:0-:22" +
895b790481SEduardo Habkost                       (",ipv6=no" if not self.ipv6 else ""),
90ff2ebff0SFam Zheng            "-device", "virtio-net-pci,netdev=vnet",
918dd38334SGerd Hoffmann            "-vnc", "127.0.0.1:0,to=20"]
92071cf5a4SPhilippe Mathieu-Daudé        if vcpus and vcpus > 1:
933ace9be6SGerd Hoffmann            self._args += ["-smp", "%d" % vcpus]
9471531bb5SPhilippe Mathieu-Daudé        if kvm_available(self.arch):
95ff2ebff0SFam Zheng            self._args += ["-enable-kvm"]
96ff2ebff0SFam Zheng        else:
97ff2ebff0SFam Zheng            logging.info("KVM not available, not using -enable-kvm")
98ff2ebff0SFam Zheng        self._data_args = []
99ff2ebff0SFam Zheng
1005b4b4865SAlex Bennée    def _download_with_cache(self, url, sha256sum=None, sha512sum=None):
101ff2ebff0SFam Zheng        def check_sha256sum(fname):
102ff2ebff0SFam Zheng            if not sha256sum:
103ff2ebff0SFam Zheng                return True
104ff2ebff0SFam Zheng            checksum = subprocess.check_output(["sha256sum", fname]).split()[0]
1053ace9be6SGerd Hoffmann            return sha256sum == checksum.decode("utf-8")
106ff2ebff0SFam Zheng
1075b4b4865SAlex Bennée        def check_sha512sum(fname):
1085b4b4865SAlex Bennée            if not sha512sum:
1095b4b4865SAlex Bennée                return True
1105b4b4865SAlex Bennée            checksum = subprocess.check_output(["sha512sum", fname]).split()[0]
1115b4b4865SAlex Bennée            return sha512sum == checksum.decode("utf-8")
1125b4b4865SAlex Bennée
113ff2ebff0SFam Zheng        cache_dir = os.path.expanduser("~/.cache/qemu-vm/download")
114ff2ebff0SFam Zheng        if not os.path.exists(cache_dir):
115ff2ebff0SFam Zheng            os.makedirs(cache_dir)
1163ace9be6SGerd Hoffmann        fname = os.path.join(cache_dir,
1173ace9be6SGerd Hoffmann                             hashlib.sha1(url.encode("utf-8")).hexdigest())
1185b4b4865SAlex Bennée        if os.path.exists(fname) and check_sha256sum(fname) and check_sha512sum(fname):
119ff2ebff0SFam Zheng            return fname
120ff2ebff0SFam Zheng        logging.debug("Downloading %s to %s...", url, fname)
121ff2ebff0SFam Zheng        subprocess.check_call(["wget", "-c", url, "-O", fname + ".download"],
122ff2ebff0SFam Zheng                              stdout=self._stdout, stderr=self._stderr)
123ff2ebff0SFam Zheng        os.rename(fname + ".download", fname)
124ff2ebff0SFam Zheng        return fname
125ff2ebff0SFam Zheng
126796471e9SGerd Hoffmann    def _ssh_do(self, user, cmd, check):
12789adc5b9SRobert Foley        ssh_cmd = ["ssh",
12889adc5b9SRobert Foley                   "-t",
129ff2ebff0SFam Zheng                   "-o", "StrictHostKeyChecking=no",
130ff2ebff0SFam Zheng                   "-o", "UserKnownHostsFile=" + os.devnull,
131ff2ebff0SFam Zheng                   "-o", "ConnectTimeout=1",
132ff2ebff0SFam Zheng                   "-p", self.ssh_port, "-i", self._ssh_key_file]
13389adc5b9SRobert Foley        # If not in debug mode, set ssh to quiet mode to
13489adc5b9SRobert Foley        # avoid printing the results of commands.
13589adc5b9SRobert Foley        if not self.debug:
13689adc5b9SRobert Foley            ssh_cmd.append("-q")
137b08ba163SGerd Hoffmann        for var in self.envvars:
138b08ba163SGerd Hoffmann            ssh_cmd += ['-o', "SendEnv=%s" % var ]
139ff2ebff0SFam Zheng        assert not isinstance(cmd, str)
140ff2ebff0SFam Zheng        ssh_cmd += ["%s@127.0.0.1" % user] + list(cmd)
141ff2ebff0SFam Zheng        logging.debug("ssh_cmd: %s", " ".join(ssh_cmd))
142726c9a3bSFam Zheng        r = subprocess.call(ssh_cmd)
143ff2ebff0SFam Zheng        if check and r != 0:
144ff2ebff0SFam Zheng            raise Exception("SSH command failed: %s" % cmd)
145ff2ebff0SFam Zheng        return r
146ff2ebff0SFam Zheng
147ff2ebff0SFam Zheng    def ssh(self, *cmd):
148ff2ebff0SFam Zheng        return self._ssh_do(self.GUEST_USER, cmd, False)
149ff2ebff0SFam Zheng
150ff2ebff0SFam Zheng    def ssh_root(self, *cmd):
151ff2ebff0SFam Zheng        return self._ssh_do("root", cmd, False)
152ff2ebff0SFam Zheng
153ff2ebff0SFam Zheng    def ssh_check(self, *cmd):
154ff2ebff0SFam Zheng        self._ssh_do(self.GUEST_USER, cmd, True)
155ff2ebff0SFam Zheng
156ff2ebff0SFam Zheng    def ssh_root_check(self, *cmd):
157ff2ebff0SFam Zheng        self._ssh_do("root", cmd, True)
158ff2ebff0SFam Zheng
159ff2ebff0SFam Zheng    def build_image(self, img):
160ff2ebff0SFam Zheng        raise NotImplementedError
161ff2ebff0SFam Zheng
1621e48931cSWainer dos Santos Moschetta    def exec_qemu_img(self, *args):
1631e48931cSWainer dos Santos Moschetta        cmd = [os.environ.get("QEMU_IMG", "qemu-img")]
1641e48931cSWainer dos Santos Moschetta        cmd.extend(list(args))
1651e48931cSWainer dos Santos Moschetta        subprocess.check_call(cmd)
1661e48931cSWainer dos Santos Moschetta
167ff2ebff0SFam Zheng    def add_source_dir(self, src_dir):
1683ace9be6SGerd Hoffmann        name = "data-" + hashlib.sha1(src_dir.encode("utf-8")).hexdigest()[:5]
169ff2ebff0SFam Zheng        tarfile = os.path.join(self._tmpdir, name + ".tar")
170ff2ebff0SFam Zheng        logging.debug("Creating archive %s for src_dir dir: %s", tarfile, src_dir)
171ff2ebff0SFam Zheng        subprocess.check_call(["./scripts/archive-source.sh", tarfile],
172ff2ebff0SFam Zheng                              cwd=src_dir, stdin=self._devnull,
173ff2ebff0SFam Zheng                              stdout=self._stdout, stderr=self._stderr)
174ff2ebff0SFam Zheng        self._data_args += ["-drive",
175ff2ebff0SFam Zheng                            "file=%s,if=none,id=%s,cache=writeback,format=raw" % \
176ff2ebff0SFam Zheng                                    (tarfile, name),
177ff2ebff0SFam Zheng                            "-device",
178ff2ebff0SFam Zheng                            "virtio-blk,drive=%s,serial=%s,bootindex=1" % (name, name)]
179ff2ebff0SFam Zheng
180ff2ebff0SFam Zheng    def boot(self, img, extra_args=[]):
181ff2ebff0SFam Zheng        args = self._args + [
182ff2ebff0SFam Zheng            "-device", "VGA",
183ff2ebff0SFam Zheng            "-drive", "file=%s,if=none,id=drive0,cache=writeback" % img,
184ff2ebff0SFam Zheng            "-device", "virtio-blk,drive=drive0,bootindex=0"]
185ff2ebff0SFam Zheng        args += self._data_args + extra_args
186ff2ebff0SFam Zheng        logging.debug("QEMU args: %s", " ".join(args))
18731719c37SPhilippe Mathieu-Daudé        qemu_bin = os.environ.get("QEMU", "qemu-system-" + self.arch)
188ff2ebff0SFam Zheng        guest = QEMUMachine(binary=qemu_bin, args=args)
1898dd38334SGerd Hoffmann        guest.set_machine('pc')
1908dd38334SGerd Hoffmann        guest.set_console()
191ff2ebff0SFam Zheng        try:
192ff2ebff0SFam Zheng            guest.launch()
193ff2ebff0SFam Zheng        except:
194ff2ebff0SFam Zheng            logging.error("Failed to launch QEMU, command line:")
195ff2ebff0SFam Zheng            logging.error(" ".join([qemu_bin] + args))
196ff2ebff0SFam Zheng            logging.error("Log:")
197ff2ebff0SFam Zheng            logging.error(guest.get_log())
198ff2ebff0SFam Zheng            logging.error("QEMU version >= 2.10 is required")
199ff2ebff0SFam Zheng            raise
200ff2ebff0SFam Zheng        atexit.register(self.shutdown)
201ff2ebff0SFam Zheng        self._guest = guest
202ff2ebff0SFam Zheng        usernet_info = guest.qmp("human-monitor-command",
203ff2ebff0SFam Zheng                                 command_line="info usernet")
204ff2ebff0SFam Zheng        self.ssh_port = None
205ff2ebff0SFam Zheng        for l in usernet_info["return"].splitlines():
206ff2ebff0SFam Zheng            fields = l.split()
207ff2ebff0SFam Zheng            if "TCP[HOST_FORWARD]" in fields and "22" in fields:
208ff2ebff0SFam Zheng                self.ssh_port = l.split()[3]
209ff2ebff0SFam Zheng        if not self.ssh_port:
210ff2ebff0SFam Zheng            raise Exception("Cannot find ssh port from 'info usernet':\n%s" % \
211ff2ebff0SFam Zheng                            usernet_info)
212ff2ebff0SFam Zheng
2138dd38334SGerd Hoffmann    def console_init(self, timeout = 120):
2148dd38334SGerd Hoffmann        vm = self._guest
2158dd38334SGerd Hoffmann        vm.console_socket.settimeout(timeout)
2168dd38334SGerd Hoffmann
2178dd38334SGerd Hoffmann    def console_log(self, text):
2188dd38334SGerd Hoffmann        for line in re.split("[\r\n]", text):
2198dd38334SGerd Hoffmann            # filter out terminal escape sequences
2208dd38334SGerd Hoffmann            line = re.sub("\x1b\[[0-9;?]*[a-zA-Z]", "", line)
2218dd38334SGerd Hoffmann            line = re.sub("\x1b\([0-9;?]*[a-zA-Z]", "", line)
2228dd38334SGerd Hoffmann            # replace unprintable chars
2238dd38334SGerd Hoffmann            line = re.sub("\x1b", "<esc>", line)
2248dd38334SGerd Hoffmann            line = re.sub("[\x00-\x1f]", ".", line)
2258dd38334SGerd Hoffmann            line = re.sub("[\x80-\xff]", ".", line)
2268dd38334SGerd Hoffmann            if line == "":
2278dd38334SGerd Hoffmann                continue
2288dd38334SGerd Hoffmann            # log console line
2298dd38334SGerd Hoffmann            sys.stderr.write("con recv: %s\n" % line)
2308dd38334SGerd Hoffmann
23160136e06SGerd Hoffmann    def console_wait(self, expect, expectalt = None):
2328dd38334SGerd Hoffmann        vm = self._guest
2338dd38334SGerd Hoffmann        output = ""
2348dd38334SGerd Hoffmann        while True:
2358dd38334SGerd Hoffmann            try:
2368dd38334SGerd Hoffmann                chars = vm.console_socket.recv(1)
2378dd38334SGerd Hoffmann            except socket.timeout:
2388dd38334SGerd Hoffmann                sys.stderr.write("console: *** read timeout ***\n")
2398dd38334SGerd Hoffmann                sys.stderr.write("console: waiting for: '%s'\n" % expect)
24060136e06SGerd Hoffmann                if not expectalt is None:
24160136e06SGerd Hoffmann                    sys.stderr.write("console: waiting for: '%s' (alt)\n" % expectalt)
2428dd38334SGerd Hoffmann                sys.stderr.write("console: line buffer:\n")
2438dd38334SGerd Hoffmann                sys.stderr.write("\n")
2448dd38334SGerd Hoffmann                self.console_log(output.rstrip())
2458dd38334SGerd Hoffmann                sys.stderr.write("\n")
2468dd38334SGerd Hoffmann                raise
2478dd38334SGerd Hoffmann            output += chars.decode("latin1")
2488dd38334SGerd Hoffmann            if expect in output:
2498dd38334SGerd Hoffmann                break
25060136e06SGerd Hoffmann            if not expectalt is None and expectalt in output:
25160136e06SGerd Hoffmann                break
2528dd38334SGerd Hoffmann            if "\r" in output or "\n" in output:
2538dd38334SGerd Hoffmann                lines = re.split("[\r\n]", output)
2548dd38334SGerd Hoffmann                output = lines.pop()
2558dd38334SGerd Hoffmann                if self.debug:
2568dd38334SGerd Hoffmann                    self.console_log("\n".join(lines))
2578dd38334SGerd Hoffmann        if self.debug:
2588dd38334SGerd Hoffmann            self.console_log(output)
25960136e06SGerd Hoffmann        if not expectalt is None and expectalt in output:
26060136e06SGerd Hoffmann            return False
26160136e06SGerd Hoffmann        return True
2628dd38334SGerd Hoffmann
2636c4f0416SGerd Hoffmann    def console_consume(self):
2646c4f0416SGerd Hoffmann        vm = self._guest
2656c4f0416SGerd Hoffmann        output = ""
2666c4f0416SGerd Hoffmann        vm.console_socket.setblocking(0)
2676c4f0416SGerd Hoffmann        while True:
2686c4f0416SGerd Hoffmann            try:
2696c4f0416SGerd Hoffmann                chars = vm.console_socket.recv(1)
2706c4f0416SGerd Hoffmann            except:
2716c4f0416SGerd Hoffmann                break
2726c4f0416SGerd Hoffmann            output += chars.decode("latin1")
2736c4f0416SGerd Hoffmann            if "\r" in output or "\n" in output:
2746c4f0416SGerd Hoffmann                lines = re.split("[\r\n]", output)
2756c4f0416SGerd Hoffmann                output = lines.pop()
2766c4f0416SGerd Hoffmann                if self.debug:
2776c4f0416SGerd Hoffmann                    self.console_log("\n".join(lines))
2786c4f0416SGerd Hoffmann        if self.debug:
2796c4f0416SGerd Hoffmann            self.console_log(output)
2806c4f0416SGerd Hoffmann        vm.console_socket.setblocking(1)
2816c4f0416SGerd Hoffmann
2828dd38334SGerd Hoffmann    def console_send(self, command):
2838dd38334SGerd Hoffmann        vm = self._guest
2848dd38334SGerd Hoffmann        if self.debug:
2858dd38334SGerd Hoffmann            logline = re.sub("\n", "<enter>", command)
2868dd38334SGerd Hoffmann            logline = re.sub("[\x00-\x1f]", ".", logline)
2878dd38334SGerd Hoffmann            sys.stderr.write("con send: %s\n" % logline)
2888dd38334SGerd Hoffmann        for char in list(command):
2898dd38334SGerd Hoffmann            vm.console_socket.send(char.encode("utf-8"))
2908dd38334SGerd Hoffmann            time.sleep(0.01)
2918dd38334SGerd Hoffmann
2928dd38334SGerd Hoffmann    def console_wait_send(self, wait, command):
2938dd38334SGerd Hoffmann        self.console_wait(wait)
2948dd38334SGerd Hoffmann        self.console_send(command)
2958dd38334SGerd Hoffmann
2968dd38334SGerd Hoffmann    def console_ssh_init(self, prompt, user, pw):
2978dd38334SGerd Hoffmann        sshkey_cmd = "echo '%s' > .ssh/authorized_keys\n" % SSH_PUB_KEY.rstrip()
2988dd38334SGerd Hoffmann        self.console_wait_send("login:",    "%s\n" % user)
2998dd38334SGerd Hoffmann        self.console_wait_send("Password:", "%s\n" % pw)
3008dd38334SGerd Hoffmann        self.console_wait_send(prompt,      "mkdir .ssh\n")
3018dd38334SGerd Hoffmann        self.console_wait_send(prompt,      sshkey_cmd)
3028dd38334SGerd Hoffmann        self.console_wait_send(prompt,      "chmod 755 .ssh\n")
3038dd38334SGerd Hoffmann        self.console_wait_send(prompt,      "chmod 644 .ssh/authorized_keys\n")
3048dd38334SGerd Hoffmann
3058dd38334SGerd Hoffmann    def console_sshd_config(self, prompt):
3068dd38334SGerd Hoffmann        self.console_wait(prompt)
3078dd38334SGerd Hoffmann        self.console_send("echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config\n")
3088dd38334SGerd Hoffmann        for var in self.envvars:
3098dd38334SGerd Hoffmann            self.console_wait(prompt)
3108dd38334SGerd Hoffmann            self.console_send("echo 'AcceptEnv %s' >> /etc/ssh/sshd_config\n" % var)
3118dd38334SGerd Hoffmann
3128dd38334SGerd Hoffmann    def print_step(self, text):
3138dd38334SGerd Hoffmann        sys.stderr.write("### %s ...\n" % text)
3148dd38334SGerd Hoffmann
3156b699ae1SPeter Maydell    def wait_ssh(self, seconds=300):
316*c9de3935SRobert Foley        # Allow more time for VM to boot under TCG.
317*c9de3935SRobert Foley        if not kvm_available(self.arch):
318*c9de3935SRobert Foley            seconds *= self.tcg_ssh_timeout_multiplier
319ff2ebff0SFam Zheng        starttime = datetime.datetime.now()
320f5d3d218SPhilippe Mathieu-Daudé        endtime = starttime + datetime.timedelta(seconds=seconds)
321ff2ebff0SFam Zheng        guest_up = False
322f5d3d218SPhilippe Mathieu-Daudé        while datetime.datetime.now() < endtime:
323ff2ebff0SFam Zheng            if self.ssh("exit 0") == 0:
324ff2ebff0SFam Zheng                guest_up = True
325ff2ebff0SFam Zheng                break
326f5d3d218SPhilippe Mathieu-Daudé            seconds = (endtime - datetime.datetime.now()).total_seconds()
327f5d3d218SPhilippe Mathieu-Daudé            logging.debug("%ds before timeout", seconds)
328ff2ebff0SFam Zheng            time.sleep(1)
329ff2ebff0SFam Zheng        if not guest_up:
330ff2ebff0SFam Zheng            raise Exception("Timeout while waiting for guest ssh")
331ff2ebff0SFam Zheng
332ff2ebff0SFam Zheng    def shutdown(self):
333ff2ebff0SFam Zheng        self._guest.shutdown()
334ff2ebff0SFam Zheng
335ff2ebff0SFam Zheng    def wait(self):
336ff2ebff0SFam Zheng        self._guest.wait()
337ff2ebff0SFam Zheng
338b3f94b2fSGerd Hoffmann    def graceful_shutdown(self):
339b3f94b2fSGerd Hoffmann        self.ssh_root(self.poweroff)
340b3f94b2fSGerd Hoffmann        self._guest.wait()
341b3f94b2fSGerd Hoffmann
342ff2ebff0SFam Zheng    def qmp(self, *args, **kwargs):
343ff2ebff0SFam Zheng        return self._guest.qmp(*args, **kwargs)
344ff2ebff0SFam Zheng
34563a24c5eSPhilippe Mathieu-Daudédef parse_args(vmcls):
3468a6e007eSPhilippe Mathieu-Daudé
3478a6e007eSPhilippe Mathieu-Daudé    def get_default_jobs():
34863a24c5eSPhilippe Mathieu-Daudé        if kvm_available(vmcls.arch):
3493ad3e36eSWainer dos Santos Moschetta            return multiprocessing.cpu_count() // 2
3508a6e007eSPhilippe Mathieu-Daudé        else:
3518a6e007eSPhilippe Mathieu-Daudé            return 1
3528a6e007eSPhilippe Mathieu-Daudé
353ff2ebff0SFam Zheng    parser = optparse.OptionParser(
354ff2ebff0SFam Zheng        description="VM test utility.  Exit codes: "
355ff2ebff0SFam Zheng                    "0 = success, "
356ff2ebff0SFam Zheng                    "1 = command line error, "
357ff2ebff0SFam Zheng                    "2 = environment initialization failed, "
358ff2ebff0SFam Zheng                    "3 = test command failed")
359ff2ebff0SFam Zheng    parser.add_option("--debug", "-D", action="store_true",
360ff2ebff0SFam Zheng                      help="enable debug output")
36163a24c5eSPhilippe Mathieu-Daudé    parser.add_option("--image", "-i", default="%s.img" % vmcls.name,
362ff2ebff0SFam Zheng                      help="image file name")
363ff2ebff0SFam Zheng    parser.add_option("--force", "-f", action="store_true",
364ff2ebff0SFam Zheng                      help="force build image even if image exists")
3658a6e007eSPhilippe Mathieu-Daudé    parser.add_option("--jobs", type=int, default=get_default_jobs(),
366ff2ebff0SFam Zheng                      help="number of virtual CPUs")
36741e3340aSPeter Maydell    parser.add_option("--verbose", "-V", action="store_true",
36841e3340aSPeter Maydell                      help="Pass V=1 to builds within the guest")
369ff2ebff0SFam Zheng    parser.add_option("--build-image", "-b", action="store_true",
370ff2ebff0SFam Zheng                      help="build image")
371ff2ebff0SFam Zheng    parser.add_option("--build-qemu",
372ff2ebff0SFam Zheng                      help="build QEMU from source in guest")
3735c2ec9b6SAlex Bennée    parser.add_option("--build-target",
3745c2ec9b6SAlex Bennée                      help="QEMU build target", default="check")
375ff2ebff0SFam Zheng    parser.add_option("--interactive", "-I", action="store_true",
376ff2ebff0SFam Zheng                      help="Interactively run command")
377983c2a77SFam Zheng    parser.add_option("--snapshot", "-s", action="store_true",
378983c2a77SFam Zheng                      help="run tests with a snapshot")
379ff2ebff0SFam Zheng    parser.disable_interspersed_args()
380ff2ebff0SFam Zheng    return parser.parse_args()
381ff2ebff0SFam Zheng
382ff2ebff0SFam Zhengdef main(vmcls):
383ff2ebff0SFam Zheng    try:
38463a24c5eSPhilippe Mathieu-Daudé        args, argv = parse_args(vmcls)
385ff2ebff0SFam Zheng        if not argv and not args.build_qemu and not args.build_image:
386f03868bdSEduardo Habkost            print("Nothing to do?")
387ff2ebff0SFam Zheng            return 1
388fb3b4e6dSEduardo Habkost        logging.basicConfig(level=(logging.DEBUG if args.debug
389fb3b4e6dSEduardo Habkost                                   else logging.WARN))
390ff2ebff0SFam Zheng        vm = vmcls(debug=args.debug, vcpus=args.jobs)
391ff2ebff0SFam Zheng        if args.build_image:
392ff2ebff0SFam Zheng            if os.path.exists(args.image) and not args.force:
393ff2ebff0SFam Zheng                sys.stderr.writelines(["Image file exists: %s\n" % args.image,
394ff2ebff0SFam Zheng                                      "Use --force option to overwrite\n"])
395ff2ebff0SFam Zheng                return 1
396ff2ebff0SFam Zheng            return vm.build_image(args.image)
397ff2ebff0SFam Zheng        if args.build_qemu:
398ff2ebff0SFam Zheng            vm.add_source_dir(args.build_qemu)
399ff2ebff0SFam Zheng            cmd = [vm.BUILD_SCRIPT.format(
400ff2ebff0SFam Zheng                   configure_opts = " ".join(argv),
4013ace9be6SGerd Hoffmann                   jobs=int(args.jobs),
4025c2ec9b6SAlex Bennée                   target=args.build_target,
40341e3340aSPeter Maydell                   verbose = "V=1" if args.verbose else "")]
404ff2ebff0SFam Zheng        else:
405ff2ebff0SFam Zheng            cmd = argv
406983c2a77SFam Zheng        img = args.image
407983c2a77SFam Zheng        if args.snapshot:
408983c2a77SFam Zheng            img += ",snapshot=on"
409983c2a77SFam Zheng        vm.boot(img)
410ff2ebff0SFam Zheng        vm.wait_ssh()
411ff2ebff0SFam Zheng    except Exception as e:
412ff2ebff0SFam Zheng        if isinstance(e, SystemExit) and e.code == 0:
413ff2ebff0SFam Zheng            return 0
414ff2ebff0SFam Zheng        sys.stderr.write("Failed to prepare guest environment\n")
415ff2ebff0SFam Zheng        traceback.print_exc()
416ff2ebff0SFam Zheng        return 2
417ff2ebff0SFam Zheng
418b3f94b2fSGerd Hoffmann    exitcode = 0
419ff2ebff0SFam Zheng    if vm.ssh(*cmd) != 0:
420b3f94b2fSGerd Hoffmann        exitcode = 3
421bcc388dfSAlex Bennée    if args.interactive:
422b3f94b2fSGerd Hoffmann        vm.ssh()
423b3f94b2fSGerd Hoffmann
424b3f94b2fSGerd Hoffmann    if not args.snapshot:
425b3f94b2fSGerd Hoffmann        vm.graceful_shutdown()
426b3f94b2fSGerd Hoffmann
427b3f94b2fSGerd Hoffmann    return exitcode
428