blob: f997d16fa071ead4cf1561a9b8f735b4fac6c702 [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
mblighdcd57a82007-07-11 23:06:47 +000020import utils
21import hosts
22
mbligh03f4fc72007-11-29 20:56:14 +000023from common.error import *
24
mblighdcd57a82007-07-11 23:06:47 +000025
mblighb24d12d2007-08-10 19:30:18 +000026_qemu_ifup_script= """\
27#!/bin/sh
28# $1 is the name of the new qemu tap interface
29
30ifconfig $1 0.0.0.0 promisc up
31brctl addif br0 $1
32"""
33
34_check_process_script= """\
35if [ -f "%(pid_file_name)s" ]
36then
37 pid=$(cat "%(pid_file_name)s")
38 if [ -L /proc/$pid/exe ] && stat /proc/$pid/exe |
39 grep -q -- "-> \`%(qemu_binary)s\'\$"
40 then
41 echo "process present"
42 else
43 rm -f "%(pid_file_name)s"
44 rm -f "%(monitor_file_name)s"
45 fi
46fi
47"""
48
49_hard_reset_script= """\
50import socket
51
52monitor_socket= socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
53monitor_socket.connect("%(monitor_file_name)s")
54monitor_socket.send("system_reset\\n")\n')
55"""
56
57_remove_modules_script= """\
58if $(grep -q "^kvm_intel [[:digit:]]\+ 0" /proc/modules)
59then
60 rmmod kvm-intel
61fi
62
63if $(grep -q "^kvm_amd [[:digit:]]\+ 0" /proc/modules)
64then
65 rmmod kvm-amd
66fi
67
68if $(grep -q "^kvm [[:digit:]]\+ 0" /proc/modules)
69then
70 rmmod kvm
71fi
72"""
73
74
mblighdcd57a82007-07-11 23:06:47 +000075class KVM(hypervisor.Hypervisor):
mblighdc735a22007-08-02 16:54:37 +000076 """
mbligh890fc5b2007-08-02 18:03:36 +000077 This class represents a KVM virtual machine monitor.
78
79 Implementation details:
80 This is a leaf class in an abstract class hierarchy, it must
81 implement the unimplemented methods in parent classes.
mblighdc735a22007-08-02 16:54:37 +000082 """
mbligh890fc5b2007-08-02 18:03:36 +000083
84 build_dir= None
85 pid_dir= None
86 support_dir= None
87 addresses= []
88 insert_modules= True
mblighdc735a22007-08-02 16:54:37 +000089
90
mbligh890fc5b2007-08-02 18:03:36 +000091 def __del__(self):
mblighb24d12d2007-08-10 19:30:18 +000092 """
93 Destroy a KVM object.
94
mbligh890fc5b2007-08-02 18:03:36 +000095 Guests managed by this hypervisor that are still running will
96 be killed.
97 """
98 self.deinitialize()
99
100
101 def _insert_modules(self):
mblighb24d12d2007-08-10 19:30:18 +0000102 """
103 Insert the kvm modules into the kernel.
104
mbligh890fc5b2007-08-02 18:03:36 +0000105 The modules inserted are the ones from the build directory, NOT
106 the ones from the kernel.
mblighb24d12d2007-08-10 19:30:18 +0000107
mbligh890fc5b2007-08-02 18:03:36 +0000108 This function should only be called after install(). It will
109 check that the modules are not already loaded before attempting
110 to insert them.
111 """
112 cpu_flags= self.host.run('cat /proc/cpuinfo | '
113 'grep -e "^flags" | head -1 | cut -d " " -f 2-'
114 ).stdout.strip()
mblighb24d12d2007-08-10 19:30:18 +0000115
mbligh770e3d72007-11-02 01:33:52 +0000116 if cpu_flags.find('vmx') != -1:
mbligh890fc5b2007-08-02 18:03:36 +0000117 module_type= "intel"
118 elif cpu_flags.find('svm') != -1:
119 module_type= "amd"
120 else:
mbligh03f4fc72007-11-29 20:56:14 +0000121 raise AutoservVirtError("No harware "
mbligh890fc5b2007-08-02 18:03:36 +0000122 "virtualization extensions found, "
123 "KVM cannot run")
mblighb24d12d2007-08-10 19:30:18 +0000124
mbligh890fc5b2007-08-02 18:03:36 +0000125 self.host.run('if ! $(grep -q "^kvm " /proc/modules); '
126 'then insmod "%s"; fi' % (utils.sh_escape(
127 os.path.join(self.build_dir, "kernel/kvm.ko")),))
128 if module_type == "intel":
129 self.host.run('if ! $(grep -q "^kvm_intel " '
130 '/proc/modules); then insmod "%s"; fi' %
131 (utils.sh_escape(os.path.join(self.build_dir,
132 "kernel/kvm-intel.ko")),))
133 elif module_type == "amd":
134 self.host.run('if ! $(grep -q "^kvm_amd " '
135 '/proc/modules); then insmod "%s"; fi' %
136 (utils.sh_escape(os.path.join(self.build_dir,
137 "kernel/kvm-amd.ko")),))
mblighdc735a22007-08-02 16:54:37 +0000138
139
mbligh890fc5b2007-08-02 18:03:36 +0000140 def _remove_modules(self):
mblighb24d12d2007-08-10 19:30:18 +0000141 """
142 Remove the kvm modules from the kernel.
143
mbligh890fc5b2007-08-02 18:03:36 +0000144 This function checks that they're not in use before trying to
145 remove them.
146 """
mblighb24d12d2007-08-10 19:30:18 +0000147 self.host.run(_remove_modules_script)
mbligh890fc5b2007-08-02 18:03:36 +0000148
149
150 def install(self, addresses, build=True, insert_modules=True):
mblighb24d12d2007-08-10 19:30:18 +0000151 """
152 Compile the kvm software on the host that the object was
mbligh890fc5b2007-08-02 18:03:36 +0000153 initialized with.
mblighb24d12d2007-08-10 19:30:18 +0000154
mbligh890fc5b2007-08-02 18:03:36 +0000155 The kvm kernel modules are compiled, for this, the kernel
156 sources must be available. A custom qemu is also compiled.
157 Note that 'make install' is not run, the kernel modules and
158 qemu are run from where they were built, therefore not
159 conflicting with what might already be installed.
mblighb24d12d2007-08-10 19:30:18 +0000160
mbligh890fc5b2007-08-02 18:03:36 +0000161 Args:
162 addresses: a list of dict entries of the form
163 {"mac" : "xx:xx:xx:xx:xx:xx",
164 "ip" : "yyy.yyy.yyy.yyy"} where x and y
165 are replaced with sensible values. The ip
166 address may be a hostname or an IPv6 instead.
mblighb24d12d2007-08-10 19:30:18 +0000167
mbligh890fc5b2007-08-02 18:03:36 +0000168 When a new virtual machine is created, the
169 first available entry in that list will be
170 used. The network card in the virtual machine
171 will be assigned the specified mac address and
172 autoserv will use the specified ip address to
173 connect to the virtual host via ssh. The virtual
174 machine os must therefore be configured to
175 configure its network with the ip corresponding
176 to the mac.
177 build: build kvm from the source material, if False,
178 it is assumed that the package contains the
179 source tree after a 'make'.
180 insert_modules: build kvm modules from the source
181 material and insert them. Otherwise, the
182 running kernel is assumed to already have
183 kvm support and nothing will be done concerning
184 the modules.
mblighb24d12d2007-08-10 19:30:18 +0000185
mbligh890fc5b2007-08-02 18:03:36 +0000186 TODO(poirier): check dependencies before building
187 kvm needs:
188 libasound2-dev
189 libsdl1.2-dev (or configure qemu with --disable-gfx-check, how?)
mblighb24d12d2007-08-10 19:30:18 +0000190 bridge-utils
mbligh890fc5b2007-08-02 18:03:36 +0000191 """
192 self.addresses= [
193 {"mac" : address["mac"],
194 "ip" : address["ip"],
195 "is_used" : False} for address in addresses]
mblighb24d12d2007-08-10 19:30:18 +0000196
mbligh890fc5b2007-08-02 18:03:36 +0000197 self.build_dir = self.host.get_tmp_dir()
198 self.support_dir= self.host.get_tmp_dir()
mblighb24d12d2007-08-10 19:30:18 +0000199
mbligh890fc5b2007-08-02 18:03:36 +0000200 self.host.run('echo "%s" > "%s"' % (
mblighb24d12d2007-08-10 19:30:18 +0000201 utils.sh_escape(_qemu_ifup_script),
mbligh890fc5b2007-08-02 18:03:36 +0000202 utils.sh_escape(os.path.join(self.support_dir,
203 "qemu-ifup.sh")),))
204 self.host.run('chmod a+x "%s"' % (
205 utils.sh_escape(os.path.join(self.support_dir,
206 "qemu-ifup.sh")),))
mblighb24d12d2007-08-10 19:30:18 +0000207
mbligh890fc5b2007-08-02 18:03:36 +0000208 self.host.send_file(self.source_material, self.build_dir)
209 remote_source_material= os.path.join(self.build_dir,
210 os.path.basename(self.source_material))
mblighb24d12d2007-08-10 19:30:18 +0000211
mbligh890fc5b2007-08-02 18:03:36 +0000212 self.build_dir= utils.unarchive(self.host,
213 remote_source_material)
mblighb24d12d2007-08-10 19:30:18 +0000214
mbligh890fc5b2007-08-02 18:03:36 +0000215 if insert_modules:
216 configure_modules= ""
217 self.insert_modules= True
218 else:
219 configure_modules= "--with-patched-kernel "
220 self.insert_modules= False
mblighb24d12d2007-08-10 19:30:18 +0000221
mbligh890fc5b2007-08-02 18:03:36 +0000222 # build
223 if build:
224 try:
225 self.host.run('make -C "%s" clean' % (
226 utils.sh_escape(self.build_dir),))
mbligh03f4fc72007-11-29 20:56:14 +0000227 except AutoservRunError:
mbligh890fc5b2007-08-02 18:03:36 +0000228 # directory was already clean and contained
229 # no makefile
230 pass
231 self.host.run('cd "%s" && ./configure %s' % (
232 utils.sh_escape(self.build_dir),
233 configure_modules,))
234 self.host.run('make -j%d -C "%s"' % (
235 self.host.get_num_cpu() * 2,
mblighdc735a22007-08-02 16:54:37 +0000236 utils.sh_escape(self.build_dir),))
mblighb24d12d2007-08-10 19:30:18 +0000237
mbligh890fc5b2007-08-02 18:03:36 +0000238 self.initialize()
mblighdc735a22007-08-02 16:54:37 +0000239
240
mbligh890fc5b2007-08-02 18:03:36 +0000241 def initialize(self):
mblighb24d12d2007-08-10 19:30:18 +0000242 """
243 Initialize the hypervisor.
244
mbligh890fc5b2007-08-02 18:03:36 +0000245 Loads needed kernel modules and creates temporary directories.
246 The logic is that you could compile once and
247 initialize - deinitialize many times. But why you would do that
248 has yet to be figured.
mblighb24d12d2007-08-10 19:30:18 +0000249
mbligh890fc5b2007-08-02 18:03:36 +0000250 Raises:
251 AutoservVirtError: cpuid doesn't report virtualization
mbligh770e3d72007-11-02 01:33:52 +0000252 extentions (vmx for intel or svm for amd), in
mbligh890fc5b2007-08-02 18:03:36 +0000253 this case, kvm cannot run.
254 """
255 self.pid_dir= self.host.get_tmp_dir()
mblighb24d12d2007-08-10 19:30:18 +0000256
mbligh890fc5b2007-08-02 18:03:36 +0000257 if self.insert_modules:
258 self._remove_modules()
259 self._insert_modules()
mblighdc735a22007-08-02 16:54:37 +0000260
261
mbligh890fc5b2007-08-02 18:03:36 +0000262 def deinitialize(self):
mblighb24d12d2007-08-10 19:30:18 +0000263 """
264 Terminate the hypervisor.
265
mbligh890fc5b2007-08-02 18:03:36 +0000266 Kill all the virtual machines that are still running and
267 unload the kernel modules.
268 """
269 self.refresh_guests()
270 for address in self.addresses:
mblighdc735a22007-08-02 16:54:37 +0000271 if address["is_used"]:
mbligh890fc5b2007-08-02 18:03:36 +0000272 self.delete_guest(address["ip"])
273 self.pid_dir= None
mblighb24d12d2007-08-10 19:30:18 +0000274
mbligh890fc5b2007-08-02 18:03:36 +0000275 if self.insert_modules:
276 self._remove_modules()
277
278
279 def new_guest(self, qemu_options):
mblighb24d12d2007-08-10 19:30:18 +0000280 """
281 Start a new guest ("virtual machine").
282
mbligh890fc5b2007-08-02 18:03:36 +0000283 Returns:
284 The ip that was picked from the list supplied to
285 install() and assigned to this guest.
mblighb24d12d2007-08-10 19:30:18 +0000286
mbligh890fc5b2007-08-02 18:03:36 +0000287 Raises:
288 AutoservVirtError: no more addresses are available.
289 """
290 for address in self.addresses:
291 if not address["is_used"]:
mblighdc735a22007-08-02 16:54:37 +0000292 break
mbligh890fc5b2007-08-02 18:03:36 +0000293 else:
mbligh03f4fc72007-11-29 20:56:14 +0000294 raise AutoservVirtError(
mbligh890fc5b2007-08-02 18:03:36 +0000295 "No more addresses available")
mblighb24d12d2007-08-10 19:30:18 +0000296
mbligh890fc5b2007-08-02 18:03:36 +0000297 # TODO(poirier): uses start-stop-daemon until qemu -pidfile
mblighb24d12d2007-08-10 19:30:18 +0000298 # and -daemonize can work together, this 'hides' the return
299 # code of qemu (bad)
mbligh890fc5b2007-08-02 18:03:36 +0000300 retval= self.host.run(
301 'start-stop-daemon -S --exec "%s" --pidfile "%s" -b -- '
302 # this is the line of options that can be modified
303 ' %s '
304 '-pidfile "%s" -nographic '
305 #~ '-serial telnet::4444,server '
306 '-monitor unix:"%s",server,nowait '
mblighd7685d32007-08-10 22:08:42 +0000307 '-net nic,macaddr="%s" -net tap,script="%s" -L "%s"' % (
mbligh890fc5b2007-08-02 18:03:36 +0000308 utils.sh_escape(os.path.join(
309 self.build_dir,
310 "qemu/x86_64-softmmu/qemu-system-x86_64")),
311 utils.sh_escape(os.path.join(
312 self.pid_dir,
313 "vhost%s_pid" % (address["ip"],))),
314 qemu_options,
315 utils.sh_escape(os.path.join(
316 self.pid_dir,
317 "vhost%s_pid" % (address["ip"],))),
318 utils.sh_escape(os.path.join(
319 self.pid_dir,
320 "vhost%s_monitor" % (address["ip"],))),
321 utils.sh_escape(address["mac"]),
322 utils.sh_escape(os.path.join(
323 self.support_dir,
mblighd7685d32007-08-10 22:08:42 +0000324 "qemu-ifup.sh")),
325 utils.sh_escape(os.path.join(
326 self.build_dir,
327 "qemu/pc-bios")),))
mblighb24d12d2007-08-10 19:30:18 +0000328
mbligh890fc5b2007-08-02 18:03:36 +0000329 address["is_used"]= True
330 return address["ip"]
mblighdc735a22007-08-02 16:54:37 +0000331
332
mbligh890fc5b2007-08-02 18:03:36 +0000333 def refresh_guests(self):
mblighb24d12d2007-08-10 19:30:18 +0000334 """
335 Refresh the list of guests addresses.
336
mbligh890fc5b2007-08-02 18:03:36 +0000337 The is_used status will be updated according to the presence
338 of the process specified in the pid file that was written when
339 the virtual machine was started.
mblighb24d12d2007-08-10 19:30:18 +0000340
mbligh890fc5b2007-08-02 18:03:36 +0000341 TODO(poirier): there are a lot of race conditions in this code
342 because the process might terminate on its own anywhere in
343 between
344 """
345 for address in self.addresses:
mblighdc735a22007-08-02 16:54:37 +0000346 if address["is_used"]:
mbligh890fc5b2007-08-02 18:03:36 +0000347 pid_file_name= utils.sh_escape(os.path.join(
348 self.pid_dir,
349 "vhost%s_pid" % (address["ip"],)))
350 monitor_file_name= utils.sh_escape(os.path.join(
351 self.pid_dir,
352 "vhost%s_monitor" % (address["ip"],)))
353 retval= self.host.run(
mblighb24d12d2007-08-10 19:30:18 +0000354 _check_process_script % {
mbligh890fc5b2007-08-02 18:03:36 +0000355 "pid_file_name" : pid_file_name,
356 "monitor_file_name" : monitor_file_name,
357 "qemu_binary" : utils.sh_escape(
358 os.path.join(self.build_dir,
359 "qemu/x86_64-softmmu/"
360 "qemu-system-x86_64")),})
361 if (retval.stdout.strip() !=
362 "process present"):
363 address["is_used"]= False
364
365
366 def delete_guest(self, guest_hostname):
mblighb24d12d2007-08-10 19:30:18 +0000367 """
368 Terminate a virtual machine.
369
mbligh890fc5b2007-08-02 18:03:36 +0000370 Args:
371 guest_hostname: the ip (as it was specified in the
372 address list given to install()) of the guest
373 to terminate.
mblighb24d12d2007-08-10 19:30:18 +0000374
mbligh890fc5b2007-08-02 18:03:36 +0000375 Raises:
376 AutoservVirtError: the guest_hostname argument is
377 invalid
378
379 TODO(poirier): is there a difference in qemu between
380 sending SIGTEM or quitting from the monitor?
381 TODO(poirier): there are a lot of race conditions in this code
382 because the process might terminate on its own anywhere in
383 between
384 """
385 for address in self.addresses:
386 if address["ip"] == guest_hostname:
387 if address["is_used"]:
388 break
389 else:
390 # Will happen if deinitialize() is
391 # called while guest objects still
392 # exit and these are del'ed after.
393 # In that situation, nothing is to
394 # be done here, don't throw an error
395 # either because it will print an
396 # ugly message during garbage
397 # collection. The solution would be to
398 # delete the guest objects before
399 # calling deinitialize(), this can't be
400 # done by the KVM class, it has no
401 # reference to those objects and it
402 # cannot have any either. The Guest
403 # objects already need to have a
404 # reference to their managing
405 # hypervisor. If the hypervisor had a
406 # reference to the Guest objects it
407 # manages, it would create a circular
408 # reference and those objects would
409 # not be elligible for garbage
410 # collection. In turn, this means that
411 # the KVM object would not be
412 # automatically del'ed at the end of
413 # the program and guests that are still
414 # running would be left unattended.
415 # Note that this circular reference
416 # problem could be avoided by using
417 # weakref's in class KVM but the
418 # control file will most likely also
419 # have references to the guests.
420 return
421 else:
mbligh03f4fc72007-11-29 20:56:14 +0000422 raise AutoservVirtError("Unknown guest hostname")
mblighb24d12d2007-08-10 19:30:18 +0000423
mbligh890fc5b2007-08-02 18:03:36 +0000424 pid_file_name= utils.sh_escape(os.path.join(self.pid_dir,
425 "vhost%s_pid" % (address["ip"],)))
426 monitor_file_name= utils.sh_escape(os.path.join(self.pid_dir,
427 "vhost%s_monitor" % (address["ip"],)))
mblighb24d12d2007-08-10 19:30:18 +0000428
mbligh890fc5b2007-08-02 18:03:36 +0000429 retval= self.host.run(
mblighb24d12d2007-08-10 19:30:18 +0000430 _check_process_script % {
mbligh890fc5b2007-08-02 18:03:36 +0000431 "pid_file_name" : pid_file_name,
432 "monitor_file_name" : monitor_file_name,
433 "qemu_binary" : utils.sh_escape(os.path.join(
434 self.build_dir,
435 "qemu/x86_64-softmmu/qemu-system-x86_64")),})
436 if retval.stdout.strip() == "process present":
437 self.host.run('kill $(cat "%s")' %(
438 pid_file_name,))
439 self.host.run('rm -f "%s"' %(
440 pid_file_name,))
441 self.host.run('rm -f "%s"' %(
442 monitor_file_name,))
443 address["is_used"]= False
444
445
446 def reset_guest(self, guest_hostname):
mblighb24d12d2007-08-10 19:30:18 +0000447 """
448 Perform a hard reset on a virtual machine.
449
mbligh890fc5b2007-08-02 18:03:36 +0000450 Args:
451 guest_hostname: the ip (as it was specified in the
452 address list given to install()) of the guest
453 to terminate.
mblighb24d12d2007-08-10 19:30:18 +0000454
mbligh890fc5b2007-08-02 18:03:36 +0000455 Raises:
456 AutoservVirtError: the guest_hostname argument is
457 invalid
458 """
459 for address in self.addresses:
460 if address["ip"] is guest_hostname:
461 if address["is_used"]:
462 break
463 else:
mbligh03f4fc72007-11-29 20:56:14 +0000464 raise AutoservVirtError("guest "
mbligh890fc5b2007-08-02 18:03:36 +0000465 "hostname not in use")
466 else:
mbligh03f4fc72007-11-29 20:56:14 +0000467 raise AutoservVirtError("Unknown guest hostname")
mblighb24d12d2007-08-10 19:30:18 +0000468
mbligh890fc5b2007-08-02 18:03:36 +0000469 monitor_file_name= utils.sh_escape(os.path.join(self.pid_dir,
470 "vhost%s_monitor" % (address["ip"],)))
mblighb24d12d2007-08-10 19:30:18 +0000471
mbligh890fc5b2007-08-02 18:03:36 +0000472 self.host.run('python -c "%s"' % (utils.sh_escape(
mblighb24d12d2007-08-10 19:30:18 +0000473 _hard_reset_script % {
mbligh890fc5b2007-08-02 18:03:36 +0000474 "monitor_file_name" : monitor_file_name,}),))