1# 2# Copyright 2007 Google Inc. Released under the GPL v2 3 4""" 5This module defines the KVM class 6 7 KVM: a KVM virtual machine monitor 8""" 9 10__author__ = """ 11mbligh@google.com (Martin J. Bligh), 12poirier@google.com (Benjamin Poirier), 13stutsman@google.com (Ryan Stutsman) 14""" 15 16import os 17 18from autotest_lib.client.common_lib import error 19from autotest_lib.server import hypervisor, utils, hosts 20 21 22_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 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" 41 fi 42fi 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 56 rmmod kvm-intel 57fi 58 59if $(grep -q "^kvm_amd [[:digit:]]\+ 0" /proc/modules) 60then 61 rmmod kvm-amd 62fi 63 64if $(grep -q "^kvm [[:digit:]]\+ 0" /proc/modules) 65then 66 rmmod kvm 67fi 68""" 69 70 71class KVM(hypervisor.Hypervisor): 72 """ 73 This class represents a KVM virtual machine monitor. 74 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 """ 79 80 build_dir= None 81 pid_dir= None 82 support_dir= None 83 addresses= [] 84 insert_modules= True 85 modules= {} 86 87 88 def __del__(self): 89 """ 90 Destroy a KVM object. 91 92 Guests managed by this hypervisor that are still running will 93 be killed. 94 """ 95 self.deinitialize() 96 97 98 def _insert_modules(self): 99 """ 100 Insert the kvm modules into the kernel. 101 102 The modules inserted are the ones from the build directory, NOT 103 the ones from the kernel. 104 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. 108 """ 109 cpu_flags= self.host.run('cat /proc/cpuinfo | ' 110 'grep -e "^flags" | head -1 | cut -d " " -f 2-' 111 ).stdout.strip() 112 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") 121 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")),)) 135 136 137 def _remove_modules(self): 138 """ 139 Remove the kvm modules from the kernel. 140 141 This function checks that they're not in use before trying to 142 remove them. 143 """ 144 self.host.run(_remove_modules_script) 145 146 147 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. 151 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. 157 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. 164 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. 182 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 188 """ 189 self.addresses= [ 190 {"mac" : address["mac"], 191 "ip" : address["ip"], 192 "is_used" : False} for address in addresses] 193 194 self.build_dir = self.host.get_tmp_dir() 195 self.support_dir= self.host.get_tmp_dir() 196 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")),)) 204 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)) 208 209 self.build_dir= utils.unarchive(self.host, 210 remote_source_material) 211 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 218 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 251 252 self.initialize() 253 254 255 def initialize(self): 256 """ 257 Initialize the hypervisor. 258 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. 263 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. 268 """ 269 self.pid_dir= self.host.get_tmp_dir() 270 271 if self.insert_modules: 272 self._remove_modules() 273 self._insert_modules() 274 275 276 def deinitialize(self): 277 """ 278 Terminate the hypervisor. 279 280 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 288 289 if self.insert_modules: 290 self._remove_modules() 291 292 293 def new_guest(self, qemu_options): 294 """ 295 Start a new guest ("virtual machine"). 296 297 Returns: 298 The ip that was picked from the list supplied to 299 install() and assigned to this guest. 300 301 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") 310 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")),)) 336 337 address["is_used"]= True 338 return address["ip"] 339 340 341 def refresh_guests(self): 342 """ 343 Refresh the list of guests addresses. 344 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. 348 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 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 372 373 374 def delete_guest(self, guest_hostname): 375 """ 376 Terminate a virtual machine. 377 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. 382 383 Raises: 384 AutoservVirtError: the guest_hostname argument is 385 invalid 386 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 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") 431 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"],))) 436 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 452 453 454 def reset_guest(self, guest_hostname): 455 """ 456 Perform a hard reset on a virtual machine. 457 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. 462 463 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") 476 477 monitor_file_name= utils.sh_escape(os.path.join(self.pid_dir, 478 "vhost%s_monitor" % (address["ip"],))) 479 480 self.host.run('python -c "%s"' % (utils.sh_escape( 481 _hard_reset_script % { 482 "monitor_file_name" : monitor_file_name,}),)) 483