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