blob: 740f811052a03d46bd1678a46097b8207f3b7a9d [file] [log] [blame]
mblighdcd57a82007-07-11 23:06:47 +00001#!/usr/bin/python
2#
3# Copyright 2007 Google Inc. Released under the GPL v2
4
mblighdc735a22007-08-02 16:54:37 +00005"""
6This module defines the KVM class
mblighdcd57a82007-07-11 23:06:47 +00007
mbligh890fc5b2007-08-02 18:03:36 +00008 KVM: a KVM virtual machine monitor
mblighdcd57a82007-07-11 23:06:47 +00009"""
10
mblighdc735a22007-08-02 16:54:37 +000011__author__ = """
12mbligh@google.com (Martin J. Bligh),
mblighdcd57a82007-07-11 23:06:47 +000013poirier@google.com (Benjamin Poirier),
mblighdc735a22007-08-02 16:54:37 +000014stutsman@google.com (Ryan Stutsman)
15"""
mblighdcd57a82007-07-11 23:06:47 +000016
17import os
18
19import hypervisor
20import errors
21import utils
22import hosts
23
24
mblighb24d12d2007-08-10 19:30:18 +000025_qemu_ifup_script= """\
26#!/bin/sh
27# $1 is the name of the new qemu tap interface
28
29ifconfig $1 0.0.0.0 promisc up
30brctl addif br0 $1
31"""
32
33_check_process_script= """\
34if [ -f "%(pid_file_name)s" ]
35then
36 pid=$(cat "%(pid_file_name)s")
37 if [ -L /proc/$pid/exe ] && stat /proc/$pid/exe |
38 grep -q -- "-> \`%(qemu_binary)s\'\$"
39 then
40 echo "process present"
41 else
42 rm -f "%(pid_file_name)s"
43 rm -f "%(monitor_file_name)s"
44 fi
45fi
46"""
47
48_hard_reset_script= """\
49import socket
50
51monitor_socket= socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
52monitor_socket.connect("%(monitor_file_name)s")
53monitor_socket.send("system_reset\\n")\n')
54"""
55
56_remove_modules_script= """\
57if $(grep -q "^kvm_intel [[:digit:]]\+ 0" /proc/modules)
58then
59 rmmod kvm-intel
60fi
61
62if $(grep -q "^kvm_amd [[:digit:]]\+ 0" /proc/modules)
63then
64 rmmod kvm-amd
65fi
66
67if $(grep -q "^kvm [[:digit:]]\+ 0" /proc/modules)
68then
69 rmmod kvm
70fi
71"""
72
73
mblighdcd57a82007-07-11 23:06:47 +000074class KVM(hypervisor.Hypervisor):
mblighdc735a22007-08-02 16:54:37 +000075 """
mbligh890fc5b2007-08-02 18:03:36 +000076 This class represents a KVM virtual machine monitor.
77
78 Implementation details:
79 This is a leaf class in an abstract class hierarchy, it must
80 implement the unimplemented methods in parent classes.
mblighdc735a22007-08-02 16:54:37 +000081 """
mbligh890fc5b2007-08-02 18:03:36 +000082
83 build_dir= None
84 pid_dir= None
85 support_dir= None
86 addresses= []
87 insert_modules= True
mblighdc735a22007-08-02 16:54:37 +000088
89
mbligh890fc5b2007-08-02 18:03:36 +000090 def __del__(self):
mblighb24d12d2007-08-10 19:30:18 +000091 """
92 Destroy a KVM object.
93
mbligh890fc5b2007-08-02 18:03:36 +000094 Guests managed by this hypervisor that are still running will
95 be killed.
96 """
97 self.deinitialize()
98
99
100 def _insert_modules(self):
mblighb24d12d2007-08-10 19:30:18 +0000101 """
102 Insert the kvm modules into the kernel.
103
mbligh890fc5b2007-08-02 18:03:36 +0000104 The modules inserted are the ones from the build directory, NOT
105 the ones from the kernel.
mblighb24d12d2007-08-10 19:30:18 +0000106
mbligh890fc5b2007-08-02 18:03:36 +0000107 This function should only be called after install(). It will
108 check that the modules are not already loaded before attempting
109 to insert them.
110 """
111 cpu_flags= self.host.run('cat /proc/cpuinfo | '
112 'grep -e "^flags" | head -1 | cut -d " " -f 2-'
113 ).stdout.strip()
mblighb24d12d2007-08-10 19:30:18 +0000114
mbligh890fc5b2007-08-02 18:03:36 +0000115 if cpu_flags.find('vme') != -1:
116 module_type= "intel"
117 elif cpu_flags.find('svm') != -1:
118 module_type= "amd"
119 else:
120 raise errors.AutoservVirtError("No harware "
121 "virtualization extensions found, "
122 "KVM cannot run")
mblighb24d12d2007-08-10 19:30:18 +0000123
mbligh890fc5b2007-08-02 18:03:36 +0000124 self.host.run('if ! $(grep -q "^kvm " /proc/modules); '
125 'then insmod "%s"; fi' % (utils.sh_escape(
126 os.path.join(self.build_dir, "kernel/kvm.ko")),))
127 if module_type == "intel":
128 self.host.run('if ! $(grep -q "^kvm_intel " '
129 '/proc/modules); then insmod "%s"; fi' %
130 (utils.sh_escape(os.path.join(self.build_dir,
131 "kernel/kvm-intel.ko")),))
132 elif module_type == "amd":
133 self.host.run('if ! $(grep -q "^kvm_amd " '
134 '/proc/modules); then insmod "%s"; fi' %
135 (utils.sh_escape(os.path.join(self.build_dir,
136 "kernel/kvm-amd.ko")),))
mblighdc735a22007-08-02 16:54:37 +0000137
138
mbligh890fc5b2007-08-02 18:03:36 +0000139 def _remove_modules(self):
mblighb24d12d2007-08-10 19:30:18 +0000140 """
141 Remove the kvm modules from the kernel.
142
mbligh890fc5b2007-08-02 18:03:36 +0000143 This function checks that they're not in use before trying to
144 remove them.
145 """
mblighb24d12d2007-08-10 19:30:18 +0000146 self.host.run(_remove_modules_script)
mbligh890fc5b2007-08-02 18:03:36 +0000147
148
149 def install(self, addresses, build=True, insert_modules=True):
mblighb24d12d2007-08-10 19:30:18 +0000150 """
151 Compile the kvm software on the host that the object was
mbligh890fc5b2007-08-02 18:03:36 +0000152 initialized with.
mblighb24d12d2007-08-10 19:30:18 +0000153
mbligh890fc5b2007-08-02 18:03:36 +0000154 The kvm kernel modules are compiled, for this, the kernel
155 sources must be available. A custom qemu is also compiled.
156 Note that 'make install' is not run, the kernel modules and
157 qemu are run from where they were built, therefore not
158 conflicting with what might already be installed.
mblighb24d12d2007-08-10 19:30:18 +0000159
mbligh890fc5b2007-08-02 18:03:36 +0000160 Args:
161 addresses: a list of dict entries of the form
162 {"mac" : "xx:xx:xx:xx:xx:xx",
163 "ip" : "yyy.yyy.yyy.yyy"} where x and y
164 are replaced with sensible values. The ip
165 address may be a hostname or an IPv6 instead.
mblighb24d12d2007-08-10 19:30:18 +0000166
mbligh890fc5b2007-08-02 18:03:36 +0000167 When a new virtual machine is created, the
168 first available entry in that list will be
169 used. The network card in the virtual machine
170 will be assigned the specified mac address and
171 autoserv will use the specified ip address to
172 connect to the virtual host via ssh. The virtual
173 machine os must therefore be configured to
174 configure its network with the ip corresponding
175 to the mac.
176 build: build kvm from the source material, if False,
177 it is assumed that the package contains the
178 source tree after a 'make'.
179 insert_modules: build kvm modules from the source
180 material and insert them. Otherwise, the
181 running kernel is assumed to already have
182 kvm support and nothing will be done concerning
183 the modules.
mblighb24d12d2007-08-10 19:30:18 +0000184
mbligh890fc5b2007-08-02 18:03:36 +0000185 TODO(poirier): check dependencies before building
186 kvm needs:
187 libasound2-dev
188 libsdl1.2-dev (or configure qemu with --disable-gfx-check, how?)
mblighb24d12d2007-08-10 19:30:18 +0000189 bridge-utils
mbligh890fc5b2007-08-02 18:03:36 +0000190 """
191 self.addresses= [
192 {"mac" : address["mac"],
193 "ip" : address["ip"],
194 "is_used" : False} for address in addresses]
mblighb24d12d2007-08-10 19:30:18 +0000195
mbligh890fc5b2007-08-02 18:03:36 +0000196 self.build_dir = self.host.get_tmp_dir()
197 self.support_dir= self.host.get_tmp_dir()
mblighb24d12d2007-08-10 19:30:18 +0000198
mbligh890fc5b2007-08-02 18:03:36 +0000199 self.host.run('echo "%s" > "%s"' % (
mblighb24d12d2007-08-10 19:30:18 +0000200 utils.sh_escape(_qemu_ifup_script),
mbligh890fc5b2007-08-02 18:03:36 +0000201 utils.sh_escape(os.path.join(self.support_dir,
202 "qemu-ifup.sh")),))
203 self.host.run('chmod a+x "%s"' % (
204 utils.sh_escape(os.path.join(self.support_dir,
205 "qemu-ifup.sh")),))
mblighb24d12d2007-08-10 19:30:18 +0000206
mbligh890fc5b2007-08-02 18:03:36 +0000207 self.host.send_file(self.source_material, self.build_dir)
208 remote_source_material= os.path.join(self.build_dir,
209 os.path.basename(self.source_material))
mblighb24d12d2007-08-10 19:30:18 +0000210
mbligh890fc5b2007-08-02 18:03:36 +0000211 self.build_dir= utils.unarchive(self.host,
212 remote_source_material)
mblighb24d12d2007-08-10 19:30:18 +0000213
mbligh890fc5b2007-08-02 18:03:36 +0000214 if insert_modules:
215 configure_modules= ""
216 self.insert_modules= True
217 else:
218 configure_modules= "--with-patched-kernel "
219 self.insert_modules= False
mblighb24d12d2007-08-10 19:30:18 +0000220
mbligh890fc5b2007-08-02 18:03:36 +0000221 # build
222 if build:
223 try:
224 self.host.run('make -C "%s" clean' % (
225 utils.sh_escape(self.build_dir),))
226 except errors.AutoservRunError:
227 # directory was already clean and contained
228 # no makefile
229 pass
230 self.host.run('cd "%s" && ./configure %s' % (
231 utils.sh_escape(self.build_dir),
232 configure_modules,))
233 self.host.run('make -j%d -C "%s"' % (
234 self.host.get_num_cpu() * 2,
mblighdc735a22007-08-02 16:54:37 +0000235 utils.sh_escape(self.build_dir),))
mblighb24d12d2007-08-10 19:30:18 +0000236
mbligh890fc5b2007-08-02 18:03:36 +0000237 self.initialize()
mblighdc735a22007-08-02 16:54:37 +0000238
239
mbligh890fc5b2007-08-02 18:03:36 +0000240 def initialize(self):
mblighb24d12d2007-08-10 19:30:18 +0000241 """
242 Initialize the hypervisor.
243
mbligh890fc5b2007-08-02 18:03:36 +0000244 Loads needed kernel modules and creates temporary directories.
245 The logic is that you could compile once and
246 initialize - deinitialize many times. But why you would do that
247 has yet to be figured.
mblighb24d12d2007-08-10 19:30:18 +0000248
mbligh890fc5b2007-08-02 18:03:36 +0000249 Raises:
250 AutoservVirtError: cpuid doesn't report virtualization
251 extentions (vme for intel or svm for amd), in
252 this case, kvm cannot run.
253 """
254 self.pid_dir= self.host.get_tmp_dir()
mblighb24d12d2007-08-10 19:30:18 +0000255
mbligh890fc5b2007-08-02 18:03:36 +0000256 if self.insert_modules:
257 self._remove_modules()
258 self._insert_modules()
mblighdc735a22007-08-02 16:54:37 +0000259
260
mbligh890fc5b2007-08-02 18:03:36 +0000261 def deinitialize(self):
mblighb24d12d2007-08-10 19:30:18 +0000262 """
263 Terminate the hypervisor.
264
mbligh890fc5b2007-08-02 18:03:36 +0000265 Kill all the virtual machines that are still running and
266 unload the kernel modules.
267 """
268 self.refresh_guests()
269 for address in self.addresses:
mblighdc735a22007-08-02 16:54:37 +0000270 if address["is_used"]:
mbligh890fc5b2007-08-02 18:03:36 +0000271 self.delete_guest(address["ip"])
272 self.pid_dir= None
mblighb24d12d2007-08-10 19:30:18 +0000273
mbligh890fc5b2007-08-02 18:03:36 +0000274 if self.insert_modules:
275 self._remove_modules()
276
277
278 def new_guest(self, qemu_options):
mblighb24d12d2007-08-10 19:30:18 +0000279 """
280 Start a new guest ("virtual machine").
281
mbligh890fc5b2007-08-02 18:03:36 +0000282 Returns:
283 The ip that was picked from the list supplied to
284 install() and assigned to this guest.
mblighb24d12d2007-08-10 19:30:18 +0000285
mbligh890fc5b2007-08-02 18:03:36 +0000286 Raises:
287 AutoservVirtError: no more addresses are available.
288 """
289 for address in self.addresses:
290 if not address["is_used"]:
mblighdc735a22007-08-02 16:54:37 +0000291 break
mbligh890fc5b2007-08-02 18:03:36 +0000292 else:
293 raise errors.AutoservVirtError(
294 "No more addresses available")
mblighb24d12d2007-08-10 19:30:18 +0000295
mbligh890fc5b2007-08-02 18:03:36 +0000296 # TODO(poirier): uses start-stop-daemon until qemu -pidfile
mblighb24d12d2007-08-10 19:30:18 +0000297 # and -daemonize can work together, this 'hides' the return
298 # code of qemu (bad)
mbligh890fc5b2007-08-02 18:03:36 +0000299 retval= self.host.run(
300 'start-stop-daemon -S --exec "%s" --pidfile "%s" -b -- '
301 # this is the line of options that can be modified
302 ' %s '
303 '-pidfile "%s" -nographic '
304 #~ '-serial telnet::4444,server '
305 '-monitor unix:"%s",server,nowait '
mblighd7685d32007-08-10 22:08:42 +0000306 '-net nic,macaddr="%s" -net tap,script="%s" -L "%s"' % (
mbligh890fc5b2007-08-02 18:03:36 +0000307 utils.sh_escape(os.path.join(
308 self.build_dir,
309 "qemu/x86_64-softmmu/qemu-system-x86_64")),
310 utils.sh_escape(os.path.join(
311 self.pid_dir,
312 "vhost%s_pid" % (address["ip"],))),
313 qemu_options,
314 utils.sh_escape(os.path.join(
315 self.pid_dir,
316 "vhost%s_pid" % (address["ip"],))),
317 utils.sh_escape(os.path.join(
318 self.pid_dir,
319 "vhost%s_monitor" % (address["ip"],))),
320 utils.sh_escape(address["mac"]),
321 utils.sh_escape(os.path.join(
322 self.support_dir,
mblighd7685d32007-08-10 22:08:42 +0000323 "qemu-ifup.sh")),
324 utils.sh_escape(os.path.join(
325 self.build_dir,
326 "qemu/pc-bios")),))
mblighb24d12d2007-08-10 19:30:18 +0000327
mbligh890fc5b2007-08-02 18:03:36 +0000328 address["is_used"]= True
329 return address["ip"]
mblighdc735a22007-08-02 16:54:37 +0000330
331
mbligh890fc5b2007-08-02 18:03:36 +0000332 def refresh_guests(self):
mblighb24d12d2007-08-10 19:30:18 +0000333 """
334 Refresh the list of guests addresses.
335
mbligh890fc5b2007-08-02 18:03:36 +0000336 The is_used status will be updated according to the presence
337 of the process specified in the pid file that was written when
338 the virtual machine was started.
mblighb24d12d2007-08-10 19:30:18 +0000339
mbligh890fc5b2007-08-02 18:03:36 +0000340 TODO(poirier): there are a lot of race conditions in this code
341 because the process might terminate on its own anywhere in
342 between
343 """
344 for address in self.addresses:
mblighdc735a22007-08-02 16:54:37 +0000345 if address["is_used"]:
mbligh890fc5b2007-08-02 18:03:36 +0000346 pid_file_name= utils.sh_escape(os.path.join(
347 self.pid_dir,
348 "vhost%s_pid" % (address["ip"],)))
349 monitor_file_name= utils.sh_escape(os.path.join(
350 self.pid_dir,
351 "vhost%s_monitor" % (address["ip"],)))
352 retval= self.host.run(
mblighb24d12d2007-08-10 19:30:18 +0000353 _check_process_script % {
mbligh890fc5b2007-08-02 18:03:36 +0000354 "pid_file_name" : pid_file_name,
355 "monitor_file_name" : monitor_file_name,
356 "qemu_binary" : utils.sh_escape(
357 os.path.join(self.build_dir,
358 "qemu/x86_64-softmmu/"
359 "qemu-system-x86_64")),})
360 if (retval.stdout.strip() !=
361 "process present"):
362 address["is_used"]= False
363
364
365 def delete_guest(self, guest_hostname):
mblighb24d12d2007-08-10 19:30:18 +0000366 """
367 Terminate a virtual machine.
368
mbligh890fc5b2007-08-02 18:03:36 +0000369 Args:
370 guest_hostname: the ip (as it was specified in the
371 address list given to install()) of the guest
372 to terminate.
mblighb24d12d2007-08-10 19:30:18 +0000373
mbligh890fc5b2007-08-02 18:03:36 +0000374 Raises:
375 AutoservVirtError: the guest_hostname argument is
376 invalid
377
378 TODO(poirier): is there a difference in qemu between
379 sending SIGTEM or quitting from the monitor?
380 TODO(poirier): there are a lot of race conditions in this code
381 because the process might terminate on its own anywhere in
382 between
383 """
384 for address in self.addresses:
385 if address["ip"] == guest_hostname:
386 if address["is_used"]:
387 break
388 else:
389 # Will happen if deinitialize() is
390 # called while guest objects still
391 # exit and these are del'ed after.
392 # In that situation, nothing is to
393 # be done here, don't throw an error
394 # either because it will print an
395 # ugly message during garbage
396 # collection. The solution would be to
397 # delete the guest objects before
398 # calling deinitialize(), this can't be
399 # done by the KVM class, it has no
400 # reference to those objects and it
401 # cannot have any either. The Guest
402 # objects already need to have a
403 # reference to their managing
404 # hypervisor. If the hypervisor had a
405 # reference to the Guest objects it
406 # manages, it would create a circular
407 # reference and those objects would
408 # not be elligible for garbage
409 # collection. In turn, this means that
410 # the KVM object would not be
411 # automatically del'ed at the end of
412 # the program and guests that are still
413 # running would be left unattended.
414 # Note that this circular reference
415 # problem could be avoided by using
416 # weakref's in class KVM but the
417 # control file will most likely also
418 # have references to the guests.
419 return
420 else:
421 raise errors.AutoservVirtError("Unknown guest hostname")
mblighb24d12d2007-08-10 19:30:18 +0000422
mbligh890fc5b2007-08-02 18:03:36 +0000423 pid_file_name= utils.sh_escape(os.path.join(self.pid_dir,
424 "vhost%s_pid" % (address["ip"],)))
425 monitor_file_name= utils.sh_escape(os.path.join(self.pid_dir,
426 "vhost%s_monitor" % (address["ip"],)))
mblighb24d12d2007-08-10 19:30:18 +0000427
mbligh890fc5b2007-08-02 18:03:36 +0000428 retval= self.host.run(
mblighb24d12d2007-08-10 19:30:18 +0000429 _check_process_script % {
mbligh890fc5b2007-08-02 18:03:36 +0000430 "pid_file_name" : pid_file_name,
431 "monitor_file_name" : monitor_file_name,
432 "qemu_binary" : utils.sh_escape(os.path.join(
433 self.build_dir,
434 "qemu/x86_64-softmmu/qemu-system-x86_64")),})
435 if retval.stdout.strip() == "process present":
436 self.host.run('kill $(cat "%s")' %(
437 pid_file_name,))
438 self.host.run('rm -f "%s"' %(
439 pid_file_name,))
440 self.host.run('rm -f "%s"' %(
441 monitor_file_name,))
442 address["is_used"]= False
443
444
445 def reset_guest(self, guest_hostname):
mblighb24d12d2007-08-10 19:30:18 +0000446 """
447 Perform a hard reset on a virtual machine.
448
mbligh890fc5b2007-08-02 18:03:36 +0000449 Args:
450 guest_hostname: the ip (as it was specified in the
451 address list given to install()) of the guest
452 to terminate.
mblighb24d12d2007-08-10 19:30:18 +0000453
mbligh890fc5b2007-08-02 18:03:36 +0000454 Raises:
455 AutoservVirtError: the guest_hostname argument is
456 invalid
457 """
458 for address in self.addresses:
459 if address["ip"] is guest_hostname:
460 if address["is_used"]:
461 break
462 else:
463 raise errors.AutoservVirtError("guest "
464 "hostname not in use")
465 else:
466 raise errors.AutoservVirtError("Unknown guest hostname")
mblighb24d12d2007-08-10 19:30:18 +0000467
mbligh890fc5b2007-08-02 18:03:36 +0000468 monitor_file_name= utils.sh_escape(os.path.join(self.pid_dir,
469 "vhost%s_monitor" % (address["ip"],)))
mblighb24d12d2007-08-10 19:30:18 +0000470
mbligh890fc5b2007-08-02 18:03:36 +0000471 self.host.run('python -c "%s"' % (utils.sh_escape(
mblighb24d12d2007-08-10 19:30:18 +0000472 _hard_reset_script % {
mbligh890fc5b2007-08-02 18:03:36 +0000473 "monitor_file_name" : monitor_file_name,}),))