KVM test: physical_resources_check: Initialize output
[autotest-zwu.git] / server / kvm.py
blobeddf7672ab0eb2e48496b1b796c7ff66d3114dd2
2 # Copyright 2007 Google Inc. Released under the GPL v2
4 """
5 This module defines the KVM class
7 KVM: a KVM virtual machine monitor
8 """
10 __author__ = """
11 mbligh@google.com (Martin J. Bligh),
12 poirier@google.com (Benjamin Poirier),
13 stutsman@google.com (Ryan Stutsman)
14 """
16 import os
18 from autotest_lib.client.common_lib import error
19 from autotest_lib.server import hypervisor, utils, hosts
22 _qemu_ifup_script= """\
23 #!/bin/sh
24 # $1 is the name of the new qemu tap interface
26 ifconfig $1 0.0.0.0 promisc up
27 brctl addif br0 $1
28 """
30 _check_process_script= """\
31 if [ -f "%(pid_file_name)s" ]
32 then
33 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"
43 """
45 _hard_reset_script= """\
46 import socket
48 monitor_socket= socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
49 monitor_socket.connect("%(monitor_file_name)s")
50 monitor_socket.send("system_reset\\n")\n')
51 """
53 _remove_modules_script= """\
54 if $(grep -q "^kvm_intel [[:digit:]]\+ 0" /proc/modules)
55 then
56 rmmod kvm-intel
59 if $(grep -q "^kvm_amd [[:digit:]]\+ 0" /proc/modules)
60 then
61 rmmod kvm-amd
64 if $(grep -q "^kvm [[:digit:]]\+ 0" /proc/modules)
65 then
66 rmmod kvm
68 """
71 class KVM(hypervisor.Hypervisor):
72 """
73 This class represents a KVM virtual machine monitor.
75 Implementation details:
76 This is a leaf class in an abstract class hierarchy, it must
77 implement the unimplemented methods in parent classes.
78 """
80 build_dir= None
81 pid_dir= None
82 support_dir= None
83 addresses= []
84 insert_modules= True
85 modules= {}
88 def __del__(self):
89 """
90 Destroy a KVM object.
92 Guests managed by this hypervisor that are still running will
93 be killed.
94 """
95 self.deinitialize()
98 def _insert_modules(self):
99 """
100 Insert the kvm modules into the kernel.
102 The modules inserted are the ones from the build directory, NOT
103 the ones from the kernel.
105 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.
109 cpu_flags= self.host.run('cat /proc/cpuinfo | '
110 'grep -e "^flags" | head -1 | cut -d " " -f 2-'
111 ).stdout.strip()
113 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")
122 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")),))
137 def _remove_modules(self):
139 Remove the kvm modules from the kernel.
141 This function checks that they're not in use before trying to
142 remove them.
144 self.host.run(_remove_modules_script)
147 def install(self, addresses, build=True, insert_modules=True, syncdir=None):
149 Compile the kvm software on the host that the object was
150 initialized with.
152 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.
158 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.
165 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.
183 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
189 self.addresses= [
190 {"mac" : address["mac"],
191 "ip" : address["ip"],
192 "is_used" : False} for address in addresses]
194 self.build_dir = self.host.get_tmp_dir()
195 self.support_dir= self.host.get_tmp_dir()
197 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")),))
205 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))
209 self.build_dir= utils.unarchive(self.host,
210 remote_source_material)
212 if insert_modules:
213 configure_modules= ""
214 self.insert_modules= True
215 else:
216 configure_modules= "--with-patched-kernel "
217 self.insert_modules= False
219 # 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
252 self.initialize()
255 def initialize(self):
257 Initialize the hypervisor.
259 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.
264 Raises:
265 AutoservVirtError: cpuid doesn't report virtualization
266 extentions (vmx for intel or svm for amd), in
267 this case, kvm cannot run.
269 self.pid_dir= self.host.get_tmp_dir()
271 if self.insert_modules:
272 self._remove_modules()
273 self._insert_modules()
276 def deinitialize(self):
278 Terminate the hypervisor.
280 Kill all the virtual machines that are still running and
281 unload the kernel modules.
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
289 if self.insert_modules:
290 self._remove_modules()
293 def new_guest(self, qemu_options):
295 Start a new guest ("virtual machine").
297 Returns:
298 The ip that was picked from the list supplied to
299 install() and assigned to this guest.
301 Raises:
302 AutoservVirtError: no more addresses are available.
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")
311 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")),))
337 address["is_used"]= True
338 return address["ip"]
341 def refresh_guests(self):
343 Refresh the list of guests addresses.
345 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.
349 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
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
374 def delete_guest(self, guest_hostname):
376 Terminate a virtual machine.
378 Args:
379 guest_hostname: the ip (as it was specified in the
380 address list given to install()) of the guest
381 to terminate.
383 Raises:
384 AutoservVirtError: the guest_hostname argument is
385 invalid
387 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
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")
432 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"],)))
437 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
454 def reset_guest(self, guest_hostname):
456 Perform a hard reset on a virtual machine.
458 Args:
459 guest_hostname: the ip (as it was specified in the
460 address list given to install()) of the guest
461 to terminate.
463 Raises:
464 AutoservVirtError: the guest_hostname argument is
465 invalid
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")
477 monitor_file_name= utils.sh_escape(os.path.join(self.pid_dir,
478 "vhost%s_monitor" % (address["ip"],)))
480 self.host.run('python -c "%s"' % (utils.sh_escape(
481 _hard_reset_script % {
482 "monitor_file_name" : monitor_file_name,}),))