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