blob: cecf6f3aa2d11be96b36d1022cf5ca8c33ee2a05 [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
mbligh7af05522007-12-04 22:48:35 +000089 modules= {}
mblighdc735a22007-08-02 16:54:37 +000090
91
mbligh890fc5b2007-08-02 18:03:36 +000092 def __del__(self):
mblighb24d12d2007-08-10 19:30:18 +000093 """
94 Destroy a KVM object.
95
mbligh890fc5b2007-08-02 18:03:36 +000096 Guests managed by this hypervisor that are still running will
97 be killed.
98 """
99 self.deinitialize()
100
101
102 def _insert_modules(self):
mblighb24d12d2007-08-10 19:30:18 +0000103 """
104 Insert the kvm modules into the kernel.
105
mbligh890fc5b2007-08-02 18:03:36 +0000106 The modules inserted are the ones from the build directory, NOT
107 the ones from the kernel.
mblighb24d12d2007-08-10 19:30:18 +0000108
mbligh890fc5b2007-08-02 18:03:36 +0000109 This function should only be called after install(). It will
110 check that the modules are not already loaded before attempting
111 to insert them.
112 """
113 cpu_flags= self.host.run('cat /proc/cpuinfo | '
114 'grep -e "^flags" | head -1 | cut -d " " -f 2-'
115 ).stdout.strip()
mblighb24d12d2007-08-10 19:30:18 +0000116
mbligh770e3d72007-11-02 01:33:52 +0000117 if cpu_flags.find('vmx') != -1:
mbligh890fc5b2007-08-02 18:03:36 +0000118 module_type= "intel"
119 elif cpu_flags.find('svm') != -1:
120 module_type= "amd"
121 else:
mbligh03f4fc72007-11-29 20:56:14 +0000122 raise AutoservVirtError("No harware "
mbligh890fc5b2007-08-02 18:03:36 +0000123 "virtualization extensions found, "
124 "KVM cannot run")
mblighb24d12d2007-08-10 19:30:18 +0000125
mbligh890fc5b2007-08-02 18:03:36 +0000126 self.host.run('if ! $(grep -q "^kvm " /proc/modules); '
127 'then insmod "%s"; fi' % (utils.sh_escape(
128 os.path.join(self.build_dir, "kernel/kvm.ko")),))
129 if module_type == "intel":
130 self.host.run('if ! $(grep -q "^kvm_intel " '
131 '/proc/modules); then insmod "%s"; fi' %
132 (utils.sh_escape(os.path.join(self.build_dir,
133 "kernel/kvm-intel.ko")),))
134 elif module_type == "amd":
135 self.host.run('if ! $(grep -q "^kvm_amd " '
136 '/proc/modules); then insmod "%s"; fi' %
137 (utils.sh_escape(os.path.join(self.build_dir,
138 "kernel/kvm-amd.ko")),))
mblighdc735a22007-08-02 16:54:37 +0000139
140
mbligh890fc5b2007-08-02 18:03:36 +0000141 def _remove_modules(self):
mblighb24d12d2007-08-10 19:30:18 +0000142 """
143 Remove the kvm modules from the kernel.
144
mbligh890fc5b2007-08-02 18:03:36 +0000145 This function checks that they're not in use before trying to
146 remove them.
147 """
mblighb24d12d2007-08-10 19:30:18 +0000148 self.host.run(_remove_modules_script)
mbligh890fc5b2007-08-02 18:03:36 +0000149
150
mbligh7af05522007-12-04 22:48:35 +0000151 def install(self, addresses, build=True, insert_modules=True, syncdir=None):
mblighb24d12d2007-08-10 19:30:18 +0000152 """
153 Compile the kvm software on the host that the object was
mbligh890fc5b2007-08-02 18:03:36 +0000154 initialized with.
mblighb24d12d2007-08-10 19:30:18 +0000155
mbligh890fc5b2007-08-02 18:03:36 +0000156 The kvm kernel modules are compiled, for this, the kernel
157 sources must be available. A custom qemu is also compiled.
158 Note that 'make install' is not run, the kernel modules and
159 qemu are run from where they were built, therefore not
160 conflicting with what might already be installed.
mblighb24d12d2007-08-10 19:30:18 +0000161
mbligh890fc5b2007-08-02 18:03:36 +0000162 Args:
163 addresses: a list of dict entries of the form
164 {"mac" : "xx:xx:xx:xx:xx:xx",
165 "ip" : "yyy.yyy.yyy.yyy"} where x and y
166 are replaced with sensible values. The ip
167 address may be a hostname or an IPv6 instead.
mblighb24d12d2007-08-10 19:30:18 +0000168
mbligh890fc5b2007-08-02 18:03:36 +0000169 When a new virtual machine is created, the
170 first available entry in that list will be
171 used. The network card in the virtual machine
172 will be assigned the specified mac address and
173 autoserv will use the specified ip address to
174 connect to the virtual host via ssh. The virtual
175 machine os must therefore be configured to
176 configure its network with the ip corresponding
177 to the mac.
178 build: build kvm from the source material, if False,
179 it is assumed that the package contains the
180 source tree after a 'make'.
181 insert_modules: build kvm modules from the source
182 material and insert them. Otherwise, the
183 running kernel is assumed to already have
184 kvm support and nothing will be done concerning
185 the modules.
mblighb24d12d2007-08-10 19:30:18 +0000186
mbligh890fc5b2007-08-02 18:03:36 +0000187 TODO(poirier): check dependencies before building
188 kvm needs:
189 libasound2-dev
190 libsdl1.2-dev (or configure qemu with --disable-gfx-check, how?)
mblighb24d12d2007-08-10 19:30:18 +0000191 bridge-utils
mbligh890fc5b2007-08-02 18:03:36 +0000192 """
193 self.addresses= [
194 {"mac" : address["mac"],
195 "ip" : address["ip"],
196 "is_used" : False} for address in addresses]
mblighb24d12d2007-08-10 19:30:18 +0000197
mbligh890fc5b2007-08-02 18:03:36 +0000198 self.build_dir = self.host.get_tmp_dir()
199 self.support_dir= self.host.get_tmp_dir()
mblighb24d12d2007-08-10 19:30:18 +0000200
mbligh890fc5b2007-08-02 18:03:36 +0000201 self.host.run('echo "%s" > "%s"' % (
mblighb24d12d2007-08-10 19:30:18 +0000202 utils.sh_escape(_qemu_ifup_script),
mbligh890fc5b2007-08-02 18:03:36 +0000203 utils.sh_escape(os.path.join(self.support_dir,
204 "qemu-ifup.sh")),))
205 self.host.run('chmod a+x "%s"' % (
206 utils.sh_escape(os.path.join(self.support_dir,
207 "qemu-ifup.sh")),))
mblighb24d12d2007-08-10 19:30:18 +0000208
mbligh890fc5b2007-08-02 18:03:36 +0000209 self.host.send_file(self.source_material, self.build_dir)
210 remote_source_material= os.path.join(self.build_dir,
211 os.path.basename(self.source_material))
mblighb24d12d2007-08-10 19:30:18 +0000212
mbligh890fc5b2007-08-02 18:03:36 +0000213 self.build_dir= utils.unarchive(self.host,
214 remote_source_material)
mblighb24d12d2007-08-10 19:30:18 +0000215
mbligh890fc5b2007-08-02 18:03:36 +0000216 if insert_modules:
217 configure_modules= ""
218 self.insert_modules= True
219 else:
220 configure_modules= "--with-patched-kernel "
221 self.insert_modules= False
mblighb24d12d2007-08-10 19:30:18 +0000222
mbligh890fc5b2007-08-02 18:03:36 +0000223 # build
224 if build:
225 try:
226 self.host.run('make -C "%s" clean' % (
227 utils.sh_escape(self.build_dir),))
mbligh03f4fc72007-11-29 20:56:14 +0000228 except AutoservRunError:
mbligh890fc5b2007-08-02 18:03:36 +0000229 # directory was already clean and contained
230 # no makefile
231 pass
232 self.host.run('cd "%s" && ./configure %s' % (
233 utils.sh_escape(self.build_dir),
234 configure_modules,))
mbligh7af05522007-12-04 22:48:35 +0000235 if syncdir:
236 cmd = 'cd "%s/kernel" && make sync LINUX=%s' % (
237 utils.sh_escape(self.build_dir),
238 utils.sh_escape(syncdir))
239 self.host.run(cmd)
mbligh890fc5b2007-08-02 18:03:36 +0000240 self.host.run('make -j%d -C "%s"' % (
241 self.host.get_num_cpu() * 2,
mblighdc735a22007-08-02 16:54:37 +0000242 utils.sh_escape(self.build_dir),))
mbligh7af05522007-12-04 22:48:35 +0000243 # remember path to modules
244 self.modules['kvm'] = "%s" %(
245 utils.sh_escape(os.path.join(self.build_dir,
246 "kernel/kvm.ko")))
247 self.modules['kvm-intel'] = "%s" %(
248 utils.sh_escape(os.path.join(self.build_dir,
249 "kernel/kvm-intel.ko")))
250 self.modules['kvm-amd'] = "%s" %(
251 utils.sh_escape(os.path.join(self.build_dir,
252 "kernel/kvm-amd.ko")))
253 print self.modules
mblighb24d12d2007-08-10 19:30:18 +0000254
mbligh890fc5b2007-08-02 18:03:36 +0000255 self.initialize()
mblighdc735a22007-08-02 16:54:37 +0000256
257
mbligh890fc5b2007-08-02 18:03:36 +0000258 def initialize(self):
mblighb24d12d2007-08-10 19:30:18 +0000259 """
260 Initialize the hypervisor.
261
mbligh890fc5b2007-08-02 18:03:36 +0000262 Loads needed kernel modules and creates temporary directories.
263 The logic is that you could compile once and
264 initialize - deinitialize many times. But why you would do that
265 has yet to be figured.
mblighb24d12d2007-08-10 19:30:18 +0000266
mbligh890fc5b2007-08-02 18:03:36 +0000267 Raises:
268 AutoservVirtError: cpuid doesn't report virtualization
mbligh770e3d72007-11-02 01:33:52 +0000269 extentions (vmx for intel or svm for amd), in
mbligh890fc5b2007-08-02 18:03:36 +0000270 this case, kvm cannot run.
271 """
272 self.pid_dir= self.host.get_tmp_dir()
mblighb24d12d2007-08-10 19:30:18 +0000273
mbligh890fc5b2007-08-02 18:03:36 +0000274 if self.insert_modules:
275 self._remove_modules()
276 self._insert_modules()
mblighdc735a22007-08-02 16:54:37 +0000277
278
mbligh890fc5b2007-08-02 18:03:36 +0000279 def deinitialize(self):
mblighb24d12d2007-08-10 19:30:18 +0000280 """
281 Terminate the hypervisor.
282
mbligh890fc5b2007-08-02 18:03:36 +0000283 Kill all the virtual machines that are still running and
284 unload the kernel modules.
285 """
286 self.refresh_guests()
287 for address in self.addresses:
mblighdc735a22007-08-02 16:54:37 +0000288 if address["is_used"]:
mbligh890fc5b2007-08-02 18:03:36 +0000289 self.delete_guest(address["ip"])
290 self.pid_dir= None
mblighb24d12d2007-08-10 19:30:18 +0000291
mbligh890fc5b2007-08-02 18:03:36 +0000292 if self.insert_modules:
293 self._remove_modules()
294
295
296 def new_guest(self, qemu_options):
mblighb24d12d2007-08-10 19:30:18 +0000297 """
298 Start a new guest ("virtual machine").
299
mbligh890fc5b2007-08-02 18:03:36 +0000300 Returns:
301 The ip that was picked from the list supplied to
302 install() and assigned to this guest.
mblighb24d12d2007-08-10 19:30:18 +0000303
mbligh890fc5b2007-08-02 18:03:36 +0000304 Raises:
305 AutoservVirtError: no more addresses are available.
306 """
307 for address in self.addresses:
308 if not address["is_used"]:
mblighdc735a22007-08-02 16:54:37 +0000309 break
mbligh890fc5b2007-08-02 18:03:36 +0000310 else:
mbligh03f4fc72007-11-29 20:56:14 +0000311 raise AutoservVirtError(
mbligh890fc5b2007-08-02 18:03:36 +0000312 "No more addresses available")
mblighb24d12d2007-08-10 19:30:18 +0000313
mbligh890fc5b2007-08-02 18:03:36 +0000314 # TODO(poirier): uses start-stop-daemon until qemu -pidfile
mblighb24d12d2007-08-10 19:30:18 +0000315 # and -daemonize can work together, this 'hides' the return
316 # code of qemu (bad)
mbligh890fc5b2007-08-02 18:03:36 +0000317 retval= self.host.run(
318 'start-stop-daemon -S --exec "%s" --pidfile "%s" -b -- '
319 # this is the line of options that can be modified
320 ' %s '
321 '-pidfile "%s" -nographic '
322 #~ '-serial telnet::4444,server '
323 '-monitor unix:"%s",server,nowait '
mblighd7685d32007-08-10 22:08:42 +0000324 '-net nic,macaddr="%s" -net tap,script="%s" -L "%s"' % (
mbligh890fc5b2007-08-02 18:03:36 +0000325 utils.sh_escape(os.path.join(
326 self.build_dir,
327 "qemu/x86_64-softmmu/qemu-system-x86_64")),
328 utils.sh_escape(os.path.join(
329 self.pid_dir,
330 "vhost%s_pid" % (address["ip"],))),
331 qemu_options,
332 utils.sh_escape(os.path.join(
333 self.pid_dir,
334 "vhost%s_pid" % (address["ip"],))),
335 utils.sh_escape(os.path.join(
336 self.pid_dir,
337 "vhost%s_monitor" % (address["ip"],))),
338 utils.sh_escape(address["mac"]),
339 utils.sh_escape(os.path.join(
340 self.support_dir,
mblighd7685d32007-08-10 22:08:42 +0000341 "qemu-ifup.sh")),
342 utils.sh_escape(os.path.join(
343 self.build_dir,
344 "qemu/pc-bios")),))
mblighb24d12d2007-08-10 19:30:18 +0000345
mbligh890fc5b2007-08-02 18:03:36 +0000346 address["is_used"]= True
347 return address["ip"]
mblighdc735a22007-08-02 16:54:37 +0000348
349
mbligh890fc5b2007-08-02 18:03:36 +0000350 def refresh_guests(self):
mblighb24d12d2007-08-10 19:30:18 +0000351 """
352 Refresh the list of guests addresses.
353
mbligh890fc5b2007-08-02 18:03:36 +0000354 The is_used status will be updated according to the presence
355 of the process specified in the pid file that was written when
356 the virtual machine was started.
mblighb24d12d2007-08-10 19:30:18 +0000357
mbligh890fc5b2007-08-02 18:03:36 +0000358 TODO(poirier): there are a lot of race conditions in this code
359 because the process might terminate on its own anywhere in
360 between
361 """
362 for address in self.addresses:
mblighdc735a22007-08-02 16:54:37 +0000363 if address["is_used"]:
mbligh890fc5b2007-08-02 18:03:36 +0000364 pid_file_name= utils.sh_escape(os.path.join(
365 self.pid_dir,
366 "vhost%s_pid" % (address["ip"],)))
367 monitor_file_name= utils.sh_escape(os.path.join(
368 self.pid_dir,
369 "vhost%s_monitor" % (address["ip"],)))
370 retval= self.host.run(
mblighb24d12d2007-08-10 19:30:18 +0000371 _check_process_script % {
mbligh890fc5b2007-08-02 18:03:36 +0000372 "pid_file_name" : pid_file_name,
373 "monitor_file_name" : monitor_file_name,
374 "qemu_binary" : utils.sh_escape(
375 os.path.join(self.build_dir,
376 "qemu/x86_64-softmmu/"
377 "qemu-system-x86_64")),})
378 if (retval.stdout.strip() !=
379 "process present"):
380 address["is_used"]= False
381
382
383 def delete_guest(self, guest_hostname):
mblighb24d12d2007-08-10 19:30:18 +0000384 """
385 Terminate a virtual machine.
386
mbligh890fc5b2007-08-02 18:03:36 +0000387 Args:
388 guest_hostname: the ip (as it was specified in the
389 address list given to install()) of the guest
390 to terminate.
mblighb24d12d2007-08-10 19:30:18 +0000391
mbligh890fc5b2007-08-02 18:03:36 +0000392 Raises:
393 AutoservVirtError: the guest_hostname argument is
394 invalid
395
396 TODO(poirier): is there a difference in qemu between
397 sending SIGTEM or quitting from the monitor?
398 TODO(poirier): there are a lot of race conditions in this code
399 because the process might terminate on its own anywhere in
400 between
401 """
402 for address in self.addresses:
403 if address["ip"] == guest_hostname:
404 if address["is_used"]:
405 break
406 else:
407 # Will happen if deinitialize() is
408 # called while guest objects still
409 # exit and these are del'ed after.
410 # In that situation, nothing is to
411 # be done here, don't throw an error
412 # either because it will print an
413 # ugly message during garbage
414 # collection. The solution would be to
415 # delete the guest objects before
416 # calling deinitialize(), this can't be
417 # done by the KVM class, it has no
418 # reference to those objects and it
419 # cannot have any either. The Guest
420 # objects already need to have a
421 # reference to their managing
422 # hypervisor. If the hypervisor had a
423 # reference to the Guest objects it
424 # manages, it would create a circular
425 # reference and those objects would
426 # not be elligible for garbage
427 # collection. In turn, this means that
428 # the KVM object would not be
429 # automatically del'ed at the end of
430 # the program and guests that are still
431 # running would be left unattended.
432 # Note that this circular reference
433 # problem could be avoided by using
434 # weakref's in class KVM but the
435 # control file will most likely also
436 # have references to the guests.
437 return
438 else:
mbligh03f4fc72007-11-29 20:56:14 +0000439 raise AutoservVirtError("Unknown guest hostname")
mblighb24d12d2007-08-10 19:30:18 +0000440
mbligh890fc5b2007-08-02 18:03:36 +0000441 pid_file_name= utils.sh_escape(os.path.join(self.pid_dir,
442 "vhost%s_pid" % (address["ip"],)))
443 monitor_file_name= utils.sh_escape(os.path.join(self.pid_dir,
444 "vhost%s_monitor" % (address["ip"],)))
mblighb24d12d2007-08-10 19:30:18 +0000445
mbligh890fc5b2007-08-02 18:03:36 +0000446 retval= self.host.run(
mblighb24d12d2007-08-10 19:30:18 +0000447 _check_process_script % {
mbligh890fc5b2007-08-02 18:03:36 +0000448 "pid_file_name" : pid_file_name,
449 "monitor_file_name" : monitor_file_name,
450 "qemu_binary" : utils.sh_escape(os.path.join(
451 self.build_dir,
452 "qemu/x86_64-softmmu/qemu-system-x86_64")),})
453 if retval.stdout.strip() == "process present":
454 self.host.run('kill $(cat "%s")' %(
455 pid_file_name,))
456 self.host.run('rm -f "%s"' %(
457 pid_file_name,))
458 self.host.run('rm -f "%s"' %(
459 monitor_file_name,))
460 address["is_used"]= False
461
462
463 def reset_guest(self, guest_hostname):
mblighb24d12d2007-08-10 19:30:18 +0000464 """
465 Perform a hard reset on a virtual machine.
466
mbligh890fc5b2007-08-02 18:03:36 +0000467 Args:
468 guest_hostname: the ip (as it was specified in the
469 address list given to install()) of the guest
470 to terminate.
mblighb24d12d2007-08-10 19:30:18 +0000471
mbligh890fc5b2007-08-02 18:03:36 +0000472 Raises:
473 AutoservVirtError: the guest_hostname argument is
474 invalid
475 """
476 for address in self.addresses:
477 if address["ip"] is guest_hostname:
478 if address["is_used"]:
479 break
480 else:
mbligh03f4fc72007-11-29 20:56:14 +0000481 raise AutoservVirtError("guest "
mbligh890fc5b2007-08-02 18:03:36 +0000482 "hostname not in use")
483 else:
mbligh03f4fc72007-11-29 20:56:14 +0000484 raise AutoservVirtError("Unknown guest hostname")
mblighb24d12d2007-08-10 19:30:18 +0000485
mbligh890fc5b2007-08-02 18:03:36 +0000486 monitor_file_name= utils.sh_escape(os.path.join(self.pid_dir,
487 "vhost%s_monitor" % (address["ip"],)))
mblighb24d12d2007-08-10 19:30:18 +0000488
mbligh890fc5b2007-08-02 18:03:36 +0000489 self.host.run('python -c "%s"' % (utils.sh_escape(
mblighb24d12d2007-08-10 19:30:18 +0000490 _hard_reset_script % {
mbligh890fc5b2007-08-02 18:03:36 +0000491 "monitor_file_name" : monitor_file_name,}),))