blob: 28d91b114dbc35274faba5bd439419fc52d390e0 [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 '
306 '-net nic,macaddr="%s" -net tap,script="%s" ' % (
307 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,
323 "qemu-ifup.sh")),))
mblighb24d12d2007-08-10 19:30:18 +0000324
mbligh890fc5b2007-08-02 18:03:36 +0000325 address["is_used"]= True
326 return address["ip"]
mblighdc735a22007-08-02 16:54:37 +0000327
328
mbligh890fc5b2007-08-02 18:03:36 +0000329 def refresh_guests(self):
mblighb24d12d2007-08-10 19:30:18 +0000330 """
331 Refresh the list of guests addresses.
332
mbligh890fc5b2007-08-02 18:03:36 +0000333 The is_used status will be updated according to the presence
334 of the process specified in the pid file that was written when
335 the virtual machine was started.
mblighb24d12d2007-08-10 19:30:18 +0000336
mbligh890fc5b2007-08-02 18:03:36 +0000337 TODO(poirier): there are a lot of race conditions in this code
338 because the process might terminate on its own anywhere in
339 between
340 """
341 for address in self.addresses:
mblighdc735a22007-08-02 16:54:37 +0000342 if address["is_used"]:
mbligh890fc5b2007-08-02 18:03:36 +0000343 pid_file_name= utils.sh_escape(os.path.join(
344 self.pid_dir,
345 "vhost%s_pid" % (address["ip"],)))
346 monitor_file_name= utils.sh_escape(os.path.join(
347 self.pid_dir,
348 "vhost%s_monitor" % (address["ip"],)))
349 retval= self.host.run(
mblighb24d12d2007-08-10 19:30:18 +0000350 _check_process_script % {
mbligh890fc5b2007-08-02 18:03:36 +0000351 "pid_file_name" : pid_file_name,
352 "monitor_file_name" : monitor_file_name,
353 "qemu_binary" : utils.sh_escape(
354 os.path.join(self.build_dir,
355 "qemu/x86_64-softmmu/"
356 "qemu-system-x86_64")),})
357 if (retval.stdout.strip() !=
358 "process present"):
359 address["is_used"]= False
360
361
362 def delete_guest(self, guest_hostname):
mblighb24d12d2007-08-10 19:30:18 +0000363 """
364 Terminate a virtual machine.
365
mbligh890fc5b2007-08-02 18:03:36 +0000366 Args:
367 guest_hostname: the ip (as it was specified in the
368 address list given to install()) of the guest
369 to terminate.
mblighb24d12d2007-08-10 19:30:18 +0000370
mbligh890fc5b2007-08-02 18:03:36 +0000371 Raises:
372 AutoservVirtError: the guest_hostname argument is
373 invalid
374
375 TODO(poirier): is there a difference in qemu between
376 sending SIGTEM or quitting from the monitor?
377 TODO(poirier): there are a lot of race conditions in this code
378 because the process might terminate on its own anywhere in
379 between
380 """
381 for address in self.addresses:
382 if address["ip"] == guest_hostname:
383 if address["is_used"]:
384 break
385 else:
386 # Will happen if deinitialize() is
387 # called while guest objects still
388 # exit and these are del'ed after.
389 # In that situation, nothing is to
390 # be done here, don't throw an error
391 # either because it will print an
392 # ugly message during garbage
393 # collection. The solution would be to
394 # delete the guest objects before
395 # calling deinitialize(), this can't be
396 # done by the KVM class, it has no
397 # reference to those objects and it
398 # cannot have any either. The Guest
399 # objects already need to have a
400 # reference to their managing
401 # hypervisor. If the hypervisor had a
402 # reference to the Guest objects it
403 # manages, it would create a circular
404 # reference and those objects would
405 # not be elligible for garbage
406 # collection. In turn, this means that
407 # the KVM object would not be
408 # automatically del'ed at the end of
409 # the program and guests that are still
410 # running would be left unattended.
411 # Note that this circular reference
412 # problem could be avoided by using
413 # weakref's in class KVM but the
414 # control file will most likely also
415 # have references to the guests.
416 return
417 else:
418 raise errors.AutoservVirtError("Unknown guest hostname")
mblighb24d12d2007-08-10 19:30:18 +0000419
mbligh890fc5b2007-08-02 18:03:36 +0000420 pid_file_name= utils.sh_escape(os.path.join(self.pid_dir,
421 "vhost%s_pid" % (address["ip"],)))
422 monitor_file_name= utils.sh_escape(os.path.join(self.pid_dir,
423 "vhost%s_monitor" % (address["ip"],)))
mblighb24d12d2007-08-10 19:30:18 +0000424
mbligh890fc5b2007-08-02 18:03:36 +0000425 retval= self.host.run(
mblighb24d12d2007-08-10 19:30:18 +0000426 _check_process_script % {
mbligh890fc5b2007-08-02 18:03:36 +0000427 "pid_file_name" : pid_file_name,
428 "monitor_file_name" : monitor_file_name,
429 "qemu_binary" : utils.sh_escape(os.path.join(
430 self.build_dir,
431 "qemu/x86_64-softmmu/qemu-system-x86_64")),})
432 if retval.stdout.strip() == "process present":
433 self.host.run('kill $(cat "%s")' %(
434 pid_file_name,))
435 self.host.run('rm -f "%s"' %(
436 pid_file_name,))
437 self.host.run('rm -f "%s"' %(
438 monitor_file_name,))
439 address["is_used"]= False
440
441
442 def reset_guest(self, guest_hostname):
mblighb24d12d2007-08-10 19:30:18 +0000443 """
444 Perform a hard reset on a virtual machine.
445
mbligh890fc5b2007-08-02 18:03:36 +0000446 Args:
447 guest_hostname: the ip (as it was specified in the
448 address list given to install()) of the guest
449 to terminate.
mblighb24d12d2007-08-10 19:30:18 +0000450
mbligh890fc5b2007-08-02 18:03:36 +0000451 Raises:
452 AutoservVirtError: the guest_hostname argument is
453 invalid
454 """
455 for address in self.addresses:
456 if address["ip"] is guest_hostname:
457 if address["is_used"]:
458 break
459 else:
460 raise errors.AutoservVirtError("guest "
461 "hostname not in use")
462 else:
463 raise errors.AutoservVirtError("Unknown guest hostname")
mblighb24d12d2007-08-10 19:30:18 +0000464
mbligh890fc5b2007-08-02 18:03:36 +0000465 monitor_file_name= utils.sh_escape(os.path.join(self.pid_dir,
466 "vhost%s_monitor" % (address["ip"],)))
mblighb24d12d2007-08-10 19:30:18 +0000467
mbligh890fc5b2007-08-02 18:03:36 +0000468 self.host.run('python -c "%s"' % (utils.sh_escape(
mblighb24d12d2007-08-10 19:30:18 +0000469 _hard_reset_script % {
mbligh890fc5b2007-08-02 18:03:36 +0000470 "monitor_file_name" : monitor_file_name,}),))