blob: 0e57731789fc518e8c2adda30483cf373aba2ffe [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
jadmanski0afbb632008-06-06 21:10:57 +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
mbligh313f12c2008-05-15 23:33:50 +000019from autotest_lib.client.common_lib import error
mblighccb9e182008-04-17 15:42:10 +000020from autotest_lib.server import hypervisor, utils, hosts
mbligh03f4fc72007-11-29 20:56:14 +000021
mblighdcd57a82007-07-11 23:06:47 +000022
mblighb24d12d2007-08-10 19:30:18 +000023_qemu_ifup_script= """\
24#!/bin/sh
25# $1 is the name of the new qemu tap interface
26
27ifconfig $1 0.0.0.0 promisc up
28brctl addif br0 $1
29"""
30
31_check_process_script= """\
32if [ -f "%(pid_file_name)s" ]
33then
jadmanski0afbb632008-06-06 21:10:57 +000034 pid=$(cat "%(pid_file_name)s")
35 if [ -L /proc/$pid/exe ] && stat /proc/$pid/exe |
36 grep -q -- "-> \`%(qemu_binary)s\'\$"
37 then
38 echo "process present"
39 else
40 rm -f "%(pid_file_name)s"
41 rm -f "%(monitor_file_name)s"
42 fi
mblighb24d12d2007-08-10 19:30:18 +000043fi
44"""
45
46_hard_reset_script= """\
47import socket
48
49monitor_socket= socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
50monitor_socket.connect("%(monitor_file_name)s")
51monitor_socket.send("system_reset\\n")\n')
52"""
53
54_remove_modules_script= """\
55if $(grep -q "^kvm_intel [[:digit:]]\+ 0" /proc/modules)
56then
jadmanski0afbb632008-06-06 21:10:57 +000057 rmmod kvm-intel
mblighb24d12d2007-08-10 19:30:18 +000058fi
59
60if $(grep -q "^kvm_amd [[:digit:]]\+ 0" /proc/modules)
61then
jadmanski0afbb632008-06-06 21:10:57 +000062 rmmod kvm-amd
mblighb24d12d2007-08-10 19:30:18 +000063fi
64
65if $(grep -q "^kvm [[:digit:]]\+ 0" /proc/modules)
66then
jadmanski0afbb632008-06-06 21:10:57 +000067 rmmod kvm
mblighb24d12d2007-08-10 19:30:18 +000068fi
69"""
70
71
mblighdcd57a82007-07-11 23:06:47 +000072class KVM(hypervisor.Hypervisor):
jadmanski0afbb632008-06-06 21:10:57 +000073 """
74 This class represents a KVM virtual machine monitor.
mbligh890fc5b2007-08-02 18:03:36 +000075
jadmanski0afbb632008-06-06 21:10:57 +000076 Implementation details:
77 This is a leaf class in an abstract class hierarchy, it must
78 implement the unimplemented methods in parent classes.
79 """
mbligh890fc5b2007-08-02 18:03:36 +000080
jadmanski0afbb632008-06-06 21:10:57 +000081 build_dir= None
82 pid_dir= None
83 support_dir= None
84 addresses= []
85 insert_modules= True
86 modules= {}
mblighdc735a22007-08-02 16:54:37 +000087
88
jadmanski0afbb632008-06-06 21:10:57 +000089 def __del__(self):
90 """
91 Destroy a KVM object.
mblighb24d12d2007-08-10 19:30:18 +000092
jadmanski0afbb632008-06-06 21:10:57 +000093 Guests managed by this hypervisor that are still running will
94 be killed.
95 """
96 self.deinitialize()
mbligh890fc5b2007-08-02 18:03:36 +000097
98
jadmanski0afbb632008-06-06 21:10:57 +000099 def _insert_modules(self):
100 """
101 Insert the kvm modules into the kernel.
mblighb24d12d2007-08-10 19:30:18 +0000102
jadmanski0afbb632008-06-06 21:10:57 +0000103 The modules inserted are the ones from the build directory, NOT
104 the ones from the kernel.
mblighb24d12d2007-08-10 19:30:18 +0000105
jadmanski0afbb632008-06-06 21:10:57 +0000106 This function should only be called after install(). It will
107 check that the modules are not already loaded before attempting
108 to insert them.
109 """
110 cpu_flags= self.host.run('cat /proc/cpuinfo | '
111 'grep -e "^flags" | head -1 | cut -d " " -f 2-'
112 ).stdout.strip()
mblighb24d12d2007-08-10 19:30:18 +0000113
jadmanski0afbb632008-06-06 21:10:57 +0000114 if cpu_flags.find('vmx') != -1:
115 module_type= "intel"
116 elif cpu_flags.find('svm') != -1:
117 module_type= "amd"
118 else:
119 raise error.AutoservVirtError("No harware "
120 "virtualization extensions found, "
121 "KVM cannot run")
mblighb24d12d2007-08-10 19:30:18 +0000122
jadmanski0afbb632008-06-06 21:10:57 +0000123 self.host.run('if ! $(grep -q "^kvm " /proc/modules); '
124 'then insmod "%s"; fi' % (utils.sh_escape(
125 os.path.join(self.build_dir, "kernel/kvm.ko")),))
126 if module_type == "intel":
127 self.host.run('if ! $(grep -q "^kvm_intel " '
128 '/proc/modules); then insmod "%s"; fi' %
129 (utils.sh_escape(os.path.join(self.build_dir,
130 "kernel/kvm-intel.ko")),))
131 elif module_type == "amd":
132 self.host.run('if ! $(grep -q "^kvm_amd " '
133 '/proc/modules); then insmod "%s"; fi' %
134 (utils.sh_escape(os.path.join(self.build_dir,
135 "kernel/kvm-amd.ko")),))
mblighdc735a22007-08-02 16:54:37 +0000136
137
jadmanski0afbb632008-06-06 21:10:57 +0000138 def _remove_modules(self):
139 """
140 Remove the kvm modules from the kernel.
mblighb24d12d2007-08-10 19:30:18 +0000141
jadmanski0afbb632008-06-06 21:10:57 +0000142 This function checks that they're not in use before trying to
143 remove them.
144 """
145 self.host.run(_remove_modules_script)
mbligh890fc5b2007-08-02 18:03:36 +0000146
147
jadmanski0afbb632008-06-06 21:10:57 +0000148 def install(self, addresses, build=True, insert_modules=True, syncdir=None):
149 """
150 Compile the kvm software on the host that the object was
151 initialized with.
mblighb24d12d2007-08-10 19:30:18 +0000152
jadmanski0afbb632008-06-06 21:10:57 +0000153 The kvm kernel modules are compiled, for this, the kernel
154 sources must be available. A custom qemu is also compiled.
155 Note that 'make install' is not run, the kernel modules and
156 qemu are run from where they were built, therefore not
157 conflicting with what might already be installed.
mblighb24d12d2007-08-10 19:30:18 +0000158
jadmanski0afbb632008-06-06 21:10:57 +0000159 Args:
160 addresses: a list of dict entries of the form
161 {"mac" : "xx:xx:xx:xx:xx:xx",
162 "ip" : "yyy.yyy.yyy.yyy"} where x and y
163 are replaced with sensible values. The ip
164 address may be a hostname or an IPv6 instead.
mblighb24d12d2007-08-10 19:30:18 +0000165
jadmanski0afbb632008-06-06 21:10:57 +0000166 When a new virtual machine is created, the
167 first available entry in that list will be
168 used. The network card in the virtual machine
169 will be assigned the specified mac address and
170 autoserv will use the specified ip address to
171 connect to the virtual host via ssh. The virtual
172 machine os must therefore be configured to
173 configure its network with the ip corresponding
174 to the mac.
175 build: build kvm from the source material, if False,
176 it is assumed that the package contains the
177 source tree after a 'make'.
178 insert_modules: build kvm modules from the source
179 material and insert them. Otherwise, the
180 running kernel is assumed to already have
181 kvm support and nothing will be done concerning
182 the modules.
mblighb24d12d2007-08-10 19:30:18 +0000183
jadmanski0afbb632008-06-06 21:10:57 +0000184 TODO(poirier): check dependencies before building
185 kvm needs:
186 libasound2-dev
187 libsdl1.2-dev (or configure qemu with --disable-gfx-check, how?)
188 bridge-utils
189 """
190 self.addresses= [
191 {"mac" : address["mac"],
192 "ip" : address["ip"],
193 "is_used" : False} for address in addresses]
mblighb24d12d2007-08-10 19:30:18 +0000194
jadmanski0afbb632008-06-06 21:10:57 +0000195 self.build_dir = self.host.get_tmp_dir()
196 self.support_dir= self.host.get_tmp_dir()
mblighb24d12d2007-08-10 19:30:18 +0000197
jadmanski0afbb632008-06-06 21:10:57 +0000198 self.host.run('echo "%s" > "%s"' % (
199 utils.sh_escape(_qemu_ifup_script),
200 utils.sh_escape(os.path.join(self.support_dir,
201 "qemu-ifup.sh")),))
202 self.host.run('chmod a+x "%s"' % (
203 utils.sh_escape(os.path.join(self.support_dir,
204 "qemu-ifup.sh")),))
mblighb24d12d2007-08-10 19:30:18 +0000205
jadmanski0afbb632008-06-06 21:10:57 +0000206 self.host.send_file(self.source_material, self.build_dir)
207 remote_source_material= os.path.join(self.build_dir,
208 os.path.basename(self.source_material))
mblighb24d12d2007-08-10 19:30:18 +0000209
jadmanski0afbb632008-06-06 21:10:57 +0000210 self.build_dir= utils.unarchive(self.host,
211 remote_source_material)
mblighb24d12d2007-08-10 19:30:18 +0000212
jadmanski0afbb632008-06-06 21:10:57 +0000213 if insert_modules:
214 configure_modules= ""
215 self.insert_modules= True
216 else:
217 configure_modules= "--with-patched-kernel "
218 self.insert_modules= False
mblighb24d12d2007-08-10 19:30:18 +0000219
jadmanski0afbb632008-06-06 21:10:57 +0000220 # build
221 if build:
222 try:
223 self.host.run('make -C "%s" clean' % (
224 utils.sh_escape(self.build_dir),),
225 timeout=600)
226 except error.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,), timeout=600)
233 if syncdir:
234 cmd = 'cd "%s/kernel" && make sync LINUX=%s' % (
235 utils.sh_escape(self.build_dir),
236 utils.sh_escape(syncdir))
237 self.host.run(cmd)
238 self.host.run('make -j%d -C "%s"' % (
239 self.host.get_num_cpu() * 2,
240 utils.sh_escape(self.build_dir),), timeout=3600)
241 # remember path to modules
242 self.modules['kvm'] = "%s" %(
243 utils.sh_escape(os.path.join(self.build_dir,
244 "kernel/kvm.ko")))
245 self.modules['kvm-intel'] = "%s" %(
246 utils.sh_escape(os.path.join(self.build_dir,
247 "kernel/kvm-intel.ko")))
248 self.modules['kvm-amd'] = "%s" %(
249 utils.sh_escape(os.path.join(self.build_dir,
250 "kernel/kvm-amd.ko")))
251 print self.modules
mblighb24d12d2007-08-10 19:30:18 +0000252
jadmanski0afbb632008-06-06 21:10:57 +0000253 self.initialize()
mblighdc735a22007-08-02 16:54:37 +0000254
255
jadmanski0afbb632008-06-06 21:10:57 +0000256 def initialize(self):
257 """
258 Initialize the hypervisor.
mblighb24d12d2007-08-10 19:30:18 +0000259
jadmanski0afbb632008-06-06 21:10:57 +0000260 Loads needed kernel modules and creates temporary directories.
261 The logic is that you could compile once and
262 initialize - deinitialize many times. But why you would do that
263 has yet to be figured.
mblighb24d12d2007-08-10 19:30:18 +0000264
jadmanski0afbb632008-06-06 21:10:57 +0000265 Raises:
266 AutoservVirtError: cpuid doesn't report virtualization
267 extentions (vmx for intel or svm for amd), in
268 this case, kvm cannot run.
269 """
270 self.pid_dir= self.host.get_tmp_dir()
mblighb24d12d2007-08-10 19:30:18 +0000271
jadmanski0afbb632008-06-06 21:10:57 +0000272 if self.insert_modules:
273 self._remove_modules()
274 self._insert_modules()
mblighdc735a22007-08-02 16:54:37 +0000275
276
jadmanski0afbb632008-06-06 21:10:57 +0000277 def deinitialize(self):
278 """
279 Terminate the hypervisor.
mblighb24d12d2007-08-10 19:30:18 +0000280
jadmanski0afbb632008-06-06 21:10:57 +0000281 Kill all the virtual machines that are still running and
282 unload the kernel modules.
283 """
284 self.refresh_guests()
285 for address in self.addresses:
286 if address["is_used"]:
287 self.delete_guest(address["ip"])
288 self.pid_dir= None
mblighb24d12d2007-08-10 19:30:18 +0000289
jadmanski0afbb632008-06-06 21:10:57 +0000290 if self.insert_modules:
291 self._remove_modules()
mbligh890fc5b2007-08-02 18:03:36 +0000292
293
jadmanski0afbb632008-06-06 21:10:57 +0000294 def new_guest(self, qemu_options):
295 """
296 Start a new guest ("virtual machine").
mblighb24d12d2007-08-10 19:30:18 +0000297
jadmanski0afbb632008-06-06 21:10:57 +0000298 Returns:
299 The ip that was picked from the list supplied to
300 install() and assigned to this guest.
mblighb24d12d2007-08-10 19:30:18 +0000301
jadmanski0afbb632008-06-06 21:10:57 +0000302 Raises:
303 AutoservVirtError: no more addresses are available.
304 """
305 for address in self.addresses:
306 if not address["is_used"]:
307 break
308 else:
309 raise error.AutoservVirtError(
310 "No more addresses available")
mblighb24d12d2007-08-10 19:30:18 +0000311
jadmanski0afbb632008-06-06 21:10:57 +0000312 retval= self.host.run(
313 '%s'
314 # this is the line of options that can be modified
315 ' %s '
316 '-pidfile "%s" -daemonize -nographic '
317 #~ '-serial telnet::4444,server '
318 '-monitor unix:"%s",server,nowait '
319 '-net nic,macaddr="%s" -net tap,script="%s" -L "%s"' % (
320 utils.sh_escape(os.path.join(
321 self.build_dir,
322 "qemu/x86_64-softmmu/qemu-system-x86_64")),
323 qemu_options,
324 utils.sh_escape(os.path.join(
325 self.pid_dir,
326 "vhost%s_pid" % (address["ip"],))),
327 utils.sh_escape(os.path.join(
328 self.pid_dir,
329 "vhost%s_monitor" % (address["ip"],))),
330 utils.sh_escape(address["mac"]),
331 utils.sh_escape(os.path.join(
332 self.support_dir,
333 "qemu-ifup.sh")),
334 utils.sh_escape(os.path.join(
335 self.build_dir,
336 "qemu/pc-bios")),))
mblighb24d12d2007-08-10 19:30:18 +0000337
jadmanski0afbb632008-06-06 21:10:57 +0000338 address["is_used"]= True
339 return address["ip"]
mblighdc735a22007-08-02 16:54:37 +0000340
341
jadmanski0afbb632008-06-06 21:10:57 +0000342 def refresh_guests(self):
343 """
344 Refresh the list of guests addresses.
mblighb24d12d2007-08-10 19:30:18 +0000345
jadmanski0afbb632008-06-06 21:10:57 +0000346 The is_used status will be updated according to the presence
347 of the process specified in the pid file that was written when
348 the virtual machine was started.
mblighb24d12d2007-08-10 19:30:18 +0000349
jadmanski0afbb632008-06-06 21:10:57 +0000350 TODO(poirier): there are a lot of race conditions in this code
351 because the process might terminate on its own anywhere in
352 between
353 """
354 for address in self.addresses:
355 if address["is_used"]:
356 pid_file_name= utils.sh_escape(os.path.join(
357 self.pid_dir,
358 "vhost%s_pid" % (address["ip"],)))
359 monitor_file_name= utils.sh_escape(os.path.join(
360 self.pid_dir,
361 "vhost%s_monitor" % (address["ip"],)))
362 retval= self.host.run(
363 _check_process_script % {
364 "pid_file_name" : pid_file_name,
365 "monitor_file_name" : monitor_file_name,
366 "qemu_binary" : utils.sh_escape(
367 os.path.join(self.build_dir,
368 "qemu/x86_64-softmmu/"
369 "qemu-system-x86_64")),})
370 if (retval.stdout.strip() !=
371 "process present"):
372 address["is_used"]= False
mbligh890fc5b2007-08-02 18:03:36 +0000373
374
jadmanski0afbb632008-06-06 21:10:57 +0000375 def delete_guest(self, guest_hostname):
376 """
377 Terminate a virtual machine.
mblighb24d12d2007-08-10 19:30:18 +0000378
jadmanski0afbb632008-06-06 21:10:57 +0000379 Args:
380 guest_hostname: the ip (as it was specified in the
381 address list given to install()) of the guest
382 to terminate.
mblighb24d12d2007-08-10 19:30:18 +0000383
jadmanski0afbb632008-06-06 21:10:57 +0000384 Raises:
385 AutoservVirtError: the guest_hostname argument is
386 invalid
mbligh890fc5b2007-08-02 18:03:36 +0000387
jadmanski0afbb632008-06-06 21:10:57 +0000388 TODO(poirier): is there a difference in qemu between
389 sending SIGTEM or quitting from the monitor?
390 TODO(poirier): there are a lot of race conditions in this code
391 because the process might terminate on its own anywhere in
392 between
393 """
394 for address in self.addresses:
395 if address["ip"] == guest_hostname:
396 if address["is_used"]:
397 break
398 else:
399 # Will happen if deinitialize() is
400 # called while guest objects still
401 # exit and these are del'ed after.
402 # In that situation, nothing is to
403 # be done here, don't throw an error
404 # either because it will print an
405 # ugly message during garbage
406 # collection. The solution would be to
407 # delete the guest objects before
408 # calling deinitialize(), this can't be
409 # done by the KVM class, it has no
410 # reference to those objects and it
411 # cannot have any either. The Guest
412 # objects already need to have a
413 # reference to their managing
414 # hypervisor. If the hypervisor had a
415 # reference to the Guest objects it
416 # manages, it would create a circular
417 # reference and those objects would
418 # not be elligible for garbage
419 # collection. In turn, this means that
420 # the KVM object would not be
421 # automatically del'ed at the end of
422 # the program and guests that are still
423 # running would be left unattended.
424 # Note that this circular reference
425 # problem could be avoided by using
426 # weakref's in class KVM but the
427 # control file will most likely also
428 # have references to the guests.
429 return
430 else:
431 raise error.AutoservVirtError("Unknown guest hostname")
mblighb24d12d2007-08-10 19:30:18 +0000432
jadmanski0afbb632008-06-06 21:10:57 +0000433 pid_file_name= utils.sh_escape(os.path.join(self.pid_dir,
434 "vhost%s_pid" % (address["ip"],)))
435 monitor_file_name= utils.sh_escape(os.path.join(self.pid_dir,
436 "vhost%s_monitor" % (address["ip"],)))
mblighb24d12d2007-08-10 19:30:18 +0000437
jadmanski0afbb632008-06-06 21:10:57 +0000438 retval= self.host.run(
439 _check_process_script % {
440 "pid_file_name" : pid_file_name,
441 "monitor_file_name" : monitor_file_name,
442 "qemu_binary" : utils.sh_escape(os.path.join(
443 self.build_dir,
444 "qemu/x86_64-softmmu/qemu-system-x86_64")),})
445 if retval.stdout.strip() == "process present":
446 self.host.run('kill $(cat "%s")' %(
447 pid_file_name,))
448 self.host.run('rm -f "%s"' %(
449 pid_file_name,))
450 self.host.run('rm -f "%s"' %(
451 monitor_file_name,))
452 address["is_used"]= False
mbligh890fc5b2007-08-02 18:03:36 +0000453
454
jadmanski0afbb632008-06-06 21:10:57 +0000455 def reset_guest(self, guest_hostname):
456 """
457 Perform a hard reset on a virtual machine.
mblighb24d12d2007-08-10 19:30:18 +0000458
jadmanski0afbb632008-06-06 21:10:57 +0000459 Args:
460 guest_hostname: the ip (as it was specified in the
461 address list given to install()) of the guest
462 to terminate.
mblighb24d12d2007-08-10 19:30:18 +0000463
jadmanski0afbb632008-06-06 21:10:57 +0000464 Raises:
465 AutoservVirtError: the guest_hostname argument is
466 invalid
467 """
468 for address in self.addresses:
469 if address["ip"] is guest_hostname:
470 if address["is_used"]:
471 break
472 else:
473 raise error.AutoservVirtError("guest "
474 "hostname not in use")
475 else:
476 raise error.AutoservVirtError("Unknown guest hostname")
mblighb24d12d2007-08-10 19:30:18 +0000477
jadmanski0afbb632008-06-06 21:10:57 +0000478 monitor_file_name= utils.sh_escape(os.path.join(self.pid_dir,
479 "vhost%s_monitor" % (address["ip"],)))
mblighb24d12d2007-08-10 19:30:18 +0000480
jadmanski0afbb632008-06-06 21:10:57 +0000481 self.host.run('python -c "%s"' % (utils.sh_escape(
482 _hard_reset_script % {
483 "monitor_file_name" : monitor_file_name,}),))