blob: eddf7672ab0eb2e48496b1b796c7ff66d3114dd2 [file] [log] [blame]
mblighdcd57a82007-07-11 23:06:47 +00001#
2# Copyright 2007 Google Inc. Released under the GPL v2
3
mblighdc735a22007-08-02 16:54:37 +00004"""
5This module defines the KVM class
mblighdcd57a82007-07-11 23:06:47 +00006
jadmanski0afbb632008-06-06 21:10:57 +00007 KVM: a KVM virtual machine monitor
mblighdcd57a82007-07-11 23:06:47 +00008"""
9
mblighdc735a22007-08-02 16:54:37 +000010__author__ = """
11mbligh@google.com (Martin J. Bligh),
mblighdcd57a82007-07-11 23:06:47 +000012poirier@google.com (Benjamin Poirier),
mblighdc735a22007-08-02 16:54:37 +000013stutsman@google.com (Ryan Stutsman)
14"""
mblighdcd57a82007-07-11 23:06:47 +000015
16import os
17
mbligh313f12c2008-05-15 23:33:50 +000018from autotest_lib.client.common_lib import error
mblighccb9e182008-04-17 15:42:10 +000019from autotest_lib.server import hypervisor, utils, hosts
mbligh03f4fc72007-11-29 20:56:14 +000020
mblighdcd57a82007-07-11 23:06:47 +000021
mblighb24d12d2007-08-10 19:30:18 +000022_qemu_ifup_script= """\
23#!/bin/sh
24# $1 is the name of the new qemu tap interface
25
26ifconfig $1 0.0.0.0 promisc up
27brctl addif br0 $1
28"""
29
30_check_process_script= """\
31if [ -f "%(pid_file_name)s" ]
32then
jadmanski0afbb632008-06-06 21:10:57 +000033 pid=$(cat "%(pid_file_name)s")
34 if [ -L /proc/$pid/exe ] && stat /proc/$pid/exe |
35 grep -q -- "-> \`%(qemu_binary)s\'\$"
36 then
37 echo "process present"
38 else
39 rm -f "%(pid_file_name)s"
40 rm -f "%(monitor_file_name)s"
41 fi
mblighb24d12d2007-08-10 19:30:18 +000042fi
43"""
44
45_hard_reset_script= """\
46import socket
47
48monitor_socket= socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
49monitor_socket.connect("%(monitor_file_name)s")
50monitor_socket.send("system_reset\\n")\n')
51"""
52
53_remove_modules_script= """\
54if $(grep -q "^kvm_intel [[:digit:]]\+ 0" /proc/modules)
55then
jadmanski0afbb632008-06-06 21:10:57 +000056 rmmod kvm-intel
mblighb24d12d2007-08-10 19:30:18 +000057fi
58
59if $(grep -q "^kvm_amd [[:digit:]]\+ 0" /proc/modules)
60then
jadmanski0afbb632008-06-06 21:10:57 +000061 rmmod kvm-amd
mblighb24d12d2007-08-10 19:30:18 +000062fi
63
64if $(grep -q "^kvm [[:digit:]]\+ 0" /proc/modules)
65then
jadmanski0afbb632008-06-06 21:10:57 +000066 rmmod kvm
mblighb24d12d2007-08-10 19:30:18 +000067fi
68"""
69
70
mblighdcd57a82007-07-11 23:06:47 +000071class KVM(hypervisor.Hypervisor):
jadmanski0afbb632008-06-06 21:10:57 +000072 """
73 This class represents a KVM virtual machine monitor.
mbligh890fc5b2007-08-02 18:03:36 +000074
jadmanski0afbb632008-06-06 21:10:57 +000075 Implementation details:
76 This is a leaf class in an abstract class hierarchy, it must
77 implement the unimplemented methods in parent classes.
78 """
mbligh890fc5b2007-08-02 18:03:36 +000079
jadmanski0afbb632008-06-06 21:10:57 +000080 build_dir= None
81 pid_dir= None
82 support_dir= None
83 addresses= []
84 insert_modules= True
85 modules= {}
mblighdc735a22007-08-02 16:54:37 +000086
87
jadmanski0afbb632008-06-06 21:10:57 +000088 def __del__(self):
89 """
90 Destroy a KVM object.
mblighb24d12d2007-08-10 19:30:18 +000091
jadmanski0afbb632008-06-06 21:10:57 +000092 Guests managed by this hypervisor that are still running will
93 be killed.
94 """
95 self.deinitialize()
mbligh890fc5b2007-08-02 18:03:36 +000096
97
jadmanski0afbb632008-06-06 21:10:57 +000098 def _insert_modules(self):
99 """
100 Insert the kvm modules into the kernel.
mblighb24d12d2007-08-10 19:30:18 +0000101
jadmanski0afbb632008-06-06 21:10:57 +0000102 The modules inserted are the ones from the build directory, NOT
103 the ones from the kernel.
mblighb24d12d2007-08-10 19:30:18 +0000104
jadmanski0afbb632008-06-06 21:10:57 +0000105 This function should only be called after install(). It will
106 check that the modules are not already loaded before attempting
107 to insert them.
108 """
109 cpu_flags= self.host.run('cat /proc/cpuinfo | '
110 'grep -e "^flags" | head -1 | cut -d " " -f 2-'
111 ).stdout.strip()
mblighb24d12d2007-08-10 19:30:18 +0000112
jadmanski0afbb632008-06-06 21:10:57 +0000113 if cpu_flags.find('vmx') != -1:
114 module_type= "intel"
115 elif cpu_flags.find('svm') != -1:
116 module_type= "amd"
117 else:
118 raise error.AutoservVirtError("No harware "
119 "virtualization extensions found, "
120 "KVM cannot run")
mblighb24d12d2007-08-10 19:30:18 +0000121
jadmanski0afbb632008-06-06 21:10:57 +0000122 self.host.run('if ! $(grep -q "^kvm " /proc/modules); '
123 'then insmod "%s"; fi' % (utils.sh_escape(
124 os.path.join(self.build_dir, "kernel/kvm.ko")),))
125 if module_type == "intel":
126 self.host.run('if ! $(grep -q "^kvm_intel " '
127 '/proc/modules); then insmod "%s"; fi' %
128 (utils.sh_escape(os.path.join(self.build_dir,
129 "kernel/kvm-intel.ko")),))
130 elif module_type == "amd":
131 self.host.run('if ! $(grep -q "^kvm_amd " '
132 '/proc/modules); then insmod "%s"; fi' %
133 (utils.sh_escape(os.path.join(self.build_dir,
134 "kernel/kvm-amd.ko")),))
mblighdc735a22007-08-02 16:54:37 +0000135
136
jadmanski0afbb632008-06-06 21:10:57 +0000137 def _remove_modules(self):
138 """
139 Remove the kvm modules from the kernel.
mblighb24d12d2007-08-10 19:30:18 +0000140
jadmanski0afbb632008-06-06 21:10:57 +0000141 This function checks that they're not in use before trying to
142 remove them.
143 """
144 self.host.run(_remove_modules_script)
mbligh890fc5b2007-08-02 18:03:36 +0000145
146
jadmanski0afbb632008-06-06 21:10:57 +0000147 def install(self, addresses, build=True, insert_modules=True, syncdir=None):
148 """
149 Compile the kvm software on the host that the object was
150 initialized with.
mblighb24d12d2007-08-10 19:30:18 +0000151
jadmanski0afbb632008-06-06 21:10:57 +0000152 The kvm kernel modules are compiled, for this, the kernel
153 sources must be available. A custom qemu is also compiled.
154 Note that 'make install' is not run, the kernel modules and
155 qemu are run from where they were built, therefore not
156 conflicting with what might already be installed.
mblighb24d12d2007-08-10 19:30:18 +0000157
jadmanski0afbb632008-06-06 21:10:57 +0000158 Args:
159 addresses: a list of dict entries of the form
160 {"mac" : "xx:xx:xx:xx:xx:xx",
161 "ip" : "yyy.yyy.yyy.yyy"} where x and y
162 are replaced with sensible values. The ip
163 address may be a hostname or an IPv6 instead.
mblighb24d12d2007-08-10 19:30:18 +0000164
jadmanski0afbb632008-06-06 21:10:57 +0000165 When a new virtual machine is created, the
166 first available entry in that list will be
167 used. The network card in the virtual machine
168 will be assigned the specified mac address and
169 autoserv will use the specified ip address to
170 connect to the virtual host via ssh. The virtual
171 machine os must therefore be configured to
172 configure its network with the ip corresponding
173 to the mac.
174 build: build kvm from the source material, if False,
175 it is assumed that the package contains the
176 source tree after a 'make'.
177 insert_modules: build kvm modules from the source
178 material and insert them. Otherwise, the
179 running kernel is assumed to already have
180 kvm support and nothing will be done concerning
181 the modules.
mblighb24d12d2007-08-10 19:30:18 +0000182
jadmanski0afbb632008-06-06 21:10:57 +0000183 TODO(poirier): check dependencies before building
184 kvm needs:
185 libasound2-dev
186 libsdl1.2-dev (or configure qemu with --disable-gfx-check, how?)
187 bridge-utils
188 """
189 self.addresses= [
190 {"mac" : address["mac"],
191 "ip" : address["ip"],
192 "is_used" : False} for address in addresses]
mblighb24d12d2007-08-10 19:30:18 +0000193
jadmanski0afbb632008-06-06 21:10:57 +0000194 self.build_dir = self.host.get_tmp_dir()
195 self.support_dir= self.host.get_tmp_dir()
mblighb24d12d2007-08-10 19:30:18 +0000196
jadmanski0afbb632008-06-06 21:10:57 +0000197 self.host.run('echo "%s" > "%s"' % (
198 utils.sh_escape(_qemu_ifup_script),
199 utils.sh_escape(os.path.join(self.support_dir,
200 "qemu-ifup.sh")),))
201 self.host.run('chmod a+x "%s"' % (
202 utils.sh_escape(os.path.join(self.support_dir,
203 "qemu-ifup.sh")),))
mblighb24d12d2007-08-10 19:30:18 +0000204
jadmanski0afbb632008-06-06 21:10:57 +0000205 self.host.send_file(self.source_material, self.build_dir)
206 remote_source_material= os.path.join(self.build_dir,
207 os.path.basename(self.source_material))
mblighb24d12d2007-08-10 19:30:18 +0000208
jadmanski0afbb632008-06-06 21:10:57 +0000209 self.build_dir= utils.unarchive(self.host,
210 remote_source_material)
mblighb24d12d2007-08-10 19:30:18 +0000211
jadmanski0afbb632008-06-06 21:10:57 +0000212 if insert_modules:
213 configure_modules= ""
214 self.insert_modules= True
215 else:
216 configure_modules= "--with-patched-kernel "
217 self.insert_modules= False
mblighb24d12d2007-08-10 19:30:18 +0000218
jadmanski0afbb632008-06-06 21:10:57 +0000219 # build
220 if build:
221 try:
222 self.host.run('make -C "%s" clean' % (
223 utils.sh_escape(self.build_dir),),
224 timeout=600)
225 except error.AutoservRunError:
226 # directory was already clean and contained
227 # no makefile
228 pass
229 self.host.run('cd "%s" && ./configure %s' % (
230 utils.sh_escape(self.build_dir),
231 configure_modules,), timeout=600)
232 if syncdir:
233 cmd = 'cd "%s/kernel" && make sync LINUX=%s' % (
234 utils.sh_escape(self.build_dir),
235 utils.sh_escape(syncdir))
236 self.host.run(cmd)
237 self.host.run('make -j%d -C "%s"' % (
238 self.host.get_num_cpu() * 2,
239 utils.sh_escape(self.build_dir),), timeout=3600)
240 # remember path to modules
241 self.modules['kvm'] = "%s" %(
242 utils.sh_escape(os.path.join(self.build_dir,
243 "kernel/kvm.ko")))
244 self.modules['kvm-intel'] = "%s" %(
245 utils.sh_escape(os.path.join(self.build_dir,
246 "kernel/kvm-intel.ko")))
247 self.modules['kvm-amd'] = "%s" %(
248 utils.sh_escape(os.path.join(self.build_dir,
249 "kernel/kvm-amd.ko")))
250 print self.modules
mblighb24d12d2007-08-10 19:30:18 +0000251
jadmanski0afbb632008-06-06 21:10:57 +0000252 self.initialize()
mblighdc735a22007-08-02 16:54:37 +0000253
254
jadmanski0afbb632008-06-06 21:10:57 +0000255 def initialize(self):
256 """
257 Initialize the hypervisor.
mblighb24d12d2007-08-10 19:30:18 +0000258
jadmanski0afbb632008-06-06 21:10:57 +0000259 Loads needed kernel modules and creates temporary directories.
260 The logic is that you could compile once and
261 initialize - deinitialize many times. But why you would do that
262 has yet to be figured.
mblighb24d12d2007-08-10 19:30:18 +0000263
jadmanski0afbb632008-06-06 21:10:57 +0000264 Raises:
265 AutoservVirtError: cpuid doesn't report virtualization
266 extentions (vmx for intel or svm for amd), in
267 this case, kvm cannot run.
268 """
269 self.pid_dir= self.host.get_tmp_dir()
mblighb24d12d2007-08-10 19:30:18 +0000270
jadmanski0afbb632008-06-06 21:10:57 +0000271 if self.insert_modules:
272 self._remove_modules()
273 self._insert_modules()
mblighdc735a22007-08-02 16:54:37 +0000274
275
jadmanski0afbb632008-06-06 21:10:57 +0000276 def deinitialize(self):
277 """
278 Terminate the hypervisor.
mblighb24d12d2007-08-10 19:30:18 +0000279
jadmanski0afbb632008-06-06 21:10:57 +0000280 Kill all the virtual machines that are still running and
281 unload the kernel modules.
282 """
283 self.refresh_guests()
284 for address in self.addresses:
285 if address["is_used"]:
286 self.delete_guest(address["ip"])
287 self.pid_dir= None
mblighb24d12d2007-08-10 19:30:18 +0000288
jadmanski0afbb632008-06-06 21:10:57 +0000289 if self.insert_modules:
290 self._remove_modules()
mbligh890fc5b2007-08-02 18:03:36 +0000291
292
jadmanski0afbb632008-06-06 21:10:57 +0000293 def new_guest(self, qemu_options):
294 """
295 Start a new guest ("virtual machine").
mblighb24d12d2007-08-10 19:30:18 +0000296
jadmanski0afbb632008-06-06 21:10:57 +0000297 Returns:
298 The ip that was picked from the list supplied to
299 install() and assigned to this guest.
mblighb24d12d2007-08-10 19:30:18 +0000300
jadmanski0afbb632008-06-06 21:10:57 +0000301 Raises:
302 AutoservVirtError: no more addresses are available.
303 """
304 for address in self.addresses:
305 if not address["is_used"]:
306 break
307 else:
308 raise error.AutoservVirtError(
309 "No more addresses available")
mblighb24d12d2007-08-10 19:30:18 +0000310
jadmanski0afbb632008-06-06 21:10:57 +0000311 retval= self.host.run(
312 '%s'
313 # this is the line of options that can be modified
314 ' %s '
315 '-pidfile "%s" -daemonize -nographic '
316 #~ '-serial telnet::4444,server '
317 '-monitor unix:"%s",server,nowait '
318 '-net nic,macaddr="%s" -net tap,script="%s" -L "%s"' % (
319 utils.sh_escape(os.path.join(
320 self.build_dir,
321 "qemu/x86_64-softmmu/qemu-system-x86_64")),
322 qemu_options,
323 utils.sh_escape(os.path.join(
324 self.pid_dir,
325 "vhost%s_pid" % (address["ip"],))),
326 utils.sh_escape(os.path.join(
327 self.pid_dir,
328 "vhost%s_monitor" % (address["ip"],))),
329 utils.sh_escape(address["mac"]),
330 utils.sh_escape(os.path.join(
331 self.support_dir,
332 "qemu-ifup.sh")),
333 utils.sh_escape(os.path.join(
334 self.build_dir,
335 "qemu/pc-bios")),))
mblighb24d12d2007-08-10 19:30:18 +0000336
jadmanski0afbb632008-06-06 21:10:57 +0000337 address["is_used"]= True
338 return address["ip"]
mblighdc735a22007-08-02 16:54:37 +0000339
340
jadmanski0afbb632008-06-06 21:10:57 +0000341 def refresh_guests(self):
342 """
343 Refresh the list of guests addresses.
mblighb24d12d2007-08-10 19:30:18 +0000344
jadmanski0afbb632008-06-06 21:10:57 +0000345 The is_used status will be updated according to the presence
346 of the process specified in the pid file that was written when
347 the virtual machine was started.
mblighb24d12d2007-08-10 19:30:18 +0000348
jadmanski0afbb632008-06-06 21:10:57 +0000349 TODO(poirier): there are a lot of race conditions in this code
350 because the process might terminate on its own anywhere in
351 between
352 """
353 for address in self.addresses:
354 if address["is_used"]:
355 pid_file_name= utils.sh_escape(os.path.join(
356 self.pid_dir,
357 "vhost%s_pid" % (address["ip"],)))
358 monitor_file_name= utils.sh_escape(os.path.join(
359 self.pid_dir,
360 "vhost%s_monitor" % (address["ip"],)))
361 retval= self.host.run(
362 _check_process_script % {
363 "pid_file_name" : pid_file_name,
364 "monitor_file_name" : monitor_file_name,
365 "qemu_binary" : utils.sh_escape(
366 os.path.join(self.build_dir,
367 "qemu/x86_64-softmmu/"
368 "qemu-system-x86_64")),})
369 if (retval.stdout.strip() !=
370 "process present"):
371 address["is_used"]= False
mbligh890fc5b2007-08-02 18:03:36 +0000372
373
jadmanski0afbb632008-06-06 21:10:57 +0000374 def delete_guest(self, guest_hostname):
375 """
376 Terminate a virtual machine.
mblighb24d12d2007-08-10 19:30:18 +0000377
jadmanski0afbb632008-06-06 21:10:57 +0000378 Args:
379 guest_hostname: the ip (as it was specified in the
380 address list given to install()) of the guest
381 to terminate.
mblighb24d12d2007-08-10 19:30:18 +0000382
jadmanski0afbb632008-06-06 21:10:57 +0000383 Raises:
384 AutoservVirtError: the guest_hostname argument is
385 invalid
mbligh890fc5b2007-08-02 18:03:36 +0000386
jadmanski0afbb632008-06-06 21:10:57 +0000387 TODO(poirier): is there a difference in qemu between
388 sending SIGTEM or quitting from the monitor?
389 TODO(poirier): there are a lot of race conditions in this code
390 because the process might terminate on its own anywhere in
391 between
392 """
393 for address in self.addresses:
394 if address["ip"] == guest_hostname:
395 if address["is_used"]:
396 break
397 else:
398 # Will happen if deinitialize() is
399 # called while guest objects still
400 # exit and these are del'ed after.
401 # In that situation, nothing is to
402 # be done here, don't throw an error
403 # either because it will print an
404 # ugly message during garbage
405 # collection. The solution would be to
406 # delete the guest objects before
407 # calling deinitialize(), this can't be
408 # done by the KVM class, it has no
409 # reference to those objects and it
410 # cannot have any either. The Guest
411 # objects already need to have a
412 # reference to their managing
413 # hypervisor. If the hypervisor had a
414 # reference to the Guest objects it
415 # manages, it would create a circular
416 # reference and those objects would
417 # not be elligible for garbage
418 # collection. In turn, this means that
419 # the KVM object would not be
420 # automatically del'ed at the end of
421 # the program and guests that are still
422 # running would be left unattended.
423 # Note that this circular reference
424 # problem could be avoided by using
425 # weakref's in class KVM but the
426 # control file will most likely also
427 # have references to the guests.
428 return
429 else:
430 raise error.AutoservVirtError("Unknown guest hostname")
mblighb24d12d2007-08-10 19:30:18 +0000431
jadmanski0afbb632008-06-06 21:10:57 +0000432 pid_file_name= utils.sh_escape(os.path.join(self.pid_dir,
433 "vhost%s_pid" % (address["ip"],)))
434 monitor_file_name= utils.sh_escape(os.path.join(self.pid_dir,
435 "vhost%s_monitor" % (address["ip"],)))
mblighb24d12d2007-08-10 19:30:18 +0000436
jadmanski0afbb632008-06-06 21:10:57 +0000437 retval= self.host.run(
438 _check_process_script % {
439 "pid_file_name" : pid_file_name,
440 "monitor_file_name" : monitor_file_name,
441 "qemu_binary" : utils.sh_escape(os.path.join(
442 self.build_dir,
443 "qemu/x86_64-softmmu/qemu-system-x86_64")),})
444 if retval.stdout.strip() == "process present":
445 self.host.run('kill $(cat "%s")' %(
446 pid_file_name,))
447 self.host.run('rm -f "%s"' %(
448 pid_file_name,))
449 self.host.run('rm -f "%s"' %(
450 monitor_file_name,))
451 address["is_used"]= False
mbligh890fc5b2007-08-02 18:03:36 +0000452
453
jadmanski0afbb632008-06-06 21:10:57 +0000454 def reset_guest(self, guest_hostname):
455 """
456 Perform a hard reset on a virtual machine.
mblighb24d12d2007-08-10 19:30:18 +0000457
jadmanski0afbb632008-06-06 21:10:57 +0000458 Args:
459 guest_hostname: the ip (as it was specified in the
460 address list given to install()) of the guest
461 to terminate.
mblighb24d12d2007-08-10 19:30:18 +0000462
jadmanski0afbb632008-06-06 21:10:57 +0000463 Raises:
464 AutoservVirtError: the guest_hostname argument is
465 invalid
466 """
467 for address in self.addresses:
468 if address["ip"] is guest_hostname:
469 if address["is_used"]:
470 break
471 else:
472 raise error.AutoservVirtError("guest "
473 "hostname not in use")
474 else:
475 raise error.AutoservVirtError("Unknown guest hostname")
mblighb24d12d2007-08-10 19:30:18 +0000476
jadmanski0afbb632008-06-06 21:10:57 +0000477 monitor_file_name= utils.sh_escape(os.path.join(self.pid_dir,
478 "vhost%s_monitor" % (address["ip"],)))
mblighb24d12d2007-08-10 19:30:18 +0000479
jadmanski0afbb632008-06-06 21:10:57 +0000480 self.host.run('python -c "%s"' % (utils.sh_escape(
481 _hard_reset_script % {
482 "monitor_file_name" : monitor_file_name,}),))