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