]> Git Repo - qemu.git/blob - tests/vm/basevm.py
Merge remote-tracking branch 'remotes/ehabkost/tags/python-next-pull-request' into...
[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 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", "2G",
68             "-cpu", "host",
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 += ["%[email protected]" % 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=120):
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("--build-image", "-b", action="store_true",
214                       help="build image")
215     parser.add_option("--build-qemu",
216                       help="build QEMU from source in guest")
217     parser.add_option("--interactive", "-I", action="store_true",
218                       help="Interactively run command")
219     parser.disable_interspersed_args()
220     return parser.parse_args()
221
222 def main(vmcls):
223     try:
224         args, argv = parse_args(vmcls.name)
225         if not argv and not args.build_qemu and not args.build_image:
226             print("Nothing to do?")
227             return 1
228         logging.basicConfig(level=(logging.DEBUG if args.debug
229                                    else logging.WARN))
230         vm = vmcls(debug=args.debug, vcpus=args.jobs)
231         if args.build_image:
232             if os.path.exists(args.image) and not args.force:
233                 sys.stderr.writelines(["Image file exists: %s\n" % args.image,
234                                       "Use --force option to overwrite\n"])
235                 return 1
236             return vm.build_image(args.image)
237         if args.build_qemu:
238             vm.add_source_dir(args.build_qemu)
239             cmd = [vm.BUILD_SCRIPT.format(
240                    configure_opts = " ".join(argv),
241                    jobs=args.jobs)]
242         else:
243             cmd = argv
244         vm.boot(args.image + ",snapshot=on")
245         vm.wait_ssh()
246     except Exception as e:
247         if isinstance(e, SystemExit) and e.code == 0:
248             return 0
249         sys.stderr.write("Failed to prepare guest environment\n")
250         traceback.print_exc()
251         return 2
252
253     if args.interactive:
254         if vm.ssh_interactive(*cmd) == 0:
255             return 0
256         vm.ssh_interactive()
257         return 3
258     else:
259         if vm.ssh(*cmd) != 0:
260             return 3
This page took 0.038626 seconds and 4 git commands to generate.