]> Git Repo - qemu.git/blob - tests/vm/basevm.py
Merge remote-tracking branch 'remotes/armbru/tags/pull-build-2019-07-02-v2' into...
[qemu.git] / tests / vm / basevm.py
1 #!/usr/bin/env python
2 #
3 # VM testing base class
4 #
5 # Copyright 2017 Red Hat Inc.
6 #
7 # Authors:
8 #  Fam Zheng <[email protected]>
9 #
10 # This code is licensed under the GPL version 2 or later.  See
11 # the COPYING file in the top-level directory.
12 #
13
14 from __future__ import print_function
15 import os
16 import sys
17 import logging
18 import time
19 import datetime
20 sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
21 from qemu import kvm_available
22 from qemu.machine import QEMUMachine
23 import subprocess
24 import hashlib
25 import optparse
26 import atexit
27 import tempfile
28 import shutil
29 import multiprocessing
30 import traceback
31
32 SSH_KEY = open(os.path.join(os.path.dirname(__file__),
33                "..", "keys", "id_rsa")).read()
34 SSH_PUB_KEY = open(os.path.join(os.path.dirname(__file__),
35                    "..", "keys", "id_rsa.pub")).read()
36
37 class BaseVM(object):
38     GUEST_USER = "qemu"
39     GUEST_PASS = "qemupass"
40     ROOT_PASS = "qemupass"
41
42     # The script to run in the guest that builds QEMU
43     BUILD_SCRIPT = ""
44     # The guest name, to be overridden by subclasses
45     name = "#base"
46     # The guest architecture, to be overridden by subclasses
47     arch = "#arch"
48     def __init__(self, debug=False, vcpus=None):
49         self._guest = None
50         self._tmpdir = os.path.realpath(tempfile.mkdtemp(prefix="vm-test-",
51                                                          suffix=".tmp",
52                                                          dir="."))
53         atexit.register(shutil.rmtree, self._tmpdir)
54
55         self._ssh_key_file = os.path.join(self._tmpdir, "id_rsa")
56         open(self._ssh_key_file, "w").write(SSH_KEY)
57         subprocess.check_call(["chmod", "600", self._ssh_key_file])
58
59         self._ssh_pub_key_file = os.path.join(self._tmpdir, "id_rsa.pub")
60         open(self._ssh_pub_key_file, "w").write(SSH_PUB_KEY)
61
62         self.debug = debug
63         self._stderr = sys.stderr
64         self._devnull = open(os.devnull, "w")
65         if self.debug:
66             self._stdout = sys.stdout
67         else:
68             self._stdout = self._devnull
69         self._args = [ \
70             "-nodefaults", "-m", "4G",
71             "-cpu", "max",
72             "-netdev", "user,id=vnet,hostfwd=:127.0.0.1:0-:22",
73             "-device", "virtio-net-pci,netdev=vnet",
74             "-vnc", "127.0.0.1:0,to=20",
75             "-serial", "file:%s" % os.path.join(self._tmpdir, "serial.out")]
76         if vcpus and vcpus > 1:
77             self._args += ["-smp", "%d" % vcpus]
78         if kvm_available(self.arch):
79             self._args += ["-enable-kvm"]
80         else:
81             logging.info("KVM not available, not using -enable-kvm")
82         self._data_args = []
83
84     def _download_with_cache(self, url, sha256sum=None):
85         def check_sha256sum(fname):
86             if not sha256sum:
87                 return True
88             checksum = subprocess.check_output(["sha256sum", fname]).split()[0]
89             return sha256sum == checksum.decode("utf-8")
90
91         cache_dir = os.path.expanduser("~/.cache/qemu-vm/download")
92         if not os.path.exists(cache_dir):
93             os.makedirs(cache_dir)
94         fname = os.path.join(cache_dir,
95                              hashlib.sha1(url.encode("utf-8")).hexdigest())
96         if os.path.exists(fname) and check_sha256sum(fname):
97             return fname
98         logging.debug("Downloading %s to %s...", url, fname)
99         subprocess.check_call(["wget", "-c", url, "-O", fname + ".download"],
100                               stdout=self._stdout, stderr=self._stderr)
101         os.rename(fname + ".download", fname)
102         return fname
103
104     def _ssh_do(self, user, cmd, check, interactive=False):
105         ssh_cmd = ["ssh", "-q",
106                    "-o", "StrictHostKeyChecking=no",
107                    "-o", "UserKnownHostsFile=" + os.devnull,
108                    "-o", "ConnectTimeout=1",
109                    "-p", self.ssh_port, "-i", self._ssh_key_file]
110         if interactive:
111             ssh_cmd += ['-t']
112         assert not isinstance(cmd, str)
113         ssh_cmd += ["%[email protected]" % user] + list(cmd)
114         logging.debug("ssh_cmd: %s", " ".join(ssh_cmd))
115         r = subprocess.call(ssh_cmd)
116         if check and r != 0:
117             raise Exception("SSH command failed: %s" % cmd)
118         return r
119
120     def ssh(self, *cmd):
121         return self._ssh_do(self.GUEST_USER, cmd, False)
122
123     def ssh_interactive(self, *cmd):
124         return self._ssh_do(self.GUEST_USER, cmd, False, True)
125
126     def ssh_root(self, *cmd):
127         return self._ssh_do("root", cmd, False)
128
129     def ssh_check(self, *cmd):
130         self._ssh_do(self.GUEST_USER, cmd, True)
131
132     def ssh_root_check(self, *cmd):
133         self._ssh_do("root", cmd, True)
134
135     def build_image(self, img):
136         raise NotImplementedError
137
138     def add_source_dir(self, src_dir):
139         name = "data-" + hashlib.sha1(src_dir.encode("utf-8")).hexdigest()[:5]
140         tarfile = os.path.join(self._tmpdir, name + ".tar")
141         logging.debug("Creating archive %s for src_dir dir: %s", tarfile, src_dir)
142         subprocess.check_call(["./scripts/archive-source.sh", tarfile],
143                               cwd=src_dir, stdin=self._devnull,
144                               stdout=self._stdout, stderr=self._stderr)
145         self._data_args += ["-drive",
146                             "file=%s,if=none,id=%s,cache=writeback,format=raw" % \
147                                     (tarfile, name),
148                             "-device",
149                             "virtio-blk,drive=%s,serial=%s,bootindex=1" % (name, name)]
150
151     def boot(self, img, extra_args=[]):
152         args = self._args + [
153             "-device", "VGA",
154             "-drive", "file=%s,if=none,id=drive0,cache=writeback" % img,
155             "-device", "virtio-blk,drive=drive0,bootindex=0"]
156         args += self._data_args + extra_args
157         logging.debug("QEMU args: %s", " ".join(args))
158         qemu_bin = os.environ.get("QEMU", "qemu-system-" + self.arch)
159         guest = QEMUMachine(binary=qemu_bin, args=args)
160         try:
161             guest.launch()
162         except:
163             logging.error("Failed to launch QEMU, command line:")
164             logging.error(" ".join([qemu_bin] + args))
165             logging.error("Log:")
166             logging.error(guest.get_log())
167             logging.error("QEMU version >= 2.10 is required")
168             raise
169         atexit.register(self.shutdown)
170         self._guest = guest
171         usernet_info = guest.qmp("human-monitor-command",
172                                  command_line="info usernet")
173         self.ssh_port = None
174         for l in usernet_info["return"].splitlines():
175             fields = l.split()
176             if "TCP[HOST_FORWARD]" in fields and "22" in fields:
177                 self.ssh_port = l.split()[3]
178         if not self.ssh_port:
179             raise Exception("Cannot find ssh port from 'info usernet':\n%s" % \
180                             usernet_info)
181
182     def wait_ssh(self, seconds=300):
183         starttime = datetime.datetime.now()
184         endtime = starttime + datetime.timedelta(seconds=seconds)
185         guest_up = False
186         while datetime.datetime.now() < endtime:
187             if self.ssh("exit 0") == 0:
188                 guest_up = True
189                 break
190             seconds = (endtime - datetime.datetime.now()).total_seconds()
191             logging.debug("%ds before timeout", seconds)
192             time.sleep(1)
193         if not guest_up:
194             raise Exception("Timeout while waiting for guest ssh")
195
196     def shutdown(self):
197         self._guest.shutdown()
198
199     def wait(self):
200         self._guest.wait()
201
202     def qmp(self, *args, **kwargs):
203         return self._guest.qmp(*args, **kwargs)
204
205 def parse_args(vmcls):
206
207     def get_default_jobs():
208         if kvm_available(vmcls.arch):
209             return multiprocessing.cpu_count() // 2
210         else:
211             return 1
212
213     parser = optparse.OptionParser(
214         description="VM test utility.  Exit codes: "
215                     "0 = success, "
216                     "1 = command line error, "
217                     "2 = environment initialization failed, "
218                     "3 = test command failed")
219     parser.add_option("--debug", "-D", action="store_true",
220                       help="enable debug output")
221     parser.add_option("--image", "-i", default="%s.img" % vmcls.name,
222                       help="image file name")
223     parser.add_option("--force", "-f", action="store_true",
224                       help="force build image even if image exists")
225     parser.add_option("--jobs", type=int, default=get_default_jobs(),
226                       help="number of virtual CPUs")
227     parser.add_option("--verbose", "-V", action="store_true",
228                       help="Pass V=1 to builds within the guest")
229     parser.add_option("--build-image", "-b", action="store_true",
230                       help="build image")
231     parser.add_option("--build-qemu",
232                       help="build QEMU from source in guest")
233     parser.add_option("--build-target",
234                       help="QEMU build target", default="check")
235     parser.add_option("--interactive", "-I", action="store_true",
236                       help="Interactively run command")
237     parser.add_option("--snapshot", "-s", action="store_true",
238                       help="run tests with a snapshot")
239     parser.disable_interspersed_args()
240     return parser.parse_args()
241
242 def main(vmcls):
243     try:
244         args, argv = parse_args(vmcls)
245         if not argv and not args.build_qemu and not args.build_image:
246             print("Nothing to do?")
247             return 1
248         logging.basicConfig(level=(logging.DEBUG if args.debug
249                                    else logging.WARN))
250         vm = vmcls(debug=args.debug, vcpus=args.jobs)
251         if args.build_image:
252             if os.path.exists(args.image) and not args.force:
253                 sys.stderr.writelines(["Image file exists: %s\n" % args.image,
254                                       "Use --force option to overwrite\n"])
255                 return 1
256             return vm.build_image(args.image)
257         if args.build_qemu:
258             vm.add_source_dir(args.build_qemu)
259             cmd = [vm.BUILD_SCRIPT.format(
260                    configure_opts = " ".join(argv),
261                    jobs=int(args.jobs),
262                    target=args.build_target,
263                    verbose = "V=1" if args.verbose else "")]
264         else:
265             cmd = argv
266         img = args.image
267         if args.snapshot:
268             img += ",snapshot=on"
269         vm.boot(img)
270         vm.wait_ssh()
271     except Exception as e:
272         if isinstance(e, SystemExit) and e.code == 0:
273             return 0
274         sys.stderr.write("Failed to prepare guest environment\n")
275         traceback.print_exc()
276         return 2
277
278     if args.interactive:
279         if vm.ssh_interactive(*cmd) == 0:
280             return 0
281         vm.ssh_interactive()
282         return 3
283     else:
284         if vm.ssh(*cmd) != 0:
285             return 3
This page took 0.037057 seconds and 4 git commands to generate.