Package ganeti :: Package hypervisor :: Module hv_kvm
[hide private]
[frames] | no frames]

Source Code for Module ganeti.hypervisor.hv_kvm

   1  # 
   2  # 
   3   
   4  # Copyright (C) 2008, 2009, 2010 Google Inc. 
   5  # 
   6  # This program is free software; you can redistribute it and/or modify 
   7  # it under the terms of the GNU General Public License as published by 
   8  # the Free Software Foundation; either version 2 of the License, or 
   9  # (at your option) any later version. 
  10  # 
  11  # This program is distributed in the hope that it will be useful, but 
  12  # WITHOUT ANY WARRANTY; without even the implied warranty of 
  13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
  14  # General Public License for more details. 
  15  # 
  16  # You should have received a copy of the GNU General Public License 
  17  # along with this program; if not, write to the Free Software 
  18  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
  19  # 02110-1301, USA. 
  20   
  21   
  22  """KVM hypervisor 
  23   
  24  """ 
  25   
  26  import errno 
  27  import os 
  28  import os.path 
  29  import re 
  30  import tempfile 
  31  import time 
  32  import logging 
  33  import pwd 
  34  from cStringIO import StringIO 
  35   
  36  from ganeti import utils 
  37  from ganeti import constants 
  38  from ganeti import errors 
  39  from ganeti import serializer 
  40  from ganeti import objects 
  41  from ganeti import uidpool 
  42  from ganeti import ssconf 
  43  from ganeti.hypervisor import hv_base 
  44  from ganeti import netutils 
  45   
  46   
  47  _KVM_NETWORK_SCRIPT = constants.SYSCONFDIR + "/ganeti/kvm-vif-bridge" 
