fuzz: add support for qos-assisted fuzz targets
[qemu/ar7.git] / tests / vm / basevm.py
blob4dee6647e6e67f77a7d7f9de5b70fe414be2ba0f
2 # VM testing base class
4 # Copyright 2017-2019 Red Hat Inc.
6 # Authors:
7 # Fam Zheng <famz@redhat.com>
8 # Gerd Hoffmann <kraxel@redhat.com>
10 # This code is licensed under the GPL version 2 or later. See
11 # the COPYING file in the top-level directory.
14 import os
15 import re
16 import sys
17 import socket
18 import logging
19 import time
20 import datetime
21 sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
22 from qemu.accel import kvm_available
23 from qemu.machine import QEMUMachine
24 import subprocess
25 import hashlib
26 import optparse
27 import atexit
28 import tempfile
29 import shutil
30 import multiprocessing
31 import traceback
33 SSH_KEY = open(os.path.join(os.path.dirname(__file__),
34 "..", "keys", "id_rsa")).read()
35 SSH_PUB_KEY = open(os.path.join(os.path.dirname(__file__),
36 "..", "keys", "id_rsa.pub")).read()
38 class BaseVM(object):
39 GUEST_USER = "qemu"
40 GUEST_PASS = "qemupass"
41 ROOT_PASS = "qemupass"
43 envvars = [
44 "https_proxy",
45 "http_proxy",
46 "ftp_proxy",
47 "no_proxy",
50 # The script to run in the guest that builds QEMU
51 BUILD_SCRIPT = ""
52 # The guest name, to be overridden by subclasses
53 name = "#base"
54 # The guest architecture, to be overridden by subclasses
55 arch = "#arch"
56 # command to halt the guest, can be overridden by subclasses
57 poweroff = "poweroff"
58 # enable IPv6 networking
59 ipv6 = True
60 def __init__(self, debug=False, vcpus=None):
61 self._guest = None
62 self._tmpdir = os.path.realpath(tempfile.mkdtemp(prefix="vm-test-",
63 suffix=".tmp",
64 dir="."))
65 atexit.register(shutil.rmtree, self._tmpdir)
67 self._ssh_key_file = os.path.join(self._tmpdir, "id_rsa")
68 open(self._ssh_key_file, "w").write(SSH_KEY)
69 subprocess.check_call(["chmod", "600", self._ssh_key_file])
71 self._ssh_pub_key_file = os.path.join(self._tmpdir, "id_rsa.pub")
72 open(self._ssh_pub_key_file, "w").write(SSH_PUB_KEY)
74 self.debug = debug
75 self._stderr = sys.stderr
76 self._devnull = open(os.devnull, "w")
77 if self.debug:
78 self._stdout = sys.stdout
79 else:
80 self._stdout = self._devnull
81 self._args = [ \
82 "-nodefaults", "-m", "4G",
83 "-cpu", "max",
84 "-netdev", "user,id=vnet,hostfwd=:127.0.0.1:0-:22" +
85 (",ipv6=no" if not self.ipv6 else ""),
86 "-device", "virtio-net-pci,netdev=vnet",
87 "-vnc", "127.0.0.1:0,to=20"]
88 if vcpus and vcpus > 1:
89 self._args += ["-smp", "%d" % vcpus]
90 if kvm_available(self.arch):
91 self._args += ["-enable-kvm"]
92 else:
93 logging.info("KVM not available, not using -enable-kvm")
94 self._data_args = []
96 def _download_with_cache(self, url, sha256sum=None, sha512sum=None):
97 def check_sha256sum(fname):
98 if not sha256sum:
99 return True
100 checksum = subprocess.check_output(["sha256sum", fname]).split()[0]
101 return sha256sum == checksum.decode("utf-8")
103 def check_sha512sum(fname):
104 if not sha512sum:
105 return True
106 checksum = subprocess.check_output(["sha512sum", fname]).split()[0]
107 return sha512sum == checksum.decode("utf-8")
109 cache_dir = os.path.expanduser("~/.cache/qemu-vm/download")
110 if not os.path.exists(cache_dir):
111 os.makedirs(cache_dir)
112 fname = os.path.join(cache_dir,
113 hashlib.sha1(url.encode("utf-8")).hexdigest())
114 if os.path.exists(fname) and check_sha256sum(fname) and check_sha512sum(fname):
115 return fname
116 logging.debug("Downloading %s to %s...", url, fname)
117 subprocess.check_call(["wget", "-c", url, "-O", fname + ".download"],
118 stdout=self._stdout, stderr=self._stderr)
119 os.rename(fname + ".download", fname)
120 return fname
122 def _ssh_do(self, user, cmd, check):
123 ssh_cmd = ["ssh", "-q", "-t",
124 "-o", "StrictHostKeyChecking=no",
125 "-o", "UserKnownHostsFile=" + os.devnull,
126 "-o", "ConnectTimeout=1",
127 "-p", self.ssh_port, "-i", self._ssh_key_file]
128 for var in self.envvars:
129 ssh_cmd += ['-o', "SendEnv=%s" % var ]
130 assert not isinstance(cmd, str)
131 ssh_cmd += ["%s@127.0.0.1" % user] + list(cmd)
132 logging.debug("ssh_cmd: %s", " ".join(ssh_cmd))
133 r = subprocess.call(ssh_cmd)
134 if check and r != 0:
135 raise Exception("SSH command failed: %s" % cmd)
136 return r
138 def ssh(self, *cmd):
139 return self._ssh_do(self.GUEST_USER, cmd, False)
141 def ssh_root(self, *cmd):
142 return self._ssh_do("root", cmd, False)
144 def ssh_check(self, *cmd):
145 self._ssh_do(self.GUEST_USER, cmd, True)
147 def ssh_root_check(self, *cmd):
148 self._ssh_do("root", cmd, True)
150 def build_image(self, img):
151 raise NotImplementedError
153 def exec_qemu_img(self, *args):
154 cmd = [os.environ.get("QEMU_IMG", "qemu-img")]
155 cmd.extend(list(args))
156 subprocess.check_call(cmd)
158 def add_source_dir(self, src_dir):
159 name = "data-" + hashlib.sha1(src_dir.encode("utf-8")).hexdigest()[:5]
160 tarfile = os.path.join(self._tmpdir, name + ".tar")
161 logging.debug("Creating archive %s for src_dir dir: %s", tarfile, src_dir)
162 subprocess.check_call(["./scripts/archive-source.sh", tarfile],
163 cwd=src_dir, stdin=self._devnull,
164 stdout=self._stdout, stderr=self._stderr)
165 self._data_args += ["-drive",
166 "file=%s,if=none,id=%s,cache=writeback,format=raw" % \
167 (tarfile, name),
168 "-device",
169 "virtio-blk,drive=%s,serial=%s,bootindex=1" % (name, name)]
171 def boot(self, img, extra_args=[]):
172 args = self._args + [
173 "-device", "VGA",
174 "-drive", "file=%s,if=none,id=drive0,cache=writeback" % img,
175 "-device", "virtio-blk,drive=drive0,bootindex=0"]
176 args += self._data_args + extra_args
177 logging.debug("QEMU args: %s", " ".join(args))
178 qemu_bin = os.environ.get("QEMU", "qemu-system-" + self.arch)
179 guest = QEMUMachine(binary=qemu_bin, args=args)
180 guest.set_machine('pc')
181 guest.set_console()
182 try:
183 guest.launch()
184 except:
185 logging.error("Failed to launch QEMU, command line:")
186 logging.error(" ".join([qemu_bin] + args))
187 logging.error("Log:")
188 logging.error(guest.get_log())
189 logging.error("QEMU version >= 2.10 is required")
190 raise
191 atexit.register(self.shutdown)
192 self._guest = guest
193 usernet_info = guest.qmp("human-monitor-command",
194 command_line="info usernet")
195 self.ssh_port = None
196 for l in usernet_info["return"].splitlines():
197 fields = l.split()
198 if "TCP[HOST_FORWARD]" in fields and "22" in fields:
199 self.ssh_port = l.split()[3]
200 if not self.ssh_port:
201 raise Exception("Cannot find ssh port from 'info usernet':\n%s" % \
202 usernet_info)
204 def console_init(self, timeout = 120):
205 vm = self._guest
206 vm.console_socket.settimeout(timeout)
208 def console_log(self, text):
209 for line in re.split("[\r\n]", text):
210 # filter out terminal escape sequences
211 line = re.sub("\x1b\[[0-9;?]*[a-zA-Z]", "", line)
212 line = re.sub("\x1b\([0-9;?]*[a-zA-Z]", "", line)
213 # replace unprintable chars
214 line = re.sub("\x1b", "<esc>", line)
215 line = re.sub("[\x00-\x1f]", ".", line)
216 line = re.sub("[\x80-\xff]", ".", line)
217 if line == "":
218 continue
219 # log console line
220 sys.stderr.write("con recv: %s\n" % line)
222 def console_wait(self, expect, expectalt = None):
223 vm = self._guest
224 output = ""
225 while True:
226 try:
227 chars = vm.console_socket.recv(1)
228 except socket.timeout:
229 sys.stderr.write("console: *** read timeout ***\n")
230 sys.stderr.write("console: waiting for: '%s'\n" % expect)
231 if not expectalt is None:
232 sys.stderr.write("console: waiting for: '%s' (alt)\n" % expectalt)
233 sys.stderr.write("console: line buffer:\n")
234 sys.stderr.write("\n")
235 self.console_log(output.rstrip())
236 sys.stderr.write("\n")
237 raise
238 output += chars.decode("latin1")
239 if expect in output:
240 break
241 if not expectalt is None and expectalt in output:
242 break
243 if "\r" in output or "\n" in output:
244 lines = re.split("[\r\n]", output)
245 output = lines.pop()
246 if self.debug:
247 self.console_log("\n".join(lines))
248 if self.debug:
249 self.console_log(output)
250 if not expectalt is None and expectalt in output:
251 return False
252 return True
254 def console_consume(self):
255 vm = self._guest
256 output = ""
257 vm.console_socket.setblocking(0)
258 while True:
259 try:
260 chars = vm.console_socket.recv(1)
261 except:
262 break
263 output += chars.decode("latin1")
264 if "\r" in output or "\n" in output:
265 lines = re.split("[\r\n]", output)
266 output = lines.pop()
267 if self.debug:
268 self.console_log("\n".join(lines))
269 if self.debug:
270 self.console_log(output)
271 vm.console_socket.setblocking(1)
273 def console_send(self, command):
274 vm = self._guest
275 if self.debug:
276 logline = re.sub("\n", "<enter>", command)
277 logline = re.sub("[\x00-\x1f]", ".", logline)
278 sys.stderr.write("con send: %s\n" % logline)
279 for char in list(command):
280 vm.console_socket.send(char.encode("utf-8"))
281 time.sleep(0.01)
283 def console_wait_send(self, wait, command):
284 self.console_wait(wait)
285 self.console_send(command)
287 def console_ssh_init(self, prompt, user, pw):
288 sshkey_cmd = "echo '%s' > .ssh/authorized_keys\n" % SSH_PUB_KEY.rstrip()
289 self.console_wait_send("login:", "%s\n" % user)
290 self.console_wait_send("Password:", "%s\n" % pw)
291 self.console_wait_send(prompt, "mkdir .ssh\n")
292 self.console_wait_send(prompt, sshkey_cmd)
293 self.console_wait_send(prompt, "chmod 755 .ssh\n")
294 self.console_wait_send(prompt, "chmod 644 .ssh/authorized_keys\n")
296 def console_sshd_config(self, prompt):
297 self.console_wait(prompt)
298 self.console_send("echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config\n")
299 for var in self.envvars:
300 self.console_wait(prompt)
301 self.console_send("echo 'AcceptEnv %s' >> /etc/ssh/sshd_config\n" % var)
303 def print_step(self, text):
304 sys.stderr.write("### %s ...\n" % text)
306 def wait_ssh(self, seconds=300):
307 starttime = datetime.datetime.now()
308 endtime = starttime + datetime.timedelta(seconds=seconds)
309 guest_up = False
310 while datetime.datetime.now() < endtime:
311 if self.ssh("exit 0") == 0:
312 guest_up = True
313 break
314 seconds = (endtime - datetime.datetime.now()).total_seconds()
315 logging.debug("%ds before timeout", seconds)
316 time.sleep(1)
317 if not guest_up:
318 raise Exception("Timeout while waiting for guest ssh")
320 def shutdown(self):
321 self._guest.shutdown()
323 def wait(self):
324 self._guest.wait()
326 def graceful_shutdown(self):
327 self.ssh_root(self.poweroff)
328 self._guest.wait()
330 def qmp(self, *args, **kwargs):
331 return self._guest.qmp(*args, **kwargs)
333 def parse_args(vmcls):
335 def get_default_jobs():
336 if kvm_available(vmcls.arch):
337 return multiprocessing.cpu_count() // 2
338 else:
339 return 1
341 parser = optparse.OptionParser(
342 description="VM test utility. Exit codes: "
343 "0 = success, "
344 "1 = command line error, "
345 "2 = environment initialization failed, "
346 "3 = test command failed")
347 parser.add_option("--debug", "-D", action="store_true",
348 help="enable debug output")
349 parser.add_option("--image", "-i", default="%s.img" % vmcls.name,
350 help="image file name")
351 parser.add_option("--force", "-f", action="store_true",
352 help="force build image even if image exists")
353 parser.add_option("--jobs", type=int, default=get_default_jobs(),
354 help="number of virtual CPUs")
355 parser.add_option("--verbose", "-V", action="store_true",
356 help="Pass V=1 to builds within the guest")
357 parser.add_option("--build-image", "-b", action="store_true",
358 help="build image")
359 parser.add_option("--build-qemu",
360 help="build QEMU from source in guest")
361 parser.add_option("--build-target",
362 help="QEMU build target", default="check")
363 parser.add_option("--interactive", "-I", action="store_true",
364 help="Interactively run command")
365 parser.add_option("--snapshot", "-s", action="store_true",
366 help="run tests with a snapshot")
367 parser.disable_interspersed_args()
368 return parser.parse_args()
370 def main(vmcls):
371 try:
372 args, argv = parse_args(vmcls)
373 if not argv and not args.build_qemu and not args.build_image:
374 print("Nothing to do?")
375 return 1
376 logging.basicConfig(level=(logging.DEBUG if args.debug
377 else logging.WARN))
378 vm = vmcls(debug=args.debug, vcpus=args.jobs)
379 if args.build_image:
380 if os.path.exists(args.image) and not args.force:
381 sys.stderr.writelines(["Image file exists: %s\n" % args.image,
382 "Use --force option to overwrite\n"])
383 return 1
384 return vm.build_image(args.image)
385 if args.build_qemu:
386 vm.add_source_dir(args.build_qemu)
387 cmd = [vm.BUILD_SCRIPT.format(
388 configure_opts = " ".join(argv),
389 jobs=int(args.jobs),
390 target=args.build_target,
391 verbose = "V=1" if args.verbose else "")]
392 else:
393 cmd = argv
394 img = args.image
395 if args.snapshot:
396 img += ",snapshot=on"
397 vm.boot(img)
398 vm.wait_ssh()
399 except Exception as e:
400 if isinstance(e, SystemExit) and e.code == 0:
401 return 0
402 sys.stderr.write("Failed to prepare guest environment\n")
403 traceback.print_exc()
404 return 2
406 exitcode = 0
407 if vm.ssh(*cmd) != 0:
408 exitcode = 3
409 if args.interactive:
410 vm.ssh()
412 if not args.snapshot:
413 vm.graceful_shutdown()
415 return exitcode