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