]> git.proxmox.com Git - mirror_qemu.git/blob - tests/vm/basevm.py
Merge remote-tracking branch 'remotes/armbru/tags/pull-build-2019-07-02-v2' into...
[mirror_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 <famz@redhat.com>
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 += ["%s@127.0.0.1" % 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