48 49 50 -def _WriteNetScript(instance, nic, index):
51 """Write a script to connect a net interface to the proper bridge. 52 53 This can be used by any qemu-type hypervisor. 54 55 @type instance: L{objects.Instance} 56 @param instance: Instance object 57 @type nic: L{objects.NIC} 58 @param nic: NIC object 59 @type index: int 60 @param index: NIC index 61 @return: Script 62 @rtype: string 63 64 """ 65 if instance.tags: 66 tags = " ".join(instance.tags) 67 else: 68 tags = "" 69 70 buf = StringIO() 71 sw = utils.ShellWriter(buf) 72 sw.Write("#!/bin/sh") 73 sw.Write("# this is autogenerated by Ganeti, please do not edit") 74 sw.Write("export PATH=$PATH:/sbin:/usr/sbin") 75 sw.Write("export INSTANCE=%s", utils.ShellQuote(instance.name)) 76 sw.Write("export MAC=%s", utils.ShellQuote(nic.mac)) 77 sw.Write("export MODE=%s", 78 utils.ShellQuote(nic.nicparams[constants.NIC_MODE])) 79 sw.Write("export INTERFACE=\"$1\"") 80 sw.Write("export TAGS=%s", utils.ShellQuote(tags)) 81 82 if nic.ip: 83 sw.Write("export IP=%s", utils.ShellQuote(nic.ip)) 84 85 if nic.nicparams[constants.NIC_LINK]: 86 sw.Write("export LINK=%s", 87 utils.ShellQuote(nic.nicparams[constants.NIC_LINK])) 88 89 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED: 90 sw.Write("export BRIDGE=%s", 91 utils.ShellQuote(nic.nicparams[constants.NIC_LINK])) 92 93 # TODO: make this configurable at ./configure time 94 sw.Write("if [ -x %s ]; then", utils.ShellQuote(_KVM_NETWORK_SCRIPT)) 95 sw.IncIndent() 96 try: 97 sw.Write("# Execute the user-specific vif file") 98 sw.Write(_KVM_NETWORK_SCRIPT) 99 finally: 100 sw.DecIndent() 101 sw.Write("else") 102 sw.IncIndent() 103 try: 104 sw.Write("ifconfig $INTERFACE 0.0.0.0 up") 105 106 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED: 107 sw.Write("# Connect the interface to the bridge") 108 sw.Write("brctl addif $BRIDGE $INTERFACE") 109 110 elif nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_ROUTED: 111 if not nic.ip: 112 raise errors.HypervisorError("nic/%d is routed, but has no IP" 113 " address" % index) 114 115 sw.Write("# Route traffic targeted at the IP to the interface") 116 if nic.nicparams[constants.NIC_LINK]: 117 sw.Write("while ip rule del dev $INTERFACE; do :; done") 118 sw.Write("ip rule add dev $INTERFACE table $LINK") 119 sw.Write("ip route replace $IP table $LINK proto static" 120 " dev $INTERFACE") 121 else: 122 sw.Write("ip route replace $IP proto static dev $INTERFACE") 123 124 interface_v4_conf = "/proc/sys/net/ipv4/conf/$INTERFACE" 125 sw.Write(" if [ -d %s ]; then", interface_v4_conf) 126 sw.IncIndent() 127 try: 128 sw.Write("echo 1 > %s/proxy_arp", interface_v4_conf) 129 sw.Write("echo 1 > %s/forwarding", interface_v4_conf) 130 finally: 131 sw.DecIndent() 132 sw.Write("fi") 133 134 interface_v6_conf = "/proc/sys/net/ipv6/conf/$INTERFACE" 135 sw.Write("if [ -d %s ]; then", interface_v6_conf) 136 sw.IncIndent() 137 try: 138 sw.Write("echo 1 > %s/proxy_ndp", interface_v6_conf) 139 sw.Write("echo 1 > %s/forwarding", interface_v6_conf) 140 finally: 141 sw.DecIndent() 142 sw.Write("fi") 143 finally: 144 sw.DecIndent() 145 sw.Write("fi") 146 147 return buf.getvalue()
148
149 150 -class KVMHypervisor(hv_base.BaseHypervisor):
151 """KVM hypervisor interface""" 152 CAN_MIGRATE = True 153 154 _ROOT_DIR = constants.RUN_GANETI_DIR + "/kvm-hypervisor" 155 _PIDS_DIR = _ROOT_DIR + "/pid" # contains live instances pids 156 _UIDS_DIR = _ROOT_DIR + "/uid" # contains instances reserved uids 157 _CTRL_DIR = _ROOT_DIR + "/ctrl" # contains instances control sockets 158 _CONF_DIR = _ROOT_DIR + "/conf" # contains instances startup data 159 # KVM instances with chroot enabled are started in empty chroot directories. 160 _CHROOT_DIR = _ROOT_DIR + "/chroot" # for empty chroot directories 161 # After an instance is stopped, its chroot directory is removed. 162 # If the chroot directory is not empty, it can't be removed. 163 # A non-empty chroot directory indicates a possible security incident. 164 # To support forensics, the non-empty chroot directory is quarantined in 165 # a separate directory, called 'chroot-quarantine'. 166 _CHROOT_QUARANTINE_DIR = _ROOT_DIR + "/chroot-quarantine" 167 _DIRS = [_ROOT_DIR, _PIDS_DIR, _UIDS_DIR, _CTRL_DIR, _CONF_DIR, 168 _CHROOT_DIR, _CHROOT_QUARANTINE_DIR] 169 170 PARAMETERS = { 171 constants.HV_KERNEL_PATH: hv_base.OPT_FILE_CHECK, 172 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK, 173 constants.HV_ROOT_PATH: hv_base.NO_CHECK, 174 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK, 175 constants.HV_ACPI: hv_base.NO_CHECK, 176 constants.HV_SERIAL_CONSOLE: hv_base.NO_CHECK, 177 constants.HV_VNC_BIND_ADDRESS: 178 (False, lambda x: (netutils.IP4Address.IsValid(x) or 179 utils.IsNormAbsPath(x)), 180 "the VNC bind address must be either a valid IP address or an absolute" 181 " pathname", None, None), 182 constants.HV_VNC_TLS: hv_base.NO_CHECK, 183 constants.HV_VNC_X509: hv_base.OPT_DIR_CHECK, 184 constants.HV_VNC_X509_VERIFY: hv_base.NO_CHECK, 185 constants.HV_VNC_PASSWORD_FILE: hv_base.OPT_FILE_CHECK, 186 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK, 187 constants.HV_BOOT_ORDER: 188 hv_base.ParamInSet(True, constants.HT_KVM_VALID_BO_TYPES), 189 constants.HV_NIC_TYPE: 190 hv_base.ParamInSet(True, constants.HT_KVM_VALID_NIC_TYPES), 191 constants.HV_DISK_TYPE: 192 hv_base.ParamInSet(True, constants.HT_KVM_VALID_DISK_TYPES), 193 constants.HV_USB_MOUSE: 194 hv_base.ParamInSet(False, constants.HT_KVM_VALID_MOUSE_TYPES), 195 constants.HV_MIGRATION_PORT: hv_base.NET_PORT_CHECK, 196 constants.HV_MIGRATION_BANDWIDTH: hv_base.NO_CHECK, 197 constants.HV_MIGRATION_DOWNTIME: hv_base.NO_CHECK, 198 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK, 199 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK, 200 constants.HV_DISK_CACHE: 201 hv_base.ParamInSet(True, constants.HT_VALID_CACHE_TYPES), 202 constants.HV_SECURITY_MODEL: 203 hv_base.ParamInSet(True, constants.HT_KVM_VALID_SM_TYPES), 204 constants.HV_SECURITY_DOMAIN: hv_base.NO_CHECK, 205 constants.HV_KVM_FLAG: 206 hv_base.ParamInSet(False, constants.HT_KVM_FLAG_VALUES), 207 constants.HV_VHOST_NET: hv_base.NO_CHECK, 208 constants.HV_KVM_USE_CHROOT: hv_base.NO_CHECK, 209 constants.HV_MEM_PATH: hv_base.OPT_DIR_CHECK, 210 } 211 212 _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)', 213 re.M | re.I) 214 _MIGRATION_INFO_MAX_BAD_ANSWERS = 5 215 _MIGRATION_INFO_RETRY_DELAY = 2 216 217 _VERSION_RE = re.compile(r"\b(\d+)\.(\d+)\.(\d+)\b") 218 219 ANCILLARY_FILES = [ 220 _KVM_NETWORK_SCRIPT, 221 ] 222
223 - def __init__(self):
224 hv_base.BaseHypervisor.__init__(self) 225 # Let's make sure the directories we need exist, even if the RUN_DIR lives 226 # in a tmpfs filesystem or has been otherwise wiped out. 227 dirs = [(dname, constants.RUN_DIRS_MODE) for dname in self._DIRS] 228 utils.EnsureDirs(dirs)
229 230 @classmethod
231 - def _InstancePidFile(cls, instance_name):
232 """Returns the instance pidfile. 233 234 """ 235 return utils.PathJoin(cls._PIDS_DIR, instance_name)
236 237 @classmethod
238 - def _InstanceUidFile(cls, instance_name):
239 """Returns the instance uidfile. 240 241 """ 242 return utils.PathJoin(cls._UIDS_DIR, instance_name)
243 244 @classmethod
245 - def _InstancePidInfo(cls, pid):
246 """Check pid file for instance information. 247 248 Check that a pid file is associated with an instance, and retrieve 249 information from its command line. 250 251 @type pid: string or int 252 @param pid: process id of the instance to check 253 @rtype: tuple 254 @return: (instance_name, memory, vcpus) 255 @raise errors.HypervisorError: when an instance cannot be found 256 257 """ 258 alive = utils.IsProcessAlive(pid) 259 if not alive: 260 raise errors.HypervisorError("Cannot get info for pid %s" % pid) 261 262 cmdline_file = utils.PathJoin("/proc", str(pid), "cmdline") 263 try: 264 cmdline = utils.ReadFile(cmdline_file) 265 except EnvironmentError, err: 266 raise errors.HypervisorError("Can't open cmdline file for pid %s: %s" % 267 (pid, err)) 268 269 instance = None 270 memory = 0 271 vcpus = 0 272 273 arg_list = cmdline.split('\x00') 274 while arg_list: 275 arg = arg_list.pop(0) 276 if arg == "-name": 277 instance = arg_list.pop(0) 278 elif arg == "-m": 279 memory = int(arg_list.pop(0)) 280 elif arg == "-smp": 281 vcpus = int(arg_list.pop(0)) 282 283 if instance is None: 284 raise errors.HypervisorError("Pid %s doesn't contain a ganeti kvm" 285 " instance" % pid) 286 287 return (instance, memory, vcpus)
288
289 - def _InstancePidAlive(self, instance_name):
290 """Returns the instance pidfile, pid, and liveness. 291 292 @type instance_name: string 293 @param instance_name: instance name 294 @rtype: tuple 295 @return: (pid file name, pid, liveness) 296 297 """ 298 pidfile = self._InstancePidFile(instance_name) 299 pid = utils.ReadPidFile(pidfile) 300 301 alive = False 302 try: 303 cmd_instance = self._InstancePidInfo(pid)[0] 304 alive = (cmd_instance == instance_name) 305 except errors.HypervisorError: 306 pass 307 308 return (pidfile, pid, alive)
309
310 - def _CheckDown(self, instance_name):
311 """Raises an error unless the given instance is down. 312 313 """ 314 alive = self._InstancePidAlive(instance_name)[2] 315 if alive: 316 raise errors.HypervisorError("Failed to start instance %s: %s" % 317 (instance_name, "already running"))
318 319 @classmethod
320 - def _InstanceMonitor(cls, instance_name):
321 """Returns the instance monitor socket name 322 323 """ 324 return utils.PathJoin(cls._CTRL_DIR, "%s.monitor" % instance_name)
325 326 @classmethod
327 - def _InstanceSerial(cls, instance_name):
328 """Returns the instance serial socket name 329 330 """ 331 return utils.PathJoin(cls._CTRL_DIR, "%s.serial" % instance_name)
332 333 @staticmethod
335 """Returns the correct parameters for socat 336 337 If we have a new-enough socat we can use raw mode with an escape character. 338 339 """ 340 if constants.SOCAT_USE_ESCAPE: 341 return "raw,echo=0,escape=%s" % constants.SOCAT_ESCAPE_CODE 342 else: 343 return "echo=0,icanon=0"
344 345 @classmethod
346 - def _InstanceKVMRuntime(cls, instance_name):
347 """Returns the instance KVM runtime filename 348 349 """ 350 return utils.PathJoin(cls._CONF_DIR, "%s.runtime" % instance_name)
351 352 @classmethod
353 - def _InstanceChrootDir(cls, instance_name):
354 """Returns the name of the KVM chroot dir of the instance 355 356 """ 357 return utils.PathJoin(cls._CHROOT_DIR, instance_name)
358 359 @classmethod
360 - def _TryReadUidFile(cls, uid_file):
361 """Try to read a uid file 362 363 """ 364 if os.path.exists(uid_file): 365 try: 366 uid = int(utils.ReadOneLineFile(uid_file)) 367 return uid 368 except EnvironmentError: 369 logging.warning("Can't read uid file", exc_info=True) 370 except (TypeError, ValueError): 371 logging.warning("Can't parse uid file contents", exc_info=True) 372 return None
373 374 @classmethod
375 - def _RemoveInstanceRuntimeFiles(cls, pidfile, instance_name):
376 """Removes an instance's rutime sockets/files/dirs. 377 378 """ 379 utils.RemoveFile(pidfile) 380 utils.RemoveFile(cls._InstanceMonitor(instance_name)) 381 utils.RemoveFile(cls._InstanceSerial(instance_name)) 382 utils.RemoveFile(cls._InstanceKVMRuntime(instance_name)) 383 uid_file = cls._InstanceUidFile(instance_name) 384 uid = cls._TryReadUidFile(uid_file) 385 utils.RemoveFile(uid_file) 386 if uid is not None: 387 uidpool.ReleaseUid(uid) 388 try: 389 chroot_dir = cls._InstanceChrootDir(instance_name) 390 utils.RemoveDir(chroot_dir) 391 except OSError, err: 392 if err.errno == errno.ENOTEMPTY: 393 # The chroot directory is expected to be empty, but it isn't. 394 new_chroot_dir = tempfile.mkdtemp(dir=cls._CHROOT_QUARANTINE_DIR, 395 prefix="%s-%s-" % 396 (instance_name, 397 utils.TimestampForFilename())) 398 logging.warning("The chroot directory of instance %s can not be" 399 " removed as it is not empty. Moving it to the" 400 " quarantine instead. Please investigate the" 401 " contents (%s) and clean up manually", 402 instance_name, new_chroot_dir) 403 utils.RenameFile(chroot_dir, new_chroot_dir) 404 else: 405 raise
406 407 @staticmethod
408 - def _WriteNetScriptFile(instance, seq, nic):
409 """Write a script to connect a net interface to the proper bridge. 410 411 This can be used by any qemu-type hypervisor. 412 413 @param instance: instance we're acting on 414 @type instance: instance object 415 @param seq: nic sequence number 416 @type seq: int 417 @param nic: nic we're acting on 418 @type nic: nic object 419 @return: netscript file name 420 @rtype: string 421 422 """ 423 script = _WriteNetScript(instance, nic, seq) 424 425 # As much as we'd like to put this in our _ROOT_DIR, that will happen to be 426 # mounted noexec sometimes, so we'll have to find another place. 427 (tmpfd, tmpfile_name) = tempfile.mkstemp() 428 tmpfile = os.fdopen(tmpfd, 'w') 429 try: 430 tmpfile.write(script) 431 finally: 432 tmpfile.close() 433 os.chmod(tmpfile_name, 0755) 434 return tmpfile_name
435
436 - def ListInstances(self):
437 """Get the list of running instances. 438 439 We can do this by listing our live instances directory and 440 checking whether the associated kvm process is still alive. 441 442 """ 443 result = [] 444 for name in os.listdir(self._PIDS_DIR): 445 if self._InstancePidAlive(name)[2]: 446 result.append(name) 447 return result
448
449 - def GetInstanceInfo(self, instance_name):
450 """Get instance properties. 451 452 @type instance_name: string 453 @param instance_name: the instance name 454 @rtype: tuple of strings 455 @return: (name, id, memory, vcpus, stat, times) 456 457 """ 458 _, pid, alive = self._InstancePidAlive(instance_name) 459 if not alive: 460 return None 461 462 _, memory, vcpus = self._InstancePidInfo(pid) 463 stat = "---b-" 464 times = "0" 465 466 return (instance_name, pid, memory, vcpus, stat, times)
467
468 - def GetAllInstancesInfo(self):
469 """Get properties of all instances. 470 471 @return: list of tuples (name, id, memory, vcpus, stat, times) 472 473 """ 474 data = [] 475 for name in os.listdir(self._PIDS_DIR): 476 try: 477 info = self.GetInstanceInfo(name) 478 except errors.HypervisorError: 479 continue 480 if info: 481 data.append(info) 482 return data
483
484 - def _GenerateKVMRuntime(self, instance, block_devices):
485 """Generate KVM information to start an instance. 486 487 """ 488 pidfile = self._InstancePidFile(instance.name) 489 kvm = constants.KVM_PATH 490 kvm_cmd = [kvm] 491 # used just by the vnc server, if enabled 492 kvm_cmd.extend(['-name', instance.name]) 493 kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]]) 494 kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]]) 495 kvm_cmd.extend(['-pidfile', pidfile]) 496 kvm_cmd.extend(['-daemonize']) 497 if not instance.hvparams[constants.HV_ACPI]: 498 kvm_cmd.extend(['-no-acpi']) 499 500 hvp = instance.hvparams 501 boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK 502 boot_cdrom = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_CDROM 503 boot_network = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_NETWORK 504 505 if hvp[constants.HV_KVM_FLAG] == constants.HT_KVM_ENABLED: 506 kvm_cmd.extend(["-enable-kvm"]) 507 elif hvp[constants.HV_KVM_FLAG] == constants.HT_KVM_DISABLED: 508 kvm_cmd.extend(["-disable-kvm"]) 509 510 if boot_network: 511 kvm_cmd.extend(['-boot', 'n']) 512 513 disk_type = hvp[constants.HV_DISK_TYPE] 514 if disk_type == constants.HT_DISK_PARAVIRTUAL: 515 if_val = ',if=virtio' 516 else: 517 if_val = ',if=%s' % disk_type 518 # Cache mode 519 disk_cache = hvp[constants.HV_DISK_CACHE] 520 if disk_cache != constants.HT_CACHE_DEFAULT: 521 cache_val = ",cache=%s" % disk_cache 522 else: 523 cache_val = "" 524 for cfdev, dev_path in block_devices: 525 if cfdev.mode != constants.DISK_RDWR: 526 raise errors.HypervisorError("Instance has read-only disks which" 527 " are not supported by KVM") 528 # TODO: handle FD_LOOP and FD_BLKTAP (?) 529 if boot_disk: 530 kvm_cmd.extend(['-boot', 'c']) 531 if disk_type != constants.HT_DISK_IDE: 532 boot_val = ',boot=on' 533 else: 534 boot_val = '' 535 # We only boot from the first disk 536 boot_disk = False 537 else: 538 boot_val = '' 539 540 drive_val = 'file=%s,format=raw%s%s%s' % (dev_path, if_val, boot_val, 541 cache_val) 542 kvm_cmd.extend(['-drive', drive_val]) 543 544 iso_image = hvp[constants.HV_CDROM_IMAGE_PATH] 545 if iso_image: 546 options = ',format=raw,media=cdrom' 547 if boot_cdrom: 548 kvm_cmd.extend(['-boot', 'd']) 549 if disk_type != constants.HT_DISK_IDE: 550 options = '%s,boot=on' % options 551 else: 552 if disk_type == constants.HT_DISK_PARAVIRTUAL: 553 if_val = ',if=virtio' 554 else: 555 if_val = ',if=%s' % disk_type 556 options = '%s%s' % (options, if_val) 557 drive_val = 'file=%s%s' % (iso_image, options) 558 kvm_cmd.extend(['-drive', drive_val]) 559 560 kernel_path = hvp[constants.HV_KERNEL_PATH] 561 if kernel_path: 562 kvm_cmd.extend(['-kernel', kernel_path]) 563 initrd_path = hvp[constants.HV_INITRD_PATH] 564 if initrd_path: 565 kvm_cmd.extend(['-initrd', initrd_path]) 566 root_append = ['root=%s' % hvp[constants.HV_ROOT_PATH], 567 hvp[constants.HV_KERNEL_ARGS]] 568 if hvp[constants.HV_SERIAL_CONSOLE]: 569 root_append.append('console=ttyS0,38400') 570 kvm_cmd.extend(['-append', ' '.join(root_append)]) 571 572 mem_path = hvp[constants.HV_MEM_PATH] 573 if mem_path: 574 kvm_cmd.extend(["-mem-path", mem_path, "-mem-prealloc"]) 575 576 mouse_type = hvp[constants.HV_USB_MOUSE] 577 vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS] 578 579 if mouse_type: 580 kvm_cmd.extend(['-usb']) 581 kvm_cmd.extend(['-usbdevice', mouse_type]) 582 elif vnc_bind_address: 583 kvm_cmd.extend(['-usbdevice', constants.HT_MOUSE_TABLET]) 584 585 if vnc_bind_address: 586 if netutils.IP4Address.IsValid(vnc_bind_address): 587 if instance.network_port > constants.VNC_BASE_PORT: 588 display = instance.network_port - constants.VNC_BASE_PORT 589 if vnc_bind_address == constants.IP4_ADDRESS_ANY: 590 vnc_arg = ':%d' % (display) 591 else: 592 vnc_arg = '%s:%d' % (vnc_bind_address, display) 593 else: 594 logging.error("Network port is not a valid VNC display (%d < %d)." 595 " Not starting VNC", instance.network_port, 596 constants.VNC_BASE_PORT) 597 vnc_arg = 'none' 598 599 # Only allow tls and other option when not binding to a file, for now. 600 # kvm/qemu gets confused otherwise about the filename to use. 601 vnc_append = '' 602 if hvp[constants.HV_VNC_TLS]: 603 vnc_append = '%s,tls' % vnc_append 604 if hvp[constants.HV_VNC_X509_VERIFY]: 605 vnc_append = '%s,x509verify=%s' % (vnc_append, 606 hvp[constants.HV_VNC_X509]) 607 elif hvp[constants.HV_VNC_X509]: 608 vnc_append = '%s,x509=%s' % (vnc_append, 609 hvp[constants.HV_VNC_X509]) 610 if hvp[constants.HV_VNC_PASSWORD_FILE]: 611 vnc_append = '%s,password' % vnc_append 612 613 vnc_arg = '%s%s' % (vnc_arg, vnc_append) 614 615 else: 616 vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name) 617 618 kvm_cmd.extend(['-vnc', vnc_arg]) 619 else: 620 kvm_cmd.extend(['-nographic']) 621 622 monitor_dev = ("unix:%s,server,nowait" % 623 self._InstanceMonitor(instance.name)) 624 kvm_cmd.extend(['-monitor', monitor_dev]) 625 if hvp[constants.HV_SERIAL_CONSOLE]: 626 serial_dev = ('unix:%s,server,nowait' % 627 self._InstanceSerial(instance.name)) 628 kvm_cmd.extend(['-serial', serial_dev]) 629 else: 630 kvm_cmd.extend(['-serial', 'none']) 631 632 if hvp[constants.HV_USE_LOCALTIME]: 633 kvm_cmd.extend(['-localtime']) 634 635 if hvp[constants.HV_KVM_USE_CHROOT]: 636 kvm_cmd.extend(['-chroot', self._InstanceChrootDir(instance.name)]) 637 638 # Save the current instance nics, but defer their expansion as parameters, 639 # as we'll need to generate executable temp files for them. 640 kvm_nics = instance.nics 641 hvparams = hvp 642 643 return (kvm_cmd, kvm_nics, hvparams)
644
645 - def _WriteKVMRuntime(self, instance_name, data):
646 """Write an instance's KVM runtime 647 648 """ 649 try: 650 utils.WriteFile(self._InstanceKVMRuntime(instance_name), 651 data=data) 652 except EnvironmentError, err: 653 raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
654
655 - def _ReadKVMRuntime(self, instance_name):
656 """Read an instance's KVM runtime 657 658 """ 659 try: 660 file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name)) 661 except EnvironmentError, err: 662 raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err) 663 return file_content
664
665 - def _SaveKVMRuntime(self, instance, kvm_runtime):
666 """Save an instance's KVM runtime 667 668 """ 669 kvm_cmd, kvm_nics, hvparams = kvm_runtime 670 serialized_nics = [nic.ToDict() for nic in kvm_nics] 671 serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams)) 672 self._WriteKVMRuntime(instance.name, serialized_form)
673
674 - def _LoadKVMRuntime(self, instance, serialized_runtime=None):
675 """Load an instance's KVM runtime 676 677 """ 678 if not serialized_runtime: 679 serialized_runtime = self._ReadKVMRuntime(instance.name) 680 loaded_runtime = serializer.Load(serialized_runtime) 681 kvm_cmd, serialized_nics, hvparams = loaded_runtime 682 kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics] 683 return (kvm_cmd, kvm_nics, hvparams)
684
685 - def _RunKVMCmd(self, name, kvm_cmd):
686 """Run the KVM cmd and check for errors 687 688 @type name: string 689 @param name: instance name 690 @type kvm_cmd: list of strings 691 @param kvm_cmd: runcmd input for kvm 692 693 """ 694 result = utils.RunCmd(kvm_cmd) 695 if result.failed: 696 raise errors.HypervisorError("Failed to start instance %s: %s (%s)" % 697 (name, result.fail_reason, result.output)) 698 if not self._InstancePidAlive(name)[2]: 699 raise errors.HypervisorError("Failed to start instance %s" % name)
700
701 - def _ExecuteKVMRuntime(self, instance, kvm_runtime, incoming=None):
702 """Execute a KVM cmd, after completing it with some last minute data 703 704 @type incoming: tuple of strings 705 @param incoming: (target_host_ip, port) 706 707 """ 708 # Small _ExecuteKVMRuntime hv parameters programming howto: 709 # - conf_hvp contains the parameters as configured on ganeti. they might 710 # have changed since the instance started; only use them if the change 711 # won't affect the inside of the instance (which hasn't been rebooted). 712 # - up_hvp contains the parameters as they were when the instance was 713 # started, plus any new parameter which has been added between ganeti 714 # versions: it is paramount that those default to a value which won't 715 # affect the inside of the instance as well. 716 conf_hvp = instance.hvparams 717 name = instance.name 718 self._CheckDown(name) 719 720 temp_files = [] 721 722 kvm_cmd, kvm_nics, up_hvp = kvm_runtime 723 up_hvp = objects.FillDict(conf_hvp, up_hvp) 724 725 # We know it's safe to run as a different user upon migration, so we'll use 726 # the latest conf, from conf_hvp. 727 security_model = conf_hvp[constants.HV_SECURITY_MODEL] 728 if security_model == constants.HT_SM_USER: 729 kvm_cmd.extend(["-runas", conf_hvp[constants.HV_SECURITY_DOMAIN]]) 730 731 # We have reasons to believe changing something like the nic driver/type 732 # upon migration won't exactly fly with the instance kernel, so for nic 733 # related parameters we'll use up_hvp 734 if not kvm_nics: 735 kvm_cmd.extend(["-net", "none"]) 736 else: 737 tap_extra = "" 738 nic_type = up_hvp[constants.HV_NIC_TYPE] 739 if nic_type == constants.HT_NIC_PARAVIRTUAL: 740 nic_model = "model=virtio" 741 if up_hvp[constants.HV_VHOST_NET]: 742 tap_extra = ",vhost=on" 743 else: 744 nic_model = "model=%s" % nic_type 745 746 for nic_seq, nic in enumerate(kvm_nics): 747 nic_val = "nic,vlan=%s,macaddr=%s,%s" % (nic_seq, nic.mac, nic_model) 748 script = self._WriteNetScriptFile(instance, nic_seq, nic) 749 tap_val = "tap,vlan=%s,script=%s%s" % (nic_seq, script, tap_extra) 750 kvm_cmd.extend(["-net", nic_val]) 751 kvm_cmd.extend(["-net", tap_val]) 752 temp_files.append(script) 753 754 if incoming: 755 target, port = incoming 756 kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)]) 757 758 # Changing the vnc password doesn't bother the guest that much. At most it 759 # will surprise people who connect to it. Whether positively or negatively 760 # it's debatable. 761 vnc_pwd_file = conf_hvp[constants.HV_VNC_PASSWORD_FILE] 762 vnc_pwd = None 763 if vnc_pwd_file: 764 try: 765 vnc_pwd = utils.ReadOneLineFile(vnc_pwd_file, strict=True) 766 except EnvironmentError, err: 767 raise errors.HypervisorError("Failed to open VNC password file %s: %s" 768 % (vnc_pwd_file, err)) 769 770 if conf_hvp[constants.HV_KVM_USE_CHROOT]: 771 utils.EnsureDirs([(self._InstanceChrootDir(name), 772 constants.SECURE_DIR_MODE)]) 773 774 if security_model == constants.HT_SM_POOL: 775 ss = ssconf.SimpleStore() 776 uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\n") 777 all_uids = set(uidpool.ExpandUidPool(uid_pool)) 778 uid = uidpool.RequestUnusedUid(all_uids) 779 try: 780 username = pwd.getpwuid(uid.GetUid()).pw_name 781 kvm_cmd.extend(["-runas", username]) 782 self._RunKVMCmd(name, kvm_cmd) 783 except: 784 uidpool.ReleaseUid(uid) 785 raise 786 else: 787 uid.Unlock() 788 utils.WriteFile(self._InstanceUidFile(name), data=str(uid)) 789 else: 790 self._RunKVMCmd(name, kvm_cmd) 791 792 if vnc_pwd: 793 change_cmd = 'change vnc password %s' % vnc_pwd 794 self._CallMonitorCommand(instance.name, change_cmd) 795 796 for filename in temp_files: 797 utils.RemoveFile(filename)
798
799 - def StartInstance(self, instance, block_devices):
800 """Start an instance. 801 802 """ 803 self._CheckDown(instance.name) 804 kvm_runtime = self._GenerateKVMRuntime(instance, block_devices) 805 self._SaveKVMRuntime(instance, kvm_runtime) 806 self._ExecuteKVMRuntime(instance, kvm_runtime)
807
808 - def _CallMonitorCommand(self, instance_name, command):
809 """Invoke a command on the instance monitor. 810 811 """ 812 socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" % 813 (utils.ShellQuote(command), 814 constants.SOCAT_PATH, 815 utils.ShellQuote(self._InstanceMonitor(instance_name)))) 816 result = utils.RunCmd(socat) 817 if result.failed: 818 msg = ("Failed to send command '%s' to instance %s." 819 " output: %s, error: %s, fail_reason: %s" % 820 (command, instance_name, 821 result.stdout, result.stderr, result.fail_reason)) 822 raise errors.HypervisorError(msg) 823 824 return result
825 826 @classmethod
827 - def _GetKVMVersion(cls):
828 """Return the installed KVM version 829 830 @return: (version, v_maj, v_min, v_rev), or None 831 832 """ 833 result = utils.RunCmd([constants.KVM_PATH, "--help"]) 834 if result.failed: 835 return None 836 match = cls._VERSION_RE.search(result.output.splitlines()[0]) 837 if not match: 838 return None 839 return (match.group(0), match.group(1), match.group(2), match.group(3))
840
841 - def StopInstance(self, instance, force=False, retry=False, name=None):
842 """Stop an instance. 843 844 """ 845 if name is not None and not force: 846 raise errors.HypervisorError("Cannot shutdown cleanly by name only") 847 if name is None: 848 name = instance.name 849 acpi = instance.hvparams[constants.HV_ACPI] 850 else: 851 acpi = False 852 _, pid, alive = self._InstancePidAlive(name) 853 if pid > 0 and alive: 854 if force or not acpi: 855 utils.KillProcess(pid) 856 else: 857 self._CallMonitorCommand(name, 'system_powerdown')
858
859 - def CleanupInstance(self, instance_name):
860 """Cleanup after a stopped instance 861 862 """ 863 pidfile, pid, alive = self._InstancePidAlive(instance_name) 864 if pid > 0 and alive: 865 raise errors.HypervisorError("Cannot cleanup a live instance") 866 self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
867
868 - def RebootInstance(self, instance):
869 """Reboot an instance. 870 871 """ 872 # For some reason if we do a 'send-key ctrl-alt-delete' to the control 873 # socket the instance will stop, but now power up again. So we'll resort 874 # to shutdown and restart. 875 _, _, alive = self._InstancePidAlive(instance.name) 876 if not alive: 877 raise errors.HypervisorError("Failed to reboot instance %s:" 878 " not running" % instance.name) 879 # StopInstance will delete the saved KVM runtime so: 880 # ...first load it... 881 kvm_runtime = self._LoadKVMRuntime(instance) 882 # ...now we can safely call StopInstance... 883 if not self.StopInstance(instance): 884 self.StopInstance(instance, force=True) 885 # ...and finally we can save it again, and execute it... 886 self._SaveKVMRuntime(instance, kvm_runtime) 887 self._ExecuteKVMRuntime(instance, kvm_runtime)
888
889 - def MigrationInfo(self, instance):
890 """Get instance information to perform a migration. 891 892 @type instance: L{objects.Instance} 893 @param instance: instance to be migrated 894 @rtype: string 895 @return: content of the KVM runtime file 896 897 """ 898 return self._ReadKVMRuntime(instance.name)
899
900 - def AcceptInstance(self, instance, info, target):
901 """Prepare to accept an instance. 902 903 @type instance: L{objects.Instance} 904 @param instance: instance to be accepted 905 @type info: string 906 @param info: content of the KVM runtime file on the source node 907 @type target: string 908 @param target: target host (usually ip), on this node 909 910 """ 911 kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info) 912 incoming_address = (target, instance.hvparams[constants.HV_MIGRATION_PORT]) 913 self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
914
915 - def FinalizeMigration(self, instance, info, success):
916 """Finalize an instance migration. 917 918 Stop the incoming mode KVM. 919 920 @type instance: L{objects.Instance} 921 @param instance: instance whose migration is being finalized 922 923 """ 924 if success: 925 self._WriteKVMRuntime(instance.name, info) 926 else: 927 self.StopInstance(instance, force=True)
928
929 - def MigrateInstance(self, instance, target, live):
930 """Migrate an instance to a target node. 931 932 The migration will not be attempted if the instance is not 933 currently running. 934 935 @type instance: L{objects.Instance} 936 @param instance: the instance to be migrated 937 @type target: string 938 @param target: ip address of the target node 939 @type live: boolean 940 @param live: perform a live migration 941 942 """ 943 instance_name = instance.name 944 port = instance.hvparams[constants.HV_MIGRATION_PORT] 945 pidfile, pid, alive = self._InstancePidAlive(instance_name) 946 if not alive: 947 raise errors.HypervisorError("Instance not running, cannot migrate") 948 949 if not live: 950 self._CallMonitorCommand(instance_name, 'stop') 951 952 migrate_command = ('migrate_set_speed %dm' % 953 instance.hvparams[constants.HV_MIGRATION_BANDWIDTH]) 954 self._CallMonitorCommand(instance_name, migrate_command) 955 956 migrate_command = ('migrate_set_downtime %dms' % 957 instance.hvparams[constants.HV_MIGRATION_DOWNTIME]) 958 self._CallMonitorCommand(instance_name, migrate_command) 959 960 migrate_command = 'migrate -d tcp:%s:%s' % (target, port) 961 self._CallMonitorCommand(instance_name, migrate_command) 962 963 info_command = 'info migrate' 964 done = False 965 broken_answers = 0 966 while not done: 967 result = self._CallMonitorCommand(instance_name, info_command) 968 match = self._MIGRATION_STATUS_RE.search(result.stdout) 969 if not match: 970 broken_answers += 1 971 if not result.stdout: 972 logging.info("KVM: empty 'info migrate' result") 973 else: 974 logging.warning("KVM: unknown 'info migrate' result: %s", 975 result.stdout) 976 time.sleep(self._MIGRATION_INFO_RETRY_DELAY) 977 else: 978 status = match.group(1) 979 if status == 'completed': 980 done = True 981 elif status == 'active': 982 # reset the broken answers count 983 broken_answers = 0 984 time.sleep(self._MIGRATION_INFO_RETRY_DELAY) 985 elif status == 'failed' or status == 'cancelled': 986 if not live: 987 self._CallMonitorCommand(instance_name, 'cont') 988 raise errors.HypervisorError("Migration %s at the kvm level" % 989 status) 990 else: 991 logging.warning("KVM: unknown migration status '%s'", status) 992 broken_answers += 1 993 time.sleep(self._MIGRATION_INFO_RETRY_DELAY) 994 if broken_answers >= self._MIGRATION_INFO_MAX_BAD_ANSWERS: 995 raise errors.HypervisorError("Too many 'info migrate' broken answers") 996 997 utils.KillProcess(pid) 998 self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
999
1000 - def GetNodeInfo(self):
1001 """Return information about the node. 1002 1003 This is just a wrapper over the base GetLinuxNodeInfo method. 1004 1005 @return: a dict with the following keys (values in MiB): 1006 - memory_total: the total memory size on the node 1007 - memory_free: the available memory on the node for instances 1008 - memory_dom0: the memory used by the node itself, if available 1009 1010 """ 1011 return self.GetLinuxNodeInfo()
1012 1013 @classmethod
1014 - def GetShellCommandForConsole(cls, instance, hvparams, beparams):
1015 """Return a command for connecting to the console of an instance. 1016 1017 """ 1018 if hvparams[constants.HV_SERIAL_CONSOLE]: 1019 shell_command = ("%s STDIO,%s UNIX-CONNECT:%s" % 1020 (constants.SOCAT_PATH, cls._SocatUnixConsoleParams(), 1021 utils.ShellQuote(cls._InstanceSerial(instance.name)))) 1022 else: 1023 shell_command = "echo 'No serial shell for instance %s'" % instance.name 1024 1025 vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS] 1026 if vnc_bind_address: 1027 if instance.network_port > constants.VNC_BASE_PORT: 1028 display = instance.network_port - constants.VNC_BASE_PORT 1029 vnc_command = ("echo 'Instance has VNC listening on %s:%d" 1030 " (display: %d)'" % (vnc_bind_address, 1031 instance.network_port, 1032 display)) 1033 shell_command = "%s; %s" % (vnc_command, shell_command) 1034 1035 return shell_command
1036
1037 - def Verify(self):
1038 """Verify the hypervisor. 1039 1040 Check that the binary exists. 1041 1042 """ 1043 if not os.path.exists(constants.KVM_PATH): 1044 return "The kvm binary ('%s') does not exist." % constants.KVM_PATH 1045 if not os.path.exists(constants.SOCAT_PATH): 1046 return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
1047 1048 1049 @classmethod
1050 - def CheckParameterSyntax(cls, hvparams):
1051 """Check the given parameters for validity. 1052 1053 @type hvparams: dict 1054 @param hvparams: dictionary with parameter names/value 1055 @raise errors.HypervisorError: when a parameter is not valid 1056 1057 """ 1058 super(KVMHypervisor, cls).CheckParameterSyntax(hvparams) 1059 1060 kernel_path = hvparams[constants.HV_KERNEL_PATH] 1061 if kernel_path: 1062 if not hvparams[constants.HV_ROOT_PATH]: 1063 raise errors.HypervisorError("Need a root partition for the instance," 1064 " if a kernel is defined") 1065 1066 if (hvparams[constants.HV_VNC_X509_VERIFY] and 1067 not hvparams[constants.HV_VNC_X509]): 1068 raise errors.HypervisorError("%s must be defined, if %s is" % 1069 (constants.HV_VNC_X509, 1070 constants.HV_VNC_X509_VERIFY)) 1071 1072 boot_order = hvparams[constants.HV_BOOT_ORDER] 1073 if (boot_order == constants.HT_BO_CDROM and 1074 not hvparams[constants.HV_CDROM_IMAGE_PATH]): 1075 raise errors.HypervisorError("Cannot boot from cdrom without an" 1076 " ISO path") 1077 1078 security_model = hvparams[constants.HV_SECURITY_MODEL] 1079 if security_model == constants.HT_SM_USER: 1080 if not hvparams[constants.HV_SECURITY_DOMAIN]: 1081 raise errors.HypervisorError("A security domain (user to run kvm as)" 1082 " must be specified") 1083 elif (security_model == constants.HT_SM_NONE or 1084 security_model == constants.HT_SM_POOL): 1085 if hvparams[constants.HV_SECURITY_DOMAIN]: 1086 raise errors.HypervisorError("Cannot have a security domain when the" 1087 " security model is 'none' or 'pool'")
1088 1089 @classmethod
1090 - def ValidateParameters(cls, hvparams):
1091 """Check the given parameters for validity. 1092 1093 @type hvparams: dict 1094 @param hvparams: dictionary with parameter names/value 1095 @raise errors.HypervisorError: when a parameter is not valid 1096 1097 """ 1098 super(KVMHypervisor, cls).ValidateParameters(hvparams) 1099 1100 security_model = hvparams[constants.HV_SECURITY_MODEL] 1101 if security_model == constants.HT_SM_USER: 1102 username = hvparams[constants.HV_SECURITY_DOMAIN] 1103 try: 1104 pwd.getpwnam(username) 1105 except KeyError: 1106 raise errors.HypervisorError("Unknown security domain user %s" 1107 % username)
1108 1109 @classmethod
1110 - def PowercycleNode(cls):
1111 """KVM powercycle, just a wrapper over Linux powercycle. 1112 1113 """ 1114 cls.LinuxPowercycle()
1115