]> Git Repo - qemu.git/blob - tests/vm/basevm.py
Merge remote-tracking branch 'remotes/mst/tags/for_upstream' into staging
[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 <[email protected]>
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 += ["%[email protected]" % 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
This page took 0.037836 seconds and 4 git commands to generate.