xref: /openbmc/qemu/tests/vm/basevm.py (revision b081986c85fd232c1224f84e136d6a19ebb52a39)
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
60c9de3935SRobert Foley    # Scale up some timeouts under TCG.
61c9de3935SRobert Foley    # 4 is arbitrary, but greater than 2,
62c9de3935SRobert Foley    # since we found we need to wait more than twice as long.
63c9de3935SRobert 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
315fbb3aa29SRobert Foley    def wait_ssh(self, wait_root=False, seconds=300):
316c9de3935SRobert Foley        # Allow more time for VM to boot under TCG.
317c9de3935SRobert Foley        if not kvm_available(self.arch):
318c9de3935SRobert 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:
323fbb3aa29SRobert Foley            if wait_root and self.ssh_root("exit 0") == 0:
324fbb3aa29SRobert Foley                guest_up = True
325fbb3aa29SRobert Foley                break
326fbb3aa29SRobert Foley            elif self.ssh("exit 0") == 0:
327ff2ebff0SFam Zheng                guest_up = True
328ff2ebff0SFam Zheng                break
329f5d3d218SPhilippe Mathieu-Daudé            seconds = (endtime - datetime.datetime.now()).total_seconds()
330f5d3d218SPhilippe Mathieu-Daudé            logging.debug("%ds before timeout", seconds)
331ff2ebff0SFam Zheng            time.sleep(1)
332ff2ebff0SFam Zheng        if not guest_up:
333ff2ebff0SFam Zheng            raise Exception("Timeout while waiting for guest ssh")
334ff2ebff0SFam Zheng
335ff2ebff0SFam Zheng    def shutdown(self):
336ff2ebff0SFam Zheng        self._guest.shutdown()
337ff2ebff0SFam Zheng
338ff2ebff0SFam Zheng    def wait(self):
339ff2ebff0SFam Zheng        self._guest.wait()
340ff2ebff0SFam Zheng
341b3f94b2fSGerd Hoffmann    def graceful_shutdown(self):
342b3f94b2fSGerd Hoffmann        self.ssh_root(self.poweroff)
343b3f94b2fSGerd Hoffmann        self._guest.wait()
344b3f94b2fSGerd Hoffmann
345ff2ebff0SFam Zheng    def qmp(self, *args, **kwargs):
346ff2ebff0SFam Zheng        return self._guest.qmp(*args, **kwargs)
347ff2ebff0SFam Zheng
348*b081986cSRobert Foley    def gen_cloud_init_iso(self):
349*b081986cSRobert Foley        cidir = self._tmpdir
350*b081986cSRobert Foley        mdata = open(os.path.join(cidir, "meta-data"), "w")
351*b081986cSRobert Foley        name = self.name.replace(".","-")
352*b081986cSRobert Foley        mdata.writelines(["instance-id: {}-vm-0\n".format(name),
353*b081986cSRobert Foley                          "local-hostname: {}-guest\n".format(name)])
354*b081986cSRobert Foley        mdata.close()
355*b081986cSRobert Foley        udata = open(os.path.join(cidir, "user-data"), "w")
356*b081986cSRobert Foley        print("guest user:pw {}:{}".format(self._config['guest_user'],
357*b081986cSRobert Foley                                           self._config['guest_pass']))
358*b081986cSRobert Foley        udata.writelines(["#cloud-config\n",
359*b081986cSRobert Foley                          "chpasswd:\n",
360*b081986cSRobert Foley                          "  list: |\n",
361*b081986cSRobert Foley                          "    root:%s\n" % self._config['root_pass'],
362*b081986cSRobert Foley                          "    %s:%s\n" % (self._config['guest_user'],
363*b081986cSRobert Foley                                           self._config['guest_pass']),
364*b081986cSRobert Foley                          "  expire: False\n",
365*b081986cSRobert Foley                          "users:\n",
366*b081986cSRobert Foley                          "  - name: %s\n" % self._config['guest_user'],
367*b081986cSRobert Foley                          "    sudo: ALL=(ALL) NOPASSWD:ALL\n",
368*b081986cSRobert Foley                          "    ssh-authorized-keys:\n",
369*b081986cSRobert Foley                          "    - %s\n" % self._config['ssh_pub_key'],
370*b081986cSRobert Foley                          "  - name: root\n",
371*b081986cSRobert Foley                          "    ssh-authorized-keys:\n",
372*b081986cSRobert Foley                          "    - %s\n" % self._config['ssh_pub_key'],
373*b081986cSRobert Foley                          "locale: en_US.UTF-8\n"])
374*b081986cSRobert Foley        proxy = os.environ.get("http_proxy")
375*b081986cSRobert Foley        if not proxy is None:
376*b081986cSRobert Foley            udata.writelines(["apt:\n",
377*b081986cSRobert Foley                              "  proxy: %s" % proxy])
378*b081986cSRobert Foley        udata.close()
379*b081986cSRobert Foley        subprocess.check_call(["genisoimage", "-output", "cloud-init.iso",
380*b081986cSRobert Foley                               "-volid", "cidata", "-joliet", "-rock",
381*b081986cSRobert Foley                               "user-data", "meta-data"],
382*b081986cSRobert Foley                               cwd=cidir,
383*b081986cSRobert Foley                               stdin=self._devnull, stdout=self._stdout,
384*b081986cSRobert Foley                               stderr=self._stdout)
385*b081986cSRobert Foley
386*b081986cSRobert Foley        return os.path.join(cidir, "cloud-init.iso")
387*b081986cSRobert Foley
38863a24c5eSPhilippe Mathieu-Daudédef parse_args(vmcls):
3898a6e007eSPhilippe Mathieu-Daudé
3908a6e007eSPhilippe Mathieu-Daudé    def get_default_jobs():
39163a24c5eSPhilippe Mathieu-Daudé        if kvm_available(vmcls.arch):
3923ad3e36eSWainer dos Santos Moschetta            return multiprocessing.cpu_count() // 2
3938a6e007eSPhilippe Mathieu-Daudé        else:
3948a6e007eSPhilippe Mathieu-Daudé            return 1
3958a6e007eSPhilippe Mathieu-Daudé
396ff2ebff0SFam Zheng    parser = optparse.OptionParser(
397ff2ebff0SFam Zheng        description="VM test utility.  Exit codes: "
398ff2ebff0SFam Zheng                    "0 = success, "
399ff2ebff0SFam Zheng                    "1 = command line error, "
400ff2ebff0SFam Zheng                    "2 = environment initialization failed, "
401ff2ebff0SFam Zheng                    "3 = test command failed")
402ff2ebff0SFam Zheng    parser.add_option("--debug", "-D", action="store_true",
403ff2ebff0SFam Zheng                      help="enable debug output")
40463a24c5eSPhilippe Mathieu-Daudé    parser.add_option("--image", "-i", default="%s.img" % vmcls.name,
405ff2ebff0SFam Zheng                      help="image file name")
406ff2ebff0SFam Zheng    parser.add_option("--force", "-f", action="store_true",
407ff2ebff0SFam Zheng                      help="force build image even if image exists")
4088a6e007eSPhilippe Mathieu-Daudé    parser.add_option("--jobs", type=int, default=get_default_jobs(),
409ff2ebff0SFam Zheng                      help="number of virtual CPUs")
41041e3340aSPeter Maydell    parser.add_option("--verbose", "-V", action="store_true",
41141e3340aSPeter Maydell                      help="Pass V=1 to builds within the guest")
412ff2ebff0SFam Zheng    parser.add_option("--build-image", "-b", action="store_true",
413ff2ebff0SFam Zheng                      help="build image")
414ff2ebff0SFam Zheng    parser.add_option("--build-qemu",
415ff2ebff0SFam Zheng                      help="build QEMU from source in guest")
4165c2ec9b6SAlex Bennée    parser.add_option("--build-target",
4175c2ec9b6SAlex Bennée                      help="QEMU build target", default="check")
418ff2ebff0SFam Zheng    parser.add_option("--interactive", "-I", action="store_true",
419ff2ebff0SFam Zheng                      help="Interactively run command")
420983c2a77SFam Zheng    parser.add_option("--snapshot", "-s", action="store_true",
421983c2a77SFam Zheng                      help="run tests with a snapshot")
422ff2ebff0SFam Zheng    parser.disable_interspersed_args()
423ff2ebff0SFam Zheng    return parser.parse_args()
424ff2ebff0SFam Zheng
425ff2ebff0SFam Zhengdef main(vmcls):
426ff2ebff0SFam Zheng    try:
42763a24c5eSPhilippe Mathieu-Daudé        args, argv = parse_args(vmcls)
428ff2ebff0SFam Zheng        if not argv and not args.build_qemu and not args.build_image:
429f03868bdSEduardo Habkost            print("Nothing to do?")
430ff2ebff0SFam Zheng            return 1
431fb3b4e6dSEduardo Habkost        logging.basicConfig(level=(logging.DEBUG if args.debug
432fb3b4e6dSEduardo Habkost                                   else logging.WARN))
433ff2ebff0SFam Zheng        vm = vmcls(debug=args.debug, vcpus=args.jobs)
434ff2ebff0SFam Zheng        if args.build_image:
435ff2ebff0SFam Zheng            if os.path.exists(args.image) and not args.force:
436ff2ebff0SFam Zheng                sys.stderr.writelines(["Image file exists: %s\n" % args.image,
437ff2ebff0SFam Zheng                                      "Use --force option to overwrite\n"])
438ff2ebff0SFam Zheng                return 1
439ff2ebff0SFam Zheng            return vm.build_image(args.image)
440ff2ebff0SFam Zheng        if args.build_qemu:
441ff2ebff0SFam Zheng            vm.add_source_dir(args.build_qemu)
442ff2ebff0SFam Zheng            cmd = [vm.BUILD_SCRIPT.format(
443ff2ebff0SFam Zheng                   configure_opts = " ".join(argv),
4443ace9be6SGerd Hoffmann                   jobs=int(args.jobs),
4455c2ec9b6SAlex Bennée                   target=args.build_target,
44641e3340aSPeter Maydell                   verbose = "V=1" if args.verbose else "")]
447ff2ebff0SFam Zheng        else:
448ff2ebff0SFam Zheng            cmd = argv
449983c2a77SFam Zheng        img = args.image
450983c2a77SFam Zheng        if args.snapshot:
451983c2a77SFam Zheng            img += ",snapshot=on"
452983c2a77SFam Zheng        vm.boot(img)
453ff2ebff0SFam Zheng        vm.wait_ssh()
454ff2ebff0SFam Zheng    except Exception as e:
455ff2ebff0SFam Zheng        if isinstance(e, SystemExit) and e.code == 0:
456ff2ebff0SFam Zheng            return 0
457ff2ebff0SFam Zheng        sys.stderr.write("Failed to prepare guest environment\n")
458ff2ebff0SFam Zheng        traceback.print_exc()
459ff2ebff0SFam Zheng        return 2
460ff2ebff0SFam Zheng
461b3f94b2fSGerd Hoffmann    exitcode = 0
462ff2ebff0SFam Zheng    if vm.ssh(*cmd) != 0:
463b3f94b2fSGerd Hoffmann        exitcode = 3
464bcc388dfSAlex Bennée    if args.interactive:
465b3f94b2fSGerd Hoffmann        vm.ssh()
466b3f94b2fSGerd Hoffmann
467b3f94b2fSGerd Hoffmann    if not args.snapshot:
468b3f94b2fSGerd Hoffmann        vm.graceful_shutdown()
469b3f94b2fSGerd Hoffmann
470b3f94b2fSGerd Hoffmann    return exitcode
471