xref: /openbmc/qemu/tests/vm/basevm.py (revision 6ee982c9abc42e726f9e783ba67bbb7676a9f9b4)
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
64e56c4504SRobert Foley    def __init__(self, debug=False, vcpus=None, genisoimage=None,
65e56c4504SRobert Foley                 build_path=None):
66ff2ebff0SFam Zheng        self._guest = None
6792fecad3SAlex Bennée        self._genisoimage = genisoimage
68e56c4504SRobert Foley        self._build_path = build_path
69ff2ebff0SFam Zheng        self._tmpdir = os.path.realpath(tempfile.mkdtemp(prefix="vm-test-",
70ff2ebff0SFam Zheng                                                         suffix=".tmp",
71ff2ebff0SFam Zheng                                                         dir="."))
72ff2ebff0SFam Zheng        atexit.register(shutil.rmtree, self._tmpdir)
73ff2ebff0SFam Zheng
74ff2ebff0SFam Zheng        self._ssh_key_file = os.path.join(self._tmpdir, "id_rsa")
75ff2ebff0SFam Zheng        open(self._ssh_key_file, "w").write(SSH_KEY)
76ff2ebff0SFam Zheng        subprocess.check_call(["chmod", "600", self._ssh_key_file])
77ff2ebff0SFam Zheng
78ff2ebff0SFam Zheng        self._ssh_pub_key_file = os.path.join(self._tmpdir, "id_rsa.pub")
79ff2ebff0SFam Zheng        open(self._ssh_pub_key_file, "w").write(SSH_PUB_KEY)
80ff2ebff0SFam Zheng
81ff2ebff0SFam Zheng        self.debug = debug
82ff2ebff0SFam Zheng        self._stderr = sys.stderr
83ff2ebff0SFam Zheng        self._devnull = open(os.devnull, "w")
84ff2ebff0SFam Zheng        if self.debug:
85ff2ebff0SFam Zheng            self._stdout = sys.stdout
86ff2ebff0SFam Zheng        else:
87ff2ebff0SFam Zheng            self._stdout = self._devnull
88ff2ebff0SFam Zheng        self._args = [ \
89eb2712f5SPeter Maydell            "-nodefaults", "-m", "4G",
90b33bd859SPeter Maydell            "-cpu", "max",
915b790481SEduardo Habkost            "-netdev", "user,id=vnet,hostfwd=:127.0.0.1:0-:22" +
925b790481SEduardo Habkost                       (",ipv6=no" if not self.ipv6 else ""),
93ff2ebff0SFam Zheng            "-device", "virtio-net-pci,netdev=vnet",
948dd38334SGerd Hoffmann            "-vnc", "127.0.0.1:0,to=20"]
95071cf5a4SPhilippe Mathieu-Daudé        if vcpus and vcpus > 1:
963ace9be6SGerd Hoffmann            self._args += ["-smp", "%d" % vcpus]
9771531bb5SPhilippe Mathieu-Daudé        if kvm_available(self.arch):
98ff2ebff0SFam Zheng            self._args += ["-enable-kvm"]
99ff2ebff0SFam Zheng        else:
100ff2ebff0SFam Zheng            logging.info("KVM not available, not using -enable-kvm")
101ff2ebff0SFam Zheng        self._data_args = []
102ff2ebff0SFam Zheng
1035b4b4865SAlex Bennée    def _download_with_cache(self, url, sha256sum=None, sha512sum=None):
104ff2ebff0SFam Zheng        def check_sha256sum(fname):
105ff2ebff0SFam Zheng            if not sha256sum:
106ff2ebff0SFam Zheng                return True
107ff2ebff0SFam Zheng            checksum = subprocess.check_output(["sha256sum", fname]).split()[0]
1083ace9be6SGerd Hoffmann            return sha256sum == checksum.decode("utf-8")
109ff2ebff0SFam Zheng
1105b4b4865SAlex Bennée        def check_sha512sum(fname):
1115b4b4865SAlex Bennée            if not sha512sum:
1125b4b4865SAlex Bennée                return True
1135b4b4865SAlex Bennée            checksum = subprocess.check_output(["sha512sum", fname]).split()[0]
1145b4b4865SAlex Bennée            return sha512sum == checksum.decode("utf-8")
1155b4b4865SAlex Bennée
116ff2ebff0SFam Zheng        cache_dir = os.path.expanduser("~/.cache/qemu-vm/download")
117ff2ebff0SFam Zheng        if not os.path.exists(cache_dir):
118ff2ebff0SFam Zheng            os.makedirs(cache_dir)
1193ace9be6SGerd Hoffmann        fname = os.path.join(cache_dir,
1203ace9be6SGerd Hoffmann                             hashlib.sha1(url.encode("utf-8")).hexdigest())
1215b4b4865SAlex Bennée        if os.path.exists(fname) and check_sha256sum(fname) and check_sha512sum(fname):
122ff2ebff0SFam Zheng            return fname
123ff2ebff0SFam Zheng        logging.debug("Downloading %s to %s...", url, fname)
124ff2ebff0SFam Zheng        subprocess.check_call(["wget", "-c", url, "-O", fname + ".download"],
125ff2ebff0SFam Zheng                              stdout=self._stdout, stderr=self._stderr)
126ff2ebff0SFam Zheng        os.rename(fname + ".download", fname)
127ff2ebff0SFam Zheng        return fname
128ff2ebff0SFam Zheng
129796471e9SGerd Hoffmann    def _ssh_do(self, user, cmd, check):
13089adc5b9SRobert Foley        ssh_cmd = ["ssh",
13189adc5b9SRobert Foley                   "-t",
132ff2ebff0SFam Zheng                   "-o", "StrictHostKeyChecking=no",
133ff2ebff0SFam Zheng                   "-o", "UserKnownHostsFile=" + os.devnull,
134ff2ebff0SFam Zheng                   "-o", "ConnectTimeout=1",
135ff2ebff0SFam Zheng                   "-p", self.ssh_port, "-i", self._ssh_key_file]
13689adc5b9SRobert Foley        # If not in debug mode, set ssh to quiet mode to
13789adc5b9SRobert Foley        # avoid printing the results of commands.
13889adc5b9SRobert Foley        if not self.debug:
13989adc5b9SRobert Foley            ssh_cmd.append("-q")
140b08ba163SGerd Hoffmann        for var in self.envvars:
141b08ba163SGerd Hoffmann            ssh_cmd += ['-o', "SendEnv=%s" % var ]
142ff2ebff0SFam Zheng        assert not isinstance(cmd, str)
143ff2ebff0SFam Zheng        ssh_cmd += ["%s@127.0.0.1" % user] + list(cmd)
144ff2ebff0SFam Zheng        logging.debug("ssh_cmd: %s", " ".join(ssh_cmd))
145726c9a3bSFam Zheng        r = subprocess.call(ssh_cmd)
146ff2ebff0SFam Zheng        if check and r != 0:
147ff2ebff0SFam Zheng            raise Exception("SSH command failed: %s" % cmd)
148ff2ebff0SFam Zheng        return r
149ff2ebff0SFam Zheng
150ff2ebff0SFam Zheng    def ssh(self, *cmd):
151ff2ebff0SFam Zheng        return self._ssh_do(self.GUEST_USER, cmd, False)
152ff2ebff0SFam Zheng
153ff2ebff0SFam Zheng    def ssh_root(self, *cmd):
154ff2ebff0SFam Zheng        return self._ssh_do("root", cmd, False)
155ff2ebff0SFam Zheng
156ff2ebff0SFam Zheng    def ssh_check(self, *cmd):
157ff2ebff0SFam Zheng        self._ssh_do(self.GUEST_USER, cmd, True)
158ff2ebff0SFam Zheng
159ff2ebff0SFam Zheng    def ssh_root_check(self, *cmd):
160ff2ebff0SFam Zheng        self._ssh_do("root", cmd, True)
161ff2ebff0SFam Zheng
162ff2ebff0SFam Zheng    def build_image(self, img):
163ff2ebff0SFam Zheng        raise NotImplementedError
164ff2ebff0SFam Zheng
1651e48931cSWainer dos Santos Moschetta    def exec_qemu_img(self, *args):
1661e48931cSWainer dos Santos Moschetta        cmd = [os.environ.get("QEMU_IMG", "qemu-img")]
1671e48931cSWainer dos Santos Moschetta        cmd.extend(list(args))
1681e48931cSWainer dos Santos Moschetta        subprocess.check_call(cmd)
1691e48931cSWainer dos Santos Moschetta
170ff2ebff0SFam Zheng    def add_source_dir(self, src_dir):
1713ace9be6SGerd Hoffmann        name = "data-" + hashlib.sha1(src_dir.encode("utf-8")).hexdigest()[:5]
172ff2ebff0SFam Zheng        tarfile = os.path.join(self._tmpdir, name + ".tar")
173ff2ebff0SFam Zheng        logging.debug("Creating archive %s for src_dir dir: %s", tarfile, src_dir)
174ff2ebff0SFam Zheng        subprocess.check_call(["./scripts/archive-source.sh", tarfile],
175ff2ebff0SFam Zheng                              cwd=src_dir, stdin=self._devnull,
176ff2ebff0SFam Zheng                              stdout=self._stdout, stderr=self._stderr)
177ff2ebff0SFam Zheng        self._data_args += ["-drive",
178ff2ebff0SFam Zheng                            "file=%s,if=none,id=%s,cache=writeback,format=raw" % \
179ff2ebff0SFam Zheng                                    (tarfile, name),
180ff2ebff0SFam Zheng                            "-device",
181ff2ebff0SFam Zheng                            "virtio-blk,drive=%s,serial=%s,bootindex=1" % (name, name)]
182ff2ebff0SFam Zheng
183ff2ebff0SFam Zheng    def boot(self, img, extra_args=[]):
184ff2ebff0SFam Zheng        args = self._args + [
185ff2ebff0SFam Zheng            "-drive", "file=%s,if=none,id=drive0,cache=writeback" % img,
186ff2ebff0SFam Zheng            "-device", "virtio-blk,drive=drive0,bootindex=0"]
187ff2ebff0SFam Zheng        args += self._data_args + extra_args
188ff2ebff0SFam Zheng        logging.debug("QEMU args: %s", " ".join(args))
189e56c4504SRobert Foley        qemu_path = get_qemu_path(self.arch, self._build_path)
190e56c4504SRobert Foley        guest = QEMUMachine(binary=qemu_path, args=args)
1918dd38334SGerd Hoffmann        guest.set_machine('pc')
1928dd38334SGerd Hoffmann        guest.set_console()
193ff2ebff0SFam Zheng        try:
194ff2ebff0SFam Zheng            guest.launch()
195ff2ebff0SFam Zheng        except:
196ff2ebff0SFam Zheng            logging.error("Failed to launch QEMU, command line:")
197e56c4504SRobert Foley            logging.error(" ".join([qemu_path] + args))
198ff2ebff0SFam Zheng            logging.error("Log:")
199ff2ebff0SFam Zheng            logging.error(guest.get_log())
200ff2ebff0SFam Zheng            logging.error("QEMU version >= 2.10 is required")
201ff2ebff0SFam Zheng            raise
202ff2ebff0SFam Zheng        atexit.register(self.shutdown)
203ff2ebff0SFam Zheng        self._guest = guest
204ff2ebff0SFam Zheng        usernet_info = guest.qmp("human-monitor-command",
205ff2ebff0SFam Zheng                                 command_line="info usernet")
206ff2ebff0SFam Zheng        self.ssh_port = None
207ff2ebff0SFam Zheng        for l in usernet_info["return"].splitlines():
208ff2ebff0SFam Zheng            fields = l.split()
209ff2ebff0SFam Zheng            if "TCP[HOST_FORWARD]" in fields and "22" in fields:
210ff2ebff0SFam Zheng                self.ssh_port = l.split()[3]
211ff2ebff0SFam Zheng        if not self.ssh_port:
212ff2ebff0SFam Zheng            raise Exception("Cannot find ssh port from 'info usernet':\n%s" % \
213ff2ebff0SFam Zheng                            usernet_info)
214ff2ebff0SFam Zheng
2158dd38334SGerd Hoffmann    def console_init(self, timeout = 120):
2168dd38334SGerd Hoffmann        vm = self._guest
2178dd38334SGerd Hoffmann        vm.console_socket.settimeout(timeout)
218698a64f9SGerd Hoffmann        self.console_raw_path = os.path.join(vm._temp_dir,
219698a64f9SGerd Hoffmann                                             vm._name + "-console.raw")
220698a64f9SGerd Hoffmann        self.console_raw_file = open(self.console_raw_path, 'wb')
2218dd38334SGerd Hoffmann
2228dd38334SGerd Hoffmann    def console_log(self, text):
2238dd38334SGerd Hoffmann        for line in re.split("[\r\n]", text):
2248dd38334SGerd Hoffmann            # filter out terminal escape sequences
2258dd38334SGerd Hoffmann            line = re.sub("\x1b\[[0-9;?]*[a-zA-Z]", "", line)
2268dd38334SGerd Hoffmann            line = re.sub("\x1b\([0-9;?]*[a-zA-Z]", "", line)
2278dd38334SGerd Hoffmann            # replace unprintable chars
2288dd38334SGerd Hoffmann            line = re.sub("\x1b", "<esc>", line)
2298dd38334SGerd Hoffmann            line = re.sub("[\x00-\x1f]", ".", line)
2308dd38334SGerd Hoffmann            line = re.sub("[\x80-\xff]", ".", line)
2318dd38334SGerd Hoffmann            if line == "":
2328dd38334SGerd Hoffmann                continue
2338dd38334SGerd Hoffmann            # log console line
2348dd38334SGerd Hoffmann            sys.stderr.write("con recv: %s\n" % line)
2358dd38334SGerd Hoffmann
23660136e06SGerd Hoffmann    def console_wait(self, expect, expectalt = None):
2378dd38334SGerd Hoffmann        vm = self._guest
2388dd38334SGerd Hoffmann        output = ""
2398dd38334SGerd Hoffmann        while True:
2408dd38334SGerd Hoffmann            try:
2418dd38334SGerd Hoffmann                chars = vm.console_socket.recv(1)
242698a64f9SGerd Hoffmann                if self.console_raw_file:
243698a64f9SGerd Hoffmann                    self.console_raw_file.write(chars)
244698a64f9SGerd Hoffmann                    self.console_raw_file.flush()
2458dd38334SGerd Hoffmann            except socket.timeout:
2468dd38334SGerd Hoffmann                sys.stderr.write("console: *** read timeout ***\n")
2478dd38334SGerd Hoffmann                sys.stderr.write("console: waiting for: '%s'\n" % expect)
24860136e06SGerd Hoffmann                if not expectalt is None:
24960136e06SGerd Hoffmann                    sys.stderr.write("console: waiting for: '%s' (alt)\n" % expectalt)
2508dd38334SGerd Hoffmann                sys.stderr.write("console: line buffer:\n")
2518dd38334SGerd Hoffmann                sys.stderr.write("\n")
2528dd38334SGerd Hoffmann                self.console_log(output.rstrip())
2538dd38334SGerd Hoffmann                sys.stderr.write("\n")
2548dd38334SGerd Hoffmann                raise
2558dd38334SGerd Hoffmann            output += chars.decode("latin1")
2568dd38334SGerd Hoffmann            if expect in output:
2578dd38334SGerd Hoffmann                break
25860136e06SGerd Hoffmann            if not expectalt is None and expectalt in output:
25960136e06SGerd Hoffmann                break
2608dd38334SGerd Hoffmann            if "\r" in output or "\n" in output:
2618dd38334SGerd Hoffmann                lines = re.split("[\r\n]", output)
2628dd38334SGerd Hoffmann                output = lines.pop()
2638dd38334SGerd Hoffmann                if self.debug:
2648dd38334SGerd Hoffmann                    self.console_log("\n".join(lines))
2658dd38334SGerd Hoffmann        if self.debug:
2668dd38334SGerd Hoffmann            self.console_log(output)
26760136e06SGerd Hoffmann        if not expectalt is None and expectalt in output:
26860136e06SGerd Hoffmann            return False
26960136e06SGerd Hoffmann        return True
2708dd38334SGerd Hoffmann
2716c4f0416SGerd Hoffmann    def console_consume(self):
2726c4f0416SGerd Hoffmann        vm = self._guest
2736c4f0416SGerd Hoffmann        output = ""
2746c4f0416SGerd Hoffmann        vm.console_socket.setblocking(0)
2756c4f0416SGerd Hoffmann        while True:
2766c4f0416SGerd Hoffmann            try:
2776c4f0416SGerd Hoffmann                chars = vm.console_socket.recv(1)
2786c4f0416SGerd Hoffmann            except:
2796c4f0416SGerd Hoffmann                break
2806c4f0416SGerd Hoffmann            output += chars.decode("latin1")
2816c4f0416SGerd Hoffmann            if "\r" in output or "\n" in output:
2826c4f0416SGerd Hoffmann                lines = re.split("[\r\n]", output)
2836c4f0416SGerd Hoffmann                output = lines.pop()
2846c4f0416SGerd Hoffmann                if self.debug:
2856c4f0416SGerd Hoffmann                    self.console_log("\n".join(lines))
2866c4f0416SGerd Hoffmann        if self.debug:
2876c4f0416SGerd Hoffmann            self.console_log(output)
2886c4f0416SGerd Hoffmann        vm.console_socket.setblocking(1)
2896c4f0416SGerd Hoffmann
2908dd38334SGerd Hoffmann    def console_send(self, command):
2918dd38334SGerd Hoffmann        vm = self._guest
2928dd38334SGerd Hoffmann        if self.debug:
2938dd38334SGerd Hoffmann            logline = re.sub("\n", "<enter>", command)
2948dd38334SGerd Hoffmann            logline = re.sub("[\x00-\x1f]", ".", logline)
2958dd38334SGerd Hoffmann            sys.stderr.write("con send: %s\n" % logline)
2968dd38334SGerd Hoffmann        for char in list(command):
2978dd38334SGerd Hoffmann            vm.console_socket.send(char.encode("utf-8"))
2988dd38334SGerd Hoffmann            time.sleep(0.01)
2998dd38334SGerd Hoffmann
3008dd38334SGerd Hoffmann    def console_wait_send(self, wait, command):
3018dd38334SGerd Hoffmann        self.console_wait(wait)
3028dd38334SGerd Hoffmann        self.console_send(command)
3038dd38334SGerd Hoffmann
3048dd38334SGerd Hoffmann    def console_ssh_init(self, prompt, user, pw):
3058dd38334SGerd Hoffmann        sshkey_cmd = "echo '%s' > .ssh/authorized_keys\n" % SSH_PUB_KEY.rstrip()
3068dd38334SGerd Hoffmann        self.console_wait_send("login:",    "%s\n" % user)
3078dd38334SGerd Hoffmann        self.console_wait_send("Password:", "%s\n" % pw)
3088dd38334SGerd Hoffmann        self.console_wait_send(prompt,      "mkdir .ssh\n")
3098dd38334SGerd Hoffmann        self.console_wait_send(prompt,      sshkey_cmd)
3108dd38334SGerd Hoffmann        self.console_wait_send(prompt,      "chmod 755 .ssh\n")
3118dd38334SGerd Hoffmann        self.console_wait_send(prompt,      "chmod 644 .ssh/authorized_keys\n")
3128dd38334SGerd Hoffmann
3138dd38334SGerd Hoffmann    def console_sshd_config(self, prompt):
3148dd38334SGerd Hoffmann        self.console_wait(prompt)
3158dd38334SGerd Hoffmann        self.console_send("echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config\n")
3168dd38334SGerd Hoffmann        for var in self.envvars:
3178dd38334SGerd Hoffmann            self.console_wait(prompt)
3188dd38334SGerd Hoffmann            self.console_send("echo 'AcceptEnv %s' >> /etc/ssh/sshd_config\n" % var)
3198dd38334SGerd Hoffmann
3208dd38334SGerd Hoffmann    def print_step(self, text):
3218dd38334SGerd Hoffmann        sys.stderr.write("### %s ...\n" % text)
3228dd38334SGerd Hoffmann
323*6ee982c9SRobert Foley    def wait_ssh(self, wait_root=False, seconds=300, cmd="exit 0"):
324c9de3935SRobert Foley        # Allow more time for VM to boot under TCG.
325c9de3935SRobert Foley        if not kvm_available(self.arch):
326c9de3935SRobert Foley            seconds *= self.tcg_ssh_timeout_multiplier
327ff2ebff0SFam Zheng        starttime = datetime.datetime.now()
328f5d3d218SPhilippe Mathieu-Daudé        endtime = starttime + datetime.timedelta(seconds=seconds)
329*6ee982c9SRobert Foley        cmd_success = False
330f5d3d218SPhilippe Mathieu-Daudé        while datetime.datetime.now() < endtime:
331*6ee982c9SRobert Foley            if wait_root and self.ssh_root(cmd) == 0:
332*6ee982c9SRobert Foley                cmd_success = True
333fbb3aa29SRobert Foley                break
334*6ee982c9SRobert Foley            elif self.ssh(cmd) == 0:
335*6ee982c9SRobert Foley                cmd_success = True
336ff2ebff0SFam Zheng                break
337f5d3d218SPhilippe Mathieu-Daudé            seconds = (endtime - datetime.datetime.now()).total_seconds()
338f5d3d218SPhilippe Mathieu-Daudé            logging.debug("%ds before timeout", seconds)
339ff2ebff0SFam Zheng            time.sleep(1)
340*6ee982c9SRobert Foley        if not cmd_success:
341ff2ebff0SFam Zheng            raise Exception("Timeout while waiting for guest ssh")
342ff2ebff0SFam Zheng
343ff2ebff0SFam Zheng    def shutdown(self):
344ff2ebff0SFam Zheng        self._guest.shutdown()
345ff2ebff0SFam Zheng
346ff2ebff0SFam Zheng    def wait(self):
347ff2ebff0SFam Zheng        self._guest.wait()
348ff2ebff0SFam Zheng
349b3f94b2fSGerd Hoffmann    def graceful_shutdown(self):
350b3f94b2fSGerd Hoffmann        self.ssh_root(self.poweroff)
351b3f94b2fSGerd Hoffmann        self._guest.wait()
352b3f94b2fSGerd Hoffmann
353ff2ebff0SFam Zheng    def qmp(self, *args, **kwargs):
354ff2ebff0SFam Zheng        return self._guest.qmp(*args, **kwargs)
355ff2ebff0SFam Zheng
356b081986cSRobert Foley    def gen_cloud_init_iso(self):
357b081986cSRobert Foley        cidir = self._tmpdir
358b081986cSRobert Foley        mdata = open(os.path.join(cidir, "meta-data"), "w")
359b081986cSRobert Foley        name = self.name.replace(".","-")
360b081986cSRobert Foley        mdata.writelines(["instance-id: {}-vm-0\n".format(name),
361b081986cSRobert Foley                          "local-hostname: {}-guest\n".format(name)])
362b081986cSRobert Foley        mdata.close()
363b081986cSRobert Foley        udata = open(os.path.join(cidir, "user-data"), "w")
364f01454adSAlex Bennée        print("guest user:pw {}:{}".format(self.GUEST_USER,
365f01454adSAlex Bennée                                           self.GUEST_PASS))
366b081986cSRobert Foley        udata.writelines(["#cloud-config\n",
367b081986cSRobert Foley                          "chpasswd:\n",
368b081986cSRobert Foley                          "  list: |\n",
369f01454adSAlex Bennée                          "    root:%s\n" % self.ROOT_PASS,
370f01454adSAlex Bennée                          "    %s:%s\n" % (self.GUEST_USER,
371f01454adSAlex Bennée                                           self.GUEST_PASS),
372b081986cSRobert Foley                          "  expire: False\n",
373b081986cSRobert Foley                          "users:\n",
374f01454adSAlex Bennée                          "  - name: %s\n" % self.GUEST_USER,
375b081986cSRobert Foley                          "    sudo: ALL=(ALL) NOPASSWD:ALL\n",
376b081986cSRobert Foley                          "    ssh-authorized-keys:\n",
377f01454adSAlex Bennée                          "    - %s\n" % SSH_PUB_KEY,
378b081986cSRobert Foley                          "  - name: root\n",
379b081986cSRobert Foley                          "    ssh-authorized-keys:\n",
380f01454adSAlex Bennée                          "    - %s\n" % SSH_PUB_KEY,
381b081986cSRobert Foley                          "locale: en_US.UTF-8\n"])
382b081986cSRobert Foley        proxy = os.environ.get("http_proxy")
383b081986cSRobert Foley        if not proxy is None:
384b081986cSRobert Foley            udata.writelines(["apt:\n",
385b081986cSRobert Foley                              "  proxy: %s" % proxy])
386b081986cSRobert Foley        udata.close()
38792fecad3SAlex Bennée        subprocess.check_call([self._genisoimage, "-output", "cloud-init.iso",
388b081986cSRobert Foley                               "-volid", "cidata", "-joliet", "-rock",
389b081986cSRobert Foley                               "user-data", "meta-data"],
390b081986cSRobert Foley                              cwd=cidir,
391b081986cSRobert Foley                              stdin=self._devnull, stdout=self._stdout,
392b081986cSRobert Foley                              stderr=self._stdout)
393b081986cSRobert Foley
394b081986cSRobert Foley        return os.path.join(cidir, "cloud-init.iso")
395b081986cSRobert Foley
396e56c4504SRobert Foleydef get_qemu_path(arch, build_path=None):
397e56c4504SRobert Foley    """Fetch the path to the qemu binary."""
398e56c4504SRobert Foley    # If QEMU environment variable set, it takes precedence
399e56c4504SRobert Foley    if "QEMU" in os.environ:
400e56c4504SRobert Foley        qemu_path = os.environ["QEMU"]
401e56c4504SRobert Foley    elif build_path:
402e56c4504SRobert Foley        qemu_path = os.path.join(build_path, arch + "-softmmu")
403e56c4504SRobert Foley        qemu_path = os.path.join(qemu_path, "qemu-system-" + arch)
404e56c4504SRobert Foley    else:
405e56c4504SRobert Foley        # Default is to use system path for qemu.
406e56c4504SRobert Foley        qemu_path = "qemu-system-" + arch
407e56c4504SRobert Foley    return qemu_path
408e56c4504SRobert Foley
40963a24c5eSPhilippe Mathieu-Daudédef parse_args(vmcls):
4108a6e007eSPhilippe Mathieu-Daudé
4118a6e007eSPhilippe Mathieu-Daudé    def get_default_jobs():
41263a24c5eSPhilippe Mathieu-Daudé        if kvm_available(vmcls.arch):
4133ad3e36eSWainer dos Santos Moschetta            return multiprocessing.cpu_count() // 2
4148a6e007eSPhilippe Mathieu-Daudé        else:
4158a6e007eSPhilippe Mathieu-Daudé            return 1
4168a6e007eSPhilippe Mathieu-Daudé
417ff2ebff0SFam Zheng    parser = optparse.OptionParser(
418ff2ebff0SFam Zheng        description="VM test utility.  Exit codes: "
419ff2ebff0SFam Zheng                    "0 = success, "
420ff2ebff0SFam Zheng                    "1 = command line error, "
421ff2ebff0SFam Zheng                    "2 = environment initialization failed, "
422ff2ebff0SFam Zheng                    "3 = test command failed")
423ff2ebff0SFam Zheng    parser.add_option("--debug", "-D", action="store_true",
424ff2ebff0SFam Zheng                      help="enable debug output")
42563a24c5eSPhilippe Mathieu-Daudé    parser.add_option("--image", "-i", default="%s.img" % vmcls.name,
426ff2ebff0SFam Zheng                      help="image file name")
427ff2ebff0SFam Zheng    parser.add_option("--force", "-f", action="store_true",
428ff2ebff0SFam Zheng                      help="force build image even if image exists")
4298a6e007eSPhilippe Mathieu-Daudé    parser.add_option("--jobs", type=int, default=get_default_jobs(),
430ff2ebff0SFam Zheng                      help="number of virtual CPUs")
43141e3340aSPeter Maydell    parser.add_option("--verbose", "-V", action="store_true",
43241e3340aSPeter Maydell                      help="Pass V=1 to builds within the guest")
433ff2ebff0SFam Zheng    parser.add_option("--build-image", "-b", action="store_true",
434ff2ebff0SFam Zheng                      help="build image")
435ff2ebff0SFam Zheng    parser.add_option("--build-qemu",
436ff2ebff0SFam Zheng                      help="build QEMU from source in guest")
4375c2ec9b6SAlex Bennée    parser.add_option("--build-target",
4385c2ec9b6SAlex Bennée                      help="QEMU build target", default="check")
439e56c4504SRobert Foley    parser.add_option("--build-path", default=None,
440e56c4504SRobert Foley                      help="Path of build directory, "\
441e56c4504SRobert Foley                           "for using build tree QEMU binary. ")
442ff2ebff0SFam Zheng    parser.add_option("--interactive", "-I", action="store_true",
443ff2ebff0SFam Zheng                      help="Interactively run command")
444983c2a77SFam Zheng    parser.add_option("--snapshot", "-s", action="store_true",
445983c2a77SFam Zheng                      help="run tests with a snapshot")
44692fecad3SAlex Bennée    parser.add_option("--genisoimage", default="genisoimage",
44792fecad3SAlex Bennée                      help="iso imaging tool")
448ff2ebff0SFam Zheng    parser.disable_interspersed_args()
449ff2ebff0SFam Zheng    return parser.parse_args()
450ff2ebff0SFam Zheng
451ff2ebff0SFam Zhengdef main(vmcls):
452ff2ebff0SFam Zheng    try:
45363a24c5eSPhilippe Mathieu-Daudé        args, argv = parse_args(vmcls)
454ff2ebff0SFam Zheng        if not argv and not args.build_qemu and not args.build_image:
455f03868bdSEduardo Habkost            print("Nothing to do?")
456ff2ebff0SFam Zheng            return 1
457fb3b4e6dSEduardo Habkost        logging.basicConfig(level=(logging.DEBUG if args.debug
458fb3b4e6dSEduardo Habkost                                   else logging.WARN))
45992fecad3SAlex Bennée        vm = vmcls(debug=args.debug, vcpus=args.jobs,
460e56c4504SRobert Foley                   genisoimage=args.genisoimage, build_path=args.build_path)
461ff2ebff0SFam Zheng        if args.build_image:
462ff2ebff0SFam Zheng            if os.path.exists(args.image) and not args.force:
463ff2ebff0SFam Zheng                sys.stderr.writelines(["Image file exists: %s\n" % args.image,
464ff2ebff0SFam Zheng                                      "Use --force option to overwrite\n"])
465ff2ebff0SFam Zheng                return 1
466ff2ebff0SFam Zheng            return vm.build_image(args.image)
467ff2ebff0SFam Zheng        if args.build_qemu:
468ff2ebff0SFam Zheng            vm.add_source_dir(args.build_qemu)
469ff2ebff0SFam Zheng            cmd = [vm.BUILD_SCRIPT.format(
470ff2ebff0SFam Zheng                   configure_opts = " ".join(argv),
4713ace9be6SGerd Hoffmann                   jobs=int(args.jobs),
4725c2ec9b6SAlex Bennée                   target=args.build_target,
47341e3340aSPeter Maydell                   verbose = "V=1" if args.verbose else "")]
474ff2ebff0SFam Zheng        else:
475ff2ebff0SFam Zheng            cmd = argv
476983c2a77SFam Zheng        img = args.image
477983c2a77SFam Zheng        if args.snapshot:
478983c2a77SFam Zheng            img += ",snapshot=on"
479983c2a77SFam Zheng        vm.boot(img)
480ff2ebff0SFam Zheng        vm.wait_ssh()
481ff2ebff0SFam Zheng    except Exception as e:
482ff2ebff0SFam Zheng        if isinstance(e, SystemExit) and e.code == 0:
483ff2ebff0SFam Zheng            return 0
484ff2ebff0SFam Zheng        sys.stderr.write("Failed to prepare guest environment\n")
485ff2ebff0SFam Zheng        traceback.print_exc()
486ff2ebff0SFam Zheng        return 2
487ff2ebff0SFam Zheng
488b3f94b2fSGerd Hoffmann    exitcode = 0
489ff2ebff0SFam Zheng    if vm.ssh(*cmd) != 0:
490b3f94b2fSGerd Hoffmann        exitcode = 3
491bcc388dfSAlex Bennée    if args.interactive:
492b3f94b2fSGerd Hoffmann        vm.ssh()
493b3f94b2fSGerd Hoffmann
494b3f94b2fSGerd Hoffmann    if not args.snapshot:
495b3f94b2fSGerd Hoffmann        vm.graceful_shutdown()
496b3f94b2fSGerd Hoffmann
497b3f94b2fSGerd Hoffmann    return exitcode
498