]> git.proxmox.com Git - mirror_qemu.git/blob - tests/vm/basevm.py
Merge remote-tracking branch 'remotes/famz/tags/build-and-test-automation-pull-reques...
[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 stdin=sys.stdin if interactive else self._devnull,
112 stdout=sys.stdout if interactive else self._stdout,
113 stderr=sys.stderr if interactive else self._stderr)
114 if check and r != 0:
115 raise Exception("SSH command failed: %s" % cmd)
116 return r
117
118 def ssh(self, *cmd):
119 return self._ssh_do(self.GUEST_USER, cmd, False)
120
121 def ssh_interactive(self, *cmd):
122 return self._ssh_do(self.GUEST_USER, cmd, False, True)
123
124 def ssh_root(self, *cmd):
125 return self._ssh_do("root", cmd, False)
126
127 def ssh_check(self, *cmd):
128 self._ssh_do(self.GUEST_USER, cmd, True)
129
130 def ssh_root_check(self, *cmd):
131 self._ssh_do("root", cmd, True)
132
133 def build_image(self, img):
134 raise NotImplementedError
135
136 def add_source_dir(self, src_dir):
137 name = "data-" + hashlib.sha1(src_dir).hexdigest()[:5]
138 tarfile = os.path.join(self._tmpdir, name + ".tar")
139 logging.debug("Creating archive %s for src_dir dir: %s", tarfile, src_dir)
140 subprocess.check_call(["./scripts/archive-source.sh", tarfile],
141 cwd=src_dir, stdin=self._devnull,
142 stdout=self._stdout, stderr=self._stderr)
143 self._data_args += ["-drive",
144 "file=%s,if=none,id=%s,cache=writeback,format=raw" % \
145 (tarfile, name),
146 "-device",
147 "virtio-blk,drive=%s,serial=%s,bootindex=1" % (name, name)]
148
149 def boot(self, img, extra_args=[]):
150 args = self._args + [
151 "-device", "VGA",
152 "-drive", "file=%s,if=none,id=drive0,cache=writeback" % img,
153 "-device", "virtio-blk,drive=drive0,bootindex=0"]
154 args += self._data_args + extra_args
155 logging.debug("QEMU args: %s", " ".join(args))
156 qemu_bin = os.environ.get("QEMU", "qemu-system-x86_64")
157 guest = QEMUMachine(binary=qemu_bin, args=args)
158 try:
159 guest.launch()
160 except:
161 logging.error("Failed to launch QEMU, command line:")
162 logging.error(" ".join([qemu_bin] + args))
163 logging.error("Log:")
164 logging.error(guest.get_log())
165 logging.error("QEMU version >= 2.10 is required")
166 raise
167 atexit.register(self.shutdown)
168 self._guest = guest
169 usernet_info = guest.qmp("human-monitor-command",
170 command_line="info usernet")
171 self.ssh_port = None
172 for l in usernet_info["return"].splitlines():
173 fields = l.split()
174 if "TCP[HOST_FORWARD]" in fields and "22" in fields:
175 self.ssh_port = l.split()[3]
176 if not self.ssh_port:
177 raise Exception("Cannot find ssh port from 'info usernet':\n%s" % \
178 usernet_info)
179
180 def wait_ssh(self, seconds=120):
181 starttime = datetime.datetime.now()
182 guest_up = False
183 while (datetime.datetime.now() - starttime).total_seconds() < seconds:
184 if self.ssh("exit 0") == 0:
185 guest_up = True
186 break
187 time.sleep(1)
188 if not guest_up:
189 raise Exception("Timeout while waiting for guest ssh")
190
191 def shutdown(self):
192 self._guest.shutdown()
193
194 def wait(self):
195 self._guest.wait()
196
197 def qmp(self, *args, **kwargs):
198 return self._guest.qmp(*args, **kwargs)
199
200 def parse_args(vm_name):
201 parser = optparse.OptionParser(
202 description="VM test utility. Exit codes: "
203 "0 = success, "
204 "1 = command line error, "
205 "2 = environment initialization failed, "
206 "3 = test command failed")
207 parser.add_option("--debug", "-D", action="store_true",
208 help="enable debug output")
209 parser.add_option("--image", "-i", default="%s.img" % vm_name,
210 help="image file name")
211 parser.add_option("--force", "-f", action="store_true",
212 help="force build image even if image exists")
213 parser.add_option("--jobs", type=int, default=multiprocessing.cpu_count() / 2,
214 help="number of virtual CPUs")
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.disable_interspersed_args()
222 return parser.parse_args()
223
224 def main(vmcls):
225 try:
226 args, argv = parse_args(vmcls.name)
227 if not argv and not args.build_qemu and not args.build_image:
228 print "Nothing to do?"
229 return 1
230 if args.debug:
231 logging.getLogger().setLevel(logging.DEBUG)
232 vm = vmcls(debug=args.debug, vcpus=args.jobs)
233 if args.build_image:
234 if os.path.exists(args.image) and not args.force:
235 sys.stderr.writelines(["Image file exists: %s\n" % args.image,
236 "Use --force option to overwrite\n"])
237 return 1
238 return vm.build_image(args.image)
239 if args.build_qemu:
240 vm.add_source_dir(args.build_qemu)
241 cmd = [vm.BUILD_SCRIPT.format(
242 configure_opts = " ".join(argv),
243 jobs=args.jobs)]
244 else:
245 cmd = argv
246 vm.boot(args.image + ",snapshot=on")
247 vm.wait_ssh()
248 except Exception as e:
249 if isinstance(e, SystemExit) and e.code == 0:
250 return 0
251 sys.stderr.write("Failed to prepare guest environment\n")
252 traceback.print_exc()
253 return 2
254
255 if args.interactive:
256 if vm.ssh_interactive(*cmd) == 0:
257 return 0
258 vm.ssh_interactive()
259 return 3
260 else:
261 if vm.ssh(*cmd) != 0:
262 return 3