blob: 5e5a0ab35b3946a09ee4ec4c76b99680bca29185 [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 retval= self.host.run(
mbligh74b22c32008-01-22 16:14:48 +0000315 '%s'
mbligh890fc5b2007-08-02 18:03:36 +0000316 # this is the line of options that can be modified
317 ' %s '
mbligh74b22c32008-01-22 16:14:48 +0000318 '-pidfile "%s" -daemonize -nographic '
mbligh890fc5b2007-08-02 18:03:36 +0000319 #~ '-serial telnet::4444,server '
320 '-monitor unix:"%s",server,nowait '
mblighd7685d32007-08-10 22:08:42 +0000321 '-net nic,macaddr="%s" -net tap,script="%s" -L "%s"' % (
mbligh890fc5b2007-08-02 18:03:36 +0000322 utils.sh_escape(os.path.join(
323 self.build_dir,
324 "qemu/x86_64-softmmu/qemu-system-x86_64")),
mbligh890fc5b2007-08-02 18:03:36 +0000325 qemu_options,
326 utils.sh_escape(os.path.join(
327 self.pid_dir,
328 "vhost%s_pid" % (address["ip"],))),
329 utils.sh_escape(os.path.join(
330 self.pid_dir,
331 "vhost%s_monitor" % (address["ip"],))),
332 utils.sh_escape(address["mac"]),
333 utils.sh_escape(os.path.join(
334 self.support_dir,
mblighd7685d32007-08-10 22:08:42 +0000335 "qemu-ifup.sh")),
336 utils.sh_escape(os.path.join(
337 self.build_dir,
338 "qemu/pc-bios")),))
mblighb24d12d2007-08-10 19:30:18 +0000339
mbligh890fc5b2007-08-02 18:03:36 +0000340 address["is_used"]= True
341 return address["ip"]
mblighdc735a22007-08-02 16:54:37 +0000342
343
mbligh890fc5b2007-08-02 18:03:36 +0000344 def refresh_guests(self):
mblighb24d12d2007-08-10 19:30:18 +0000345 """
346 Refresh the list of guests addresses.
347
mbligh890fc5b2007-08-02 18:03:36 +0000348 The is_used status will be updated according to the presence
349 of the process specified in the pid file that was written when
350 the virtual machine was started.
mblighb24d12d2007-08-10 19:30:18 +0000351
mbligh890fc5b2007-08-02 18:03:36 +0000352 TODO(poirier): there are a lot of race conditions in this code
353 because the process might terminate on its own anywhere in
354 between
355 """
356 for address in self.addresses:
mblighdc735a22007-08-02 16:54:37 +0000357 if address["is_used"]:
mbligh890fc5b2007-08-02 18:03:36 +0000358 pid_file_name= utils.sh_escape(os.path.join(
359 self.pid_dir,
360 "vhost%s_pid" % (address["ip"],)))
361 monitor_file_name= utils.sh_escape(os.path.join(
362 self.pid_dir,
363 "vhost%s_monitor" % (address["ip"],)))
364 retval= self.host.run(
mblighb24d12d2007-08-10 19:30:18 +0000365 _check_process_script % {
mbligh890fc5b2007-08-02 18:03:36 +0000366 "pid_file_name" : pid_file_name,
367 "monitor_file_name" : monitor_file_name,
368 "qemu_binary" : utils.sh_escape(
369 os.path.join(self.build_dir,
370 "qemu/x86_64-softmmu/"
371 "qemu-system-x86_64")),})
372 if (retval.stdout.strip() !=
373 "process present"):
374 address["is_used"]= False
375
376
377 def delete_guest(self, guest_hostname):
mblighb24d12d2007-08-10 19:30:18 +0000378 """
379 Terminate a virtual machine.
380
mbligh890fc5b2007-08-02 18:03:36 +0000381 Args:
382 guest_hostname: the ip (as it was specified in the
383 address list given to install()) of the guest
384 to terminate.
mblighb24d12d2007-08-10 19:30:18 +0000385
mbligh890fc5b2007-08-02 18:03:36 +0000386 Raises:
387 AutoservVirtError: the guest_hostname argument is
388 invalid
389
390 TODO(poirier): is there a difference in qemu between
391 sending SIGTEM or quitting from the monitor?
392 TODO(poirier): there are a lot of race conditions in this code
393 because the process might terminate on its own anywhere in
394 between
395 """
396 for address in self.addresses:
397 if address["ip"] == guest_hostname:
398 if address["is_used"]:
399 break
400 else:
401 # Will happen if deinitialize() is
402 # called while guest objects still
403 # exit and these are del'ed after.
404 # In that situation, nothing is to
405 # be done here, don't throw an error
406 # either because it will print an
407 # ugly message during garbage
408 # collection. The solution would be to
409 # delete the guest objects before
410 # calling deinitialize(), this can't be
411 # done by the KVM class, it has no
412 # reference to those objects and it
413 # cannot have any either. The Guest
414 # objects already need to have a
415 # reference to their managing
416 # hypervisor. If the hypervisor had a
417 # reference to the Guest objects it
418 # manages, it would create a circular
419 # reference and those objects would
420 # not be elligible for garbage
421 # collection. In turn, this means that
422 # the KVM object would not be
423 # automatically del'ed at the end of
424 # the program and guests that are still
425 # running would be left unattended.
426 # Note that this circular reference
427 # problem could be avoided by using
428 # weakref's in class KVM but the
429 # control file will most likely also
430 # have references to the guests.
431 return
432 else:
mbligh03f4fc72007-11-29 20:56:14 +0000433 raise AutoservVirtError("Unknown guest hostname")
mblighb24d12d2007-08-10 19:30:18 +0000434
mbligh890fc5b2007-08-02 18:03:36 +0000435 pid_file_name= utils.sh_escape(os.path.join(self.pid_dir,
436 "vhost%s_pid" % (address["ip"],)))
437 monitor_file_name= utils.sh_escape(os.path.join(self.pid_dir,
438 "vhost%s_monitor" % (address["ip"],)))
mblighb24d12d2007-08-10 19:30:18 +0000439
mbligh890fc5b2007-08-02 18:03:36 +0000440 retval= self.host.run(
mblighb24d12d2007-08-10 19:30:18 +0000441 _check_process_script % {
mbligh890fc5b2007-08-02 18:03:36 +0000442 "pid_file_name" : pid_file_name,
443 "monitor_file_name" : monitor_file_name,
444 "qemu_binary" : utils.sh_escape(os.path.join(
445 self.build_dir,
446 "qemu/x86_64-softmmu/qemu-system-x86_64")),})
447 if retval.stdout.strip() == "process present":
448 self.host.run('kill $(cat "%s")' %(
449 pid_file_name,))
450 self.host.run('rm -f "%s"' %(
451 pid_file_name,))
452 self.host.run('rm -f "%s"' %(
453 monitor_file_name,))
454 address["is_used"]= False
455
456
457 def reset_guest(self, guest_hostname):
mblighb24d12d2007-08-10 19:30:18 +0000458 """
459 Perform a hard reset on a virtual machine.
460
mbligh890fc5b2007-08-02 18:03:36 +0000461 Args:
462 guest_hostname: the ip (as it was specified in the
463 address list given to install()) of the guest
464 to terminate.
mblighb24d12d2007-08-10 19:30:18 +0000465
mbligh890fc5b2007-08-02 18:03:36 +0000466 Raises:
467 AutoservVirtError: the guest_hostname argument is
468 invalid
469 """
470 for address in self.addresses:
471 if address["ip"] is guest_hostname:
472 if address["is_used"]:
473 break
474 else:
mbligh03f4fc72007-11-29 20:56:14 +0000475 raise AutoservVirtError("guest "
mbligh890fc5b2007-08-02 18:03:36 +0000476 "hostname not in use")
477 else:
mbligh03f4fc72007-11-29 20:56:14 +0000478 raise AutoservVirtError("Unknown guest hostname")
mblighb24d12d2007-08-10 19:30:18 +0000479
mbligh890fc5b2007-08-02 18:03:36 +0000480 monitor_file_name= utils.sh_escape(os.path.join(self.pid_dir,
481 "vhost%s_monitor" % (address["ip"],)))
mblighb24d12d2007-08-10 19:30:18 +0000482
mbligh890fc5b2007-08-02 18:03:36 +0000483 self.host.run('python -c "%s"' % (utils.sh_escape(
mblighb24d12d2007-08-10 19:30:18 +0000484 _hard_reset_script % {
mbligh890fc5b2007-08-02 18:03:36 +0000485 "monitor_file_name" : monitor_file_name,}),))