hv_kvm.py 36.8 KB
Newer Older
Guido Trotter's avatar
Guido Trotter committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
#
#

# Copyright (C) 2008 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.


"""KVM hypervisor

"""

Balazs Lecz's avatar
Balazs Lecz committed
26
import errno
Guido Trotter's avatar
Guido Trotter committed
27 28 29 30
import os
import os.path
import re
import tempfile
31
import time
Guido Trotter's avatar
Guido Trotter committed
32
import logging
33
import pwd
Guido Trotter's avatar
Guido Trotter committed
34 35 36 37 38
from cStringIO import StringIO

from ganeti import utils
from ganeti import constants
from ganeti import errors
39 40
from ganeti import serializer
from ganeti import objects
41 42
from ganeti import uidpool
from ganeti import ssconf
Guido Trotter's avatar
Guido Trotter committed
43 44 45 46
from ganeti.hypervisor import hv_base


class KVMHypervisor(hv_base.BaseHypervisor):
Guido Trotter's avatar
Guido Trotter committed
47
  """KVM hypervisor interface"""
48
  CAN_MIGRATE = True
Guido Trotter's avatar
Guido Trotter committed
49 50

  _ROOT_DIR = constants.RUN_GANETI_DIR + "/kvm-hypervisor"
Guido Trotter's avatar
Guido Trotter committed
51
  _PIDS_DIR = _ROOT_DIR + "/pid" # contains live instances pids
52
  _UIDS_DIR = _ROOT_DIR + "/uid" # contains instances reserved uids
Guido Trotter's avatar
Guido Trotter committed
53 54
  _CTRL_DIR = _ROOT_DIR + "/ctrl" # contains instances control sockets
  _CONF_DIR = _ROOT_DIR + "/conf" # contains instances startup data
Balazs Lecz's avatar
Balazs Lecz committed
55 56 57 58 59 60 61 62 63 64
  # KVM instances with chroot enabled are started in empty chroot directories.
  _CHROOT_DIR = _ROOT_DIR + "/chroot" # for empty chroot directories
  # After an instance is stopped, its chroot directory is removed.
  # If the chroot directory is not empty, it can't be removed.
  # A non-empty chroot directory indicates a possible security incident.
  # To support forensics, the non-empty chroot directory is quarantined in
  # a separate directory, called 'chroot-quarantine'.
  _CHROOT_QUARANTINE_DIR = _ROOT_DIR + "/chroot-quarantine"
  _DIRS = [_ROOT_DIR, _PIDS_DIR, _UIDS_DIR, _CTRL_DIR, _CONF_DIR,
           _CHROOT_DIR, _CHROOT_QUARANTINE_DIR]
Guido Trotter's avatar
Guido Trotter committed
65

66 67 68 69 70 71 72
  PARAMETERS = {
    constants.HV_KERNEL_PATH: hv_base.OPT_FILE_CHECK,
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
    constants.HV_ACPI: hv_base.NO_CHECK,
    constants.HV_SERIAL_CONSOLE: hv_base.NO_CHECK,
Michael Hanselmann's avatar
Michael Hanselmann committed
73 74 75 76
    constants.HV_VNC_BIND_ADDRESS:
      (False, lambda x: (utils.IsValidIP(x) or utils.IsNormAbsPath(x)),
       "the VNC bind address must be either a valid IP address or an absolute"
       " pathname", None, None),
77 78 79
    constants.HV_VNC_TLS: hv_base.NO_CHECK,
    constants.HV_VNC_X509: hv_base.OPT_DIR_CHECK,
    constants.HV_VNC_X509_VERIFY: hv_base.NO_CHECK,
80
    constants.HV_VNC_PASSWORD_FILE: hv_base.OPT_FILE_CHECK,
81
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
Michael Hanselmann's avatar
Michael Hanselmann committed
82 83 84 85 86 87 88 89
    constants.HV_BOOT_ORDER:
      hv_base.ParamInSet(True, constants.HT_KVM_VALID_BO_TYPES),
    constants.HV_NIC_TYPE:
      hv_base.ParamInSet(True, constants.HT_KVM_VALID_NIC_TYPES),
    constants.HV_DISK_TYPE:
      hv_base.ParamInSet(True, constants.HT_KVM_VALID_DISK_TYPES),
    constants.HV_USB_MOUSE:
      hv_base.ParamInSet(False, constants.HT_KVM_VALID_MOUSE_TYPES),
90
    constants.HV_MIGRATION_PORT: hv_base.NET_PORT_CHECK,
91 92
    constants.HV_MIGRATION_BANDWIDTH: hv_base.NO_CHECK,
    constants.HV_MIGRATION_DOWNTIME: hv_base.NO_CHECK,
93
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
94 95
    constants.HV_DISK_CACHE:
      hv_base.ParamInSet(True, constants.HT_VALID_CACHE_TYPES),
96 97 98
    constants.HV_SECURITY_MODEL:
      hv_base.ParamInSet(True, constants.HT_KVM_VALID_SM_TYPES),
    constants.HV_SECURITY_DOMAIN: hv_base.NO_CHECK,
Guido Trotter's avatar
Guido Trotter committed
99 100
    constants.HV_KVM_FLAG:
      hv_base.ParamInSet(False, constants.HT_KVM_FLAG_VALUES),
101
    constants.HV_VHOST_NET: hv_base.NO_CHECK,
Balazs Lecz's avatar
Balazs Lecz committed
102
    constants.HV_KVM_USE_CHROOT: hv_base.NO_CHECK,
103
    }
104

Guido Trotter's avatar
Guido Trotter committed
105 106
  _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)',
                                    re.M | re.I)
107 108
  _MIGRATION_INFO_MAX_BAD_ANSWERS = 5
  _MIGRATION_INFO_RETRY_DELAY = 2
Guido Trotter's avatar
Guido Trotter committed
109

110 111
  _KVM_NETWORK_SCRIPT = constants.SYSCONFDIR + "/ganeti/kvm-vif-bridge"

112 113 114 115
  ANCILLARY_FILES = [
    _KVM_NETWORK_SCRIPT,
    ]

Guido Trotter's avatar
Guido Trotter committed
116 117 118 119
  def __init__(self):
    hv_base.BaseHypervisor.__init__(self)
    # Let's make sure the directories we need exist, even if the RUN_DIR lives
    # in a tmpfs filesystem or has been otherwise wiped out.
Iustin Pop's avatar
Iustin Pop committed
120
    dirs = [(dname, constants.RUN_DIRS_MODE) for dname in self._DIRS]
Guido Trotter's avatar
Guido Trotter committed
121
    utils.EnsureDirs(dirs)
Guido Trotter's avatar
Guido Trotter committed
122

123 124
  @classmethod
  def _InstancePidFile(cls, instance_name):
125 126 127
    """Returns the instance pidfile.

    """
128
    return utils.PathJoin(cls._PIDS_DIR, instance_name)
129

130 131 132 133 134 135 136
  @classmethod
  def _InstanceUidFile(cls, instance_name):
    """Returns the instance uidfile.

    """
    return utils.PathJoin(cls._UIDS_DIR, instance_name)

137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
  @classmethod
  def _InstancePidInfo(cls, pid):
    """Check pid file for instance information.

    Check that a pid file is associated with an instance, and retrieve
    information from its command line.

    @type pid: string or int
    @param pid: process id of the instance to check
    @rtype: tuple
    @return: (instance_name, memory, vcpus)
    @raise errors.HypervisorError: when an instance cannot be found

    """
    alive = utils.IsProcessAlive(pid)
    if not alive:
      raise errors.HypervisorError("Cannot get info for pid %s" % pid)

    cmdline_file = utils.PathJoin("/proc", str(pid), "cmdline")
    try:
      cmdline = utils.ReadFile(cmdline_file)
    except EnvironmentError, err:
      raise errors.HypervisorError("Can't open cmdline file for pid %s: %s" %
                                   (pid, err))

    instance = None
    memory = 0
    vcpus = 0

    arg_list = cmdline.split('\x00')
    while arg_list:
      arg =  arg_list.pop(0)
      if arg == "-name":
        instance = arg_list.pop(0)
      elif arg == "-m":
        memory = int(arg_list.pop(0))
      elif arg == "-smp":
        vcpus = int(arg_list.pop(0))

    if instance is None:
      raise errors.HypervisorError("Pid %s doesn't contain a ganeti kvm"
                                   " instance" % pid)

    return (instance, memory, vcpus)

182
  def _InstancePidAlive(self, instance_name):
183 184 185 186 187 188
    """Returns the instance pidfile, pid, and liveness.

    @type instance_name: string
    @param instance_name: instance name
    @rtype: tuple
    @return: (pid file name, pid, liveness)
189 190

    """
191
    pidfile = self._InstancePidFile(instance_name)
192
    pid = utils.ReadPidFile(pidfile)
193 194 195 196 197 198 199

    alive = False
    try:
      cmd_instance = self._InstancePidInfo(pid)[0]
      alive = (cmd_instance == instance_name)
    except errors.HypervisorError:
      pass
200 201 202

    return (pidfile, pid, alive)

203 204 205 206 207 208 209 210 211
  def _CheckDown(self, instance_name):
    """Raises an error unless the given instance is down.

    """
    alive = self._InstancePidAlive(instance_name)[2]
    if alive:
      raise errors.HypervisorError("Failed to start instance %s: %s" %
                                   (instance_name, "already running"))

212 213
  @classmethod
  def _InstanceMonitor(cls, instance_name):
214 215 216
    """Returns the instance monitor socket name

    """
217
    return utils.PathJoin(cls._CTRL_DIR, "%s.monitor" % instance_name)
218

219 220
  @classmethod
  def _InstanceSerial(cls, instance_name):
221 222 223
    """Returns the instance serial socket name

    """
224
    return utils.PathJoin(cls._CTRL_DIR, "%s.serial" % instance_name)
225

226 227 228 229 230 231 232
  @staticmethod
  def _SocatUnixConsoleParams():
    """Returns the correct parameters for socat

    If we have a new-enough socat we can use raw mode with an escape character.

    """
233
    if constants.SOCAT_USE_ESCAPE:
234 235 236 237
      return "raw,echo=0,escape=%s" % constants.SOCAT_ESCAPE_CODE
    else:
      return "echo=0,icanon=0"

238 239
  @classmethod
  def _InstanceKVMRuntime(cls, instance_name):
240 241 242
    """Returns the instance KVM runtime filename

    """
243
    return utils.PathJoin(cls._CONF_DIR, "%s.runtime" % instance_name)
244

Balazs Lecz's avatar
Balazs Lecz committed
245 246 247 248 249 250 251
  @classmethod
  def _InstanceChrootDir(cls, instance_name):
    """Returns the name of the KVM chroot dir of the instance

    """
    return utils.PathJoin(cls._CHROOT_DIR, instance_name)

252 253 254 255 256 257 258
  @classmethod
  def _TryReadUidFile(cls, uid_file):
    """Try to read a uid file

    """
    if os.path.exists(uid_file):
      try:
259
        uid = int(utils.ReadOneLineFile(uid_file))
260
        return uid
261 262 263 264
      except EnvironmentError:
        logging.warning("Can't read uid file", exc_info=True)
      except (TypeError, ValueError):
        logging.warning("Can't parse uid file contents", exc_info=True)
265
    return None
266

267 268
  @classmethod
  def _RemoveInstanceRuntimeFiles(cls, pidfile, instance_name):
Balazs Lecz's avatar
Balazs Lecz committed
269
    """Removes an instance's rutime sockets/files/dirs.
270 271 272 273 274 275

    """
    utils.RemoveFile(pidfile)
    utils.RemoveFile(cls._InstanceMonitor(instance_name))
    utils.RemoveFile(cls._InstanceSerial(instance_name))
    utils.RemoveFile(cls._InstanceKVMRuntime(instance_name))
276 277 278 279 280
    uid_file = cls._InstanceUidFile(instance_name)
    uid = cls._TryReadUidFile(uid_file)
    utils.RemoveFile(uid_file)
    if uid is not None:
      uidpool.ReleaseUid(uid)
Balazs Lecz's avatar
Balazs Lecz committed
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
    try:
      chroot_dir = cls._InstanceChrootDir(instance_name)
      utils.RemoveDir(chroot_dir)
    except OSError, err:
      if err.errno == errno.ENOTEMPTY:
        # The chroot directory is expected to be empty, but it isn't.
        new_chroot_dir = tempfile.mkdtemp(dir=cls._CHROOT_QUARANTINE_DIR,
                                          prefix="%s-%s-" %
                                          (instance_name,
                                           utils.TimestampForFilename()))
        logging.warning("The chroot directory of instance %s can not be"
                        " removed as it is not empty. Moving it to the"
                        " quarantine instead. Please investigate the"
                        " contents (%s) and clean up manually",
                        instance_name, new_chroot_dir)
        utils.RenameFile(chroot_dir, new_chroot_dir)
      else:
        raise
299

Guido Trotter's avatar
Guido Trotter committed
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
  def _WriteNetScript(self, instance, seq, nic):
    """Write a script to connect a net interface to the proper bridge.

    This can be used by any qemu-type hypervisor.

    @param instance: instance we're acting on
    @type instance: instance object
    @param seq: nic sequence number
    @type seq: int
    @param nic: nic we're acting on
    @type nic: nic object
    @return: netscript file name
    @rtype: string

    """
    script = StringIO()
    script.write("#!/bin/sh\n")
    script.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
318
    script.write("PATH=$PATH:/sbin:/usr/sbin\n")
Guido Trotter's avatar
Guido Trotter committed
319 320
    script.write("export INSTANCE=%s\n" % instance.name)
    script.write("export MAC=%s\n" % nic.mac)
Guido Trotter's avatar
Guido Trotter committed
321 322 323 324 325 326 327
    if nic.ip:
      script.write("export IP=%s\n" % nic.ip)
    script.write("export MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
    if nic.nicparams[constants.NIC_LINK]:
      script.write("export LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
    if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
      script.write("export BRIDGE=%s\n" % nic.nicparams[constants.NIC_LINK])
Guido Trotter's avatar
Guido Trotter committed
328
    script.write("export INTERFACE=$1\n")
329 330
    if instance.tags:
      script.write("export TAGS=\"%s\"\n" % " ".join(instance.tags))
Guido Trotter's avatar
Guido Trotter committed
331
    # TODO: make this configurable at ./configure time
332
    script.write("if [ -x '%s' ]; then\n" % self._KVM_NETWORK_SCRIPT)
Guido Trotter's avatar
Guido Trotter committed
333
    script.write("  # Execute the user-specific vif file\n")
334
    script.write("  %s\n" % self._KVM_NETWORK_SCRIPT)
Guido Trotter's avatar
Guido Trotter committed
335
    script.write("else\n")
336
    script.write("  ifconfig $INTERFACE 0.0.0.0 up\n")
Guido Trotter's avatar
Guido Trotter committed
337 338
    if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
      script.write("  # Connect the interface to the bridge\n")
339
      script.write("  brctl addif $BRIDGE $INTERFACE\n")
Guido Trotter's avatar
Guido Trotter committed
340
    elif nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_ROUTED:
341 342
      if not nic.ip:
        raise errors.HypervisorError("nic/%d is routed, but has no ip." % seq)
Guido Trotter's avatar
Guido Trotter committed
343
      script.write("  # Route traffic targeted at the IP to the interface\n")
344
      if nic.nicparams[constants.NIC_LINK]:
345 346 347
        script.write("  while ip rule del dev $INTERFACE; do :; done\n")
        script.write("  ip rule add dev $INTERFACE table $LINK\n")
        script.write("  ip route replace $IP table $LINK proto static"
Guido Trotter's avatar
Guido Trotter committed
348
                     " dev $INTERFACE\n")
349
      else:
350
        script.write("  ip route replace $IP proto static"
351
                     " dev $INTERFACE\n")
352 353 354 355 356 357 358 359 360 361
      interface_v4_conf = "/proc/sys/net/ipv4/conf/$INTERFACE"
      interface_v6_conf = "/proc/sys/net/ipv6/conf/$INTERFACE"
      script.write("  if [ -d %s ]; then\n" % interface_v4_conf)
      script.write("    echo 1 > %s/proxy_arp\n" % interface_v4_conf)
      script.write("    echo 1 > %s/forwarding\n" % interface_v4_conf)
      script.write("  fi\n")
      script.write("  if [ -d %s ]; then\n" % interface_v6_conf)
      script.write("    echo 1 > %s/proxy_ndp\n" % interface_v6_conf)
      script.write("    echo 1 > %s/forwarding\n" % interface_v6_conf)
      script.write("  fi\n")
Guido Trotter's avatar
Guido Trotter committed
362 363 364 365 366
    script.write("fi\n\n")
    # As much as we'd like to put this in our _ROOT_DIR, that will happen to be
    # mounted noexec sometimes, so we'll have to find another place.
    (tmpfd, tmpfile_name) = tempfile.mkstemp()
    tmpfile = os.fdopen(tmpfd, 'w')
Michael Hanselmann's avatar
Michael Hanselmann committed
367 368 369 370
    try:
      tmpfile.write(script.getvalue())
    finally:
      tmpfile.close()
Guido Trotter's avatar
Guido Trotter committed
371 372 373 374 375 376
    os.chmod(tmpfile_name, 0755)
    return tmpfile_name

  def ListInstances(self):
    """Get the list of running instances.

Iustin Pop's avatar
Iustin Pop committed
377 378
    We can do this by listing our live instances directory and
    checking whether the associated kvm process is still alive.
Guido Trotter's avatar
Guido Trotter committed
379 380 381 382

    """
    result = []
    for name in os.listdir(self._PIDS_DIR):
383
      if self._InstancePidAlive(name)[2]:
Guido Trotter's avatar
Guido Trotter committed
384 385 386 387 388 389
        result.append(name)
    return result

  def GetInstanceInfo(self, instance_name):
    """Get instance properties.

390
    @type instance_name: string
Iustin Pop's avatar
Iustin Pop committed
391
    @param instance_name: the instance name
392 393
    @rtype: tuple of strings
    @return: (name, id, memory, vcpus, stat, times)
Guido Trotter's avatar
Guido Trotter committed
394 395

    """
396
    _, pid, alive = self._InstancePidAlive(instance_name)
397
    if not alive:
Guido Trotter's avatar
Guido Trotter committed
398 399
      return None

400
    _, memory, vcpus = self._InstancePidInfo(pid)
Guido Trotter's avatar
Guido Trotter committed
401 402 403 404 405 406 407 408
    stat = "---b-"
    times = "0"

    return (instance_name, pid, memory, vcpus, stat, times)

  def GetAllInstancesInfo(self):
    """Get properties of all instances.

Iustin Pop's avatar
Iustin Pop committed
409 410
    @return: list of tuples (name, id, memory, vcpus, stat, times)

Guido Trotter's avatar
Guido Trotter committed
411 412 413
    """
    data = []
    for name in os.listdir(self._PIDS_DIR):
414 415 416 417 418 419
      try:
        info = self.GetInstanceInfo(name)
      except errors.HypervisorError:
        continue
      if info:
        data.append(info)
Guido Trotter's avatar
Guido Trotter committed
420 421
    return data

422
  def _GenerateKVMRuntime(self, instance, block_devices):
423
    """Generate KVM information to start an instance.
Guido Trotter's avatar
Guido Trotter committed
424 425

    """
426
    pidfile  = self._InstancePidFile(instance.name)
Guido Trotter's avatar
Guido Trotter committed
427 428
    kvm = constants.KVM_PATH
    kvm_cmd = [kvm]
429 430
    # used just by the vnc server, if enabled
    kvm_cmd.extend(['-name', instance.name])
431 432
    kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
    kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
Guido Trotter's avatar
Guido Trotter committed
433 434
    kvm_cmd.extend(['-pidfile', pidfile])
    kvm_cmd.extend(['-daemonize'])
435
    if not instance.hvparams[constants.HV_ACPI]:
Guido Trotter's avatar
Guido Trotter committed
436 437
      kvm_cmd.extend(['-no-acpi'])

438
    hvp = instance.hvparams
439 440 441
    boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
    boot_cdrom = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_CDROM
    boot_network = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_NETWORK
Guido Trotter's avatar
Guido Trotter committed
442

Guido Trotter's avatar
Guido Trotter committed
443 444 445 446 447
    if hvp[constants.HV_KVM_FLAG] == constants.HT_KVM_ENABLED:
      kvm_cmd.extend(["-enable-kvm"])
    elif hvp[constants.HV_KVM_FLAG] == constants.HT_KVM_DISABLED:
      kvm_cmd.extend(["-disable-kvm"])

Guido Trotter's avatar
Guido Trotter committed
448 449
    if boot_network:
      kvm_cmd.extend(['-boot', 'n'])
450

451
    disk_type = hvp[constants.HV_DISK_TYPE]
452 453 454 455
    if disk_type == constants.HT_DISK_PARAVIRTUAL:
      if_val = ',if=virtio'
    else:
      if_val = ',if=%s' % disk_type
456 457 458 459 460 461
    # Cache mode
    disk_cache = hvp[constants.HV_DISK_CACHE]
    if disk_cache != constants.HT_CACHE_DEFAULT:
      cache_val = ",cache=%s" % disk_cache
    else:
      cache_val = ""
462
    for cfdev, dev_path in block_devices:
463 464 465
      if cfdev.mode != constants.DISK_RDWR:
        raise errors.HypervisorError("Instance has read-only disks which"
                                     " are not supported by KVM")
Guido Trotter's avatar
Guido Trotter committed
466
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
467
      if boot_disk:
468
        kvm_cmd.extend(['-boot', 'c'])
469
        if disk_type != constants.HT_DISK_IDE:
Guido Trotter's avatar
Guido Trotter committed
470
          boot_val = ',boot=on'
471
        else:
Guido Trotter's avatar
Guido Trotter committed
472
          boot_val = ''
Guido Trotter's avatar
Guido Trotter committed
473
        # We only boot from the first disk
474
        boot_disk = False
Guido Trotter's avatar
Guido Trotter committed
475 476 477
      else:
        boot_val = ''

478 479
      drive_val = 'file=%s,format=raw%s%s%s' % (dev_path, if_val, boot_val,
                                                cache_val)
Guido Trotter's avatar
Guido Trotter committed
480 481
      kvm_cmd.extend(['-drive', drive_val])

482
    iso_image = hvp[constants.HV_CDROM_IMAGE_PATH]
483
    if iso_image:
484
      options = ',format=raw,media=cdrom'
485
      if boot_cdrom:
486
        kvm_cmd.extend(['-boot', 'd'])
487
        if disk_type != constants.HT_DISK_IDE:
Guido Trotter's avatar
Guido Trotter committed
488
          options = '%s,boot=on' % options
489
      else:
490 491 492 493 494
        if disk_type == constants.HT_DISK_PARAVIRTUAL:
          if_val = ',if=virtio'
        else:
          if_val = ',if=%s' % disk_type
        options = '%s%s' % (options, if_val)
495 496 497
      drive_val = 'file=%s%s' % (iso_image, options)
      kvm_cmd.extend(['-drive', drive_val])

498
    kernel_path = hvp[constants.HV_KERNEL_PATH]
499 500
    if kernel_path:
      kvm_cmd.extend(['-kernel', kernel_path])
501
      initrd_path = hvp[constants.HV_INITRD_PATH]
502 503
      if initrd_path:
        kvm_cmd.extend(['-initrd', initrd_path])
504 505 506 507 508
      root_append = ['root=%s' % hvp[constants.HV_ROOT_PATH],
                     hvp[constants.HV_KERNEL_ARGS]]
      if hvp[constants.HV_SERIAL_CONSOLE]:
        root_append.append('console=ttyS0,38400')
      kvm_cmd.extend(['-append', ' '.join(root_append)])
Guido Trotter's avatar
Guido Trotter committed
509

510
    mouse_type = hvp[constants.HV_USB_MOUSE]
511 512 513 514
    if mouse_type:
      kvm_cmd.extend(['-usb'])
      kvm_cmd.extend(['-usbdevice', mouse_type])

515
    vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS]
516
    if vnc_bind_address:
517
      if utils.IsValidIP(vnc_bind_address):
518 519
        if instance.network_port > constants.VNC_BASE_PORT:
          display = instance.network_port - constants.VNC_BASE_PORT
520 521 522
          if vnc_bind_address == '0.0.0.0':
            vnc_arg = ':%d' % (display)
          else:
523
            vnc_arg = '%s:%d' % (vnc_bind_address, display)
524
        else:
525
          logging.error("Network port is not a valid VNC display (%d < %d)."
Iustin Pop's avatar
Iustin Pop committed
526 527
                        " Not starting VNC", instance.network_port,
                        constants.VNC_BASE_PORT)
528
          vnc_arg = 'none'
529 530 531 532

        # Only allow tls and other option when not binding to a file, for now.
        # kvm/qemu gets confused otherwise about the filename to use.
        vnc_append = ''
533
        if hvp[constants.HV_VNC_TLS]:
534
          vnc_append = '%s,tls' % vnc_append
535
          if hvp[constants.HV_VNC_X509_VERIFY]:
536
            vnc_append = '%s,x509verify=%s' % (vnc_append,
537 538
                                               hvp[constants.HV_VNC_X509])
          elif hvp[constants.HV_VNC_X509]:
539
            vnc_append = '%s,x509=%s' % (vnc_append,
540
                                         hvp[constants.HV_VNC_X509])
541 542 543
        if hvp[constants.HV_VNC_PASSWORD_FILE]:
          vnc_append = '%s,password' % vnc_append

544 545
        vnc_arg = '%s%s' % (vnc_arg, vnc_append)

546
      else:
547 548
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)

549
      kvm_cmd.extend(['-vnc', vnc_arg])
550 551 552
    else:
      kvm_cmd.extend(['-nographic'])

553 554
    monitor_dev = ("unix:%s,server,nowait" %
                   self._InstanceMonitor(instance.name))
Guido Trotter's avatar
Guido Trotter committed
555
    kvm_cmd.extend(['-monitor', monitor_dev])
556 557 558
    if hvp[constants.HV_SERIAL_CONSOLE]:
      serial_dev = ('unix:%s,server,nowait' %
                    self._InstanceSerial(instance.name))
559 560 561
      kvm_cmd.extend(['-serial', serial_dev])
    else:
      kvm_cmd.extend(['-serial', 'none'])
Guido Trotter's avatar
Guido Trotter committed
562

563 564 565
    if hvp[constants.HV_USE_LOCALTIME]:
      kvm_cmd.extend(['-localtime'])

Balazs Lecz's avatar
Balazs Lecz committed
566 567 568
    if hvp[constants.HV_KVM_USE_CHROOT]:
      kvm_cmd.extend(['-chroot', self._InstanceChrootDir(instance.name)])

569 570 571
    # Save the current instance nics, but defer their expansion as parameters,
    # as we'll need to generate executable temp files for them.
    kvm_nics = instance.nics
572
    hvparams = hvp
573

574
    return (kvm_cmd, kvm_nics, hvparams)
575

576 577 578 579 580 581 582
  def _WriteKVMRuntime(self, instance_name, data):
    """Write an instance's KVM runtime

    """
    try:
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
                      data=data)
583
    except EnvironmentError, err:
584 585 586 587 588 589 590 591
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)

  def _ReadKVMRuntime(self, instance_name):
    """Read an instance's KVM runtime

    """
    try:
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
592
    except EnvironmentError, err:
593 594 595 596 597 598 599
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
    return file_content

  def _SaveKVMRuntime(self, instance, kvm_runtime):
    """Save an instance's KVM runtime

    """
600
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
601
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
602
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
603 604
    self._WriteKVMRuntime(instance.name, serialized_form)

Guido Trotter's avatar
Guido Trotter committed
605
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
606 607 608
    """Load an instance's KVM runtime

    """
Guido Trotter's avatar
Guido Trotter committed
609 610 611
    if not serialized_runtime:
      serialized_runtime = self._ReadKVMRuntime(instance.name)
    loaded_runtime = serializer.Load(serialized_runtime)
612
    kvm_cmd, serialized_nics, hvparams = loaded_runtime
613
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
614
    return (kvm_cmd, kvm_nics, hvparams)
615

616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631
  def _RunKVMCmd(self, name, kvm_cmd):
    """Run the KVM cmd and check for errors

    @type name: string
    @param name: instance name
    @type kvm_cmd: list of strings
    @param kvm_cmd: runcmd input for kvm

    """
    result = utils.RunCmd(kvm_cmd)
    if result.failed:
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
                                   (name, result.fail_reason, result.output))
    if not self._InstancePidAlive(name)[2]:
      raise errors.HypervisorError("Failed to start instance %s" % name)

Guido Trotter's avatar
Guido Trotter committed
632
  def _ExecuteKVMRuntime(self, instance, kvm_runtime, incoming=None):
633 634
    """Execute a KVM cmd, after completing it with some last minute data

Guido Trotter's avatar
Guido Trotter committed
635 636 637
    @type incoming: tuple of strings
    @param incoming: (target_host_ip, port)

638
    """
639 640 641 642 643 644 645 646 647
    # Small _ExecuteKVMRuntime hv parameters programming howto:
    #  - conf_hvp contains the parameters as configured on ganeti. they might
    #    have changed since the instance started; only use them if the change
    #    won't affect the inside of the instance (which hasn't been rebooted).
    #  - up_hvp contains the parameters as they were when the instance was
    #    started, plus any new parameter which has been added between ganeti
    #    versions: it is paramount that those default to a value which won't
    #    affect the inside of the instance as well.
    conf_hvp = instance.hvparams
648 649
    name = instance.name
    self._CheckDown(name)
650 651 652

    temp_files = []

653 654
    kvm_cmd, kvm_nics, up_hvp = kvm_runtime
    up_hvp = objects.FillDict(conf_hvp, up_hvp)
655

656 657 658
    # We know it's safe to run as a different user upon migration, so we'll use
    # the latest conf, from conf_hvp.
    security_model = conf_hvp[constants.HV_SECURITY_MODEL]
659
    if security_model == constants.HT_SM_USER:
660
      kvm_cmd.extend(["-runas", conf_hvp[constants.HV_SECURITY_DOMAIN]])
661

662 663 664
    # We have reasons to believe changing something like the nic driver/type
    # upon migration won't exactly fly with the instance kernel, so for nic
    # related parameters we'll use up_hvp
665
    if not kvm_nics:
666
      kvm_cmd.extend(["-net", "none"])
667
    else:
668
      tap_extra = ""
669
      nic_type = up_hvp[constants.HV_NIC_TYPE]
670 671
      if nic_type == constants.HT_NIC_PARAVIRTUAL:
        nic_model = "model=virtio"
672
        if up_hvp[constants.HV_VHOST_NET]:
673
          tap_extra = ",vhost=on"
674 675 676
      else:
        nic_model = "model=%s" % nic_type

677
      for nic_seq, nic in enumerate(kvm_nics):
678
        nic_val = "nic,vlan=%s,macaddr=%s,%s" % (nic_seq, nic.mac, nic_model)
679
        script = self._WriteNetScript(instance, nic_seq, nic)
680 681 682
        tap_val = "tap,vlan=%s,script=%s%s" % (nic_seq, script, tap_extra)
        kvm_cmd.extend(["-net", nic_val])
        kvm_cmd.extend(["-net", tap_val])
683 684
        temp_files.append(script)

Guido Trotter's avatar
Guido Trotter committed
685 686 687 688
    if incoming:
      target, port = incoming
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])

689 690 691 692
    # Changing the vnc password doesn't bother the guest that much. At most it
    # will surprise people who connect to it. Whether positively or negatively
    # it's debatable.
    vnc_pwd_file = conf_hvp[constants.HV_VNC_PASSWORD_FILE]
693 694 695
    vnc_pwd = None
    if vnc_pwd_file:
      try:
696
        vnc_pwd = utils.ReadOneLineFile(vnc_pwd_file, strict=True)
697 698 699 700
      except EnvironmentError, err:
        raise errors.HypervisorError("Failed to open VNC password file %s: %s"
                                     % (vnc_pwd_file, err))

701
    if conf_hvp[constants.HV_KVM_USE_CHROOT]:
Balazs Lecz's avatar
Balazs Lecz committed
702 703 704
      utils.EnsureDirs([(self._InstanceChrootDir(name),
                         constants.SECURE_DIR_MODE)])

705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721
    if security_model == constants.HT_SM_POOL:
      ss = ssconf.SimpleStore()
      uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\n")
      all_uids = set(uidpool.ExpandUidPool(uid_pool))
      uid = uidpool.RequestUnusedUid(all_uids)
      try:
        username = pwd.getpwuid(uid.GetUid()).pw_name
        kvm_cmd.extend(["-runas", username])
        self._RunKVMCmd(name, kvm_cmd)
      except:
        uidpool.ReleaseUid(uid)
        raise
      else:
        uid.Unlock()
        utils.WriteFile(self._InstanceUidFile(name), data=str(uid))
    else:
      self._RunKVMCmd(name, kvm_cmd)
Guido Trotter's avatar
Guido Trotter committed
722

723 724 725 726
    if vnc_pwd:
      change_cmd = 'change vnc password %s' % vnc_pwd
      self._CallMonitorCommand(instance.name, change_cmd)

727 728
    for filename in temp_files:
      utils.RemoveFile(filename)
Guido Trotter's avatar
Guido Trotter committed
729

730
  def StartInstance(self, instance, block_devices):
731 732 733
    """Start an instance.

    """
734
    self._CheckDown(instance.name)
735
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices)
736
    self._SaveKVMRuntime(instance, kvm_runtime)
737 738
    self._ExecuteKVMRuntime(instance, kvm_runtime)

739 740 741 742 743 744 745 746 747 748 749 750
  def _CallMonitorCommand(self, instance_name, command):
    """Invoke a command on the instance monitor.

    """
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
             (utils.ShellQuote(command),
              constants.SOCAT_PATH,
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
    result = utils.RunCmd(socat)
    if result.failed:
      msg = ("Failed to send command '%s' to instance %s."
             " output: %s, error: %s, fail_reason: %s" %
751 752
             (command, instance_name,
              result.stdout, result.stderr, result.fail_reason))
753 754 755 756
      raise errors.HypervisorError(msg)

    return result

757
  def StopInstance(self, instance, force=False, retry=False, name=None):
Guido Trotter's avatar
Guido Trotter committed
758 759 760
    """Stop an instance.

    """
761 762 763 764 765 766 767
    if name is not None and not force:
      raise errors.HypervisorError("Cannot shutdown cleanly by name only")
    if name is None:
      name = instance.name
      acpi = instance.hvparams[constants.HV_ACPI]
    else:
      acpi = False
768
    _, pid, alive = self._InstancePidAlive(name)
769
    if pid > 0 and alive:
770
      if force or not acpi:
Guido Trotter's avatar
Guido Trotter committed
771 772
        utils.KillProcess(pid)
      else:
773
        self._CallMonitorCommand(name, 'system_powerdown')
Guido Trotter's avatar
Guido Trotter committed
774

775 776 777 778 779 780 781 782
  def CleanupInstance(self, instance_name):
    """Cleanup after a stopped instance

    """
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
    if pid > 0 and alive:
      raise errors.HypervisorError("Cannot cleanup a live instance")
    self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
Guido Trotter's avatar
Guido Trotter committed
783 784 785 786 787 788 789 790

  def RebootInstance(self, instance):
    """Reboot an instance.

    """
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
    # socket the instance will stop, but now power up again. So we'll resort
    # to shutdown and restart.
791
    _, _, alive = self._InstancePidAlive(instance.name)
792
    if not alive:
793 794
      raise errors.HypervisorError("Failed to reboot instance %s:"
                                   " not running" % instance.name)
Guido Trotter's avatar
Guido Trotter committed
795 796 797 798 799 800 801 802 803
    # StopInstance will delete the saved KVM runtime so:
    # ...first load it...
    kvm_runtime = self._LoadKVMRuntime(instance)
    # ...now we can safely call StopInstance...
    if not self.StopInstance(instance):
      self.StopInstance(instance, force=True)
    # ...and finally we can save it again, and execute it...
    self._SaveKVMRuntime(instance, kvm_runtime)
    self._ExecuteKVMRuntime(instance, kvm_runtime)
Guido Trotter's avatar
Guido Trotter committed
804

Guido Trotter's avatar
Guido Trotter committed
805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827
  def MigrationInfo(self, instance):
    """Get instance information to perform a migration.

    @type instance: L{objects.Instance}
    @param instance: instance to be migrated
    @rtype: string
    @return: content of the KVM runtime file

    """
    return self._ReadKVMRuntime(instance.name)

  def AcceptInstance(self, instance, info, target):
    """Prepare to accept an instance.

    @type instance: L{objects.Instance}
    @param instance: instance to be accepted
    @type info: string
    @param info: content of the KVM runtime file on the source node
    @type target: string
    @param target: target host (usually ip), on this node

    """
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
828
    incoming_address = (target, instance.hvparams[constants.HV_MIGRATION_PORT])
Guido Trotter's avatar
Guido Trotter committed
829 830 831 832 833 834 835 836
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)

  def FinalizeMigration(self, instance, info, success):
    """Finalize an instance migration.

    Stop the incoming mode KVM.

    @type instance: L{objects.Instance}
837
    @param instance: instance whose migration is being finalized
Guido Trotter's avatar
Guido Trotter committed
838 839 840 841 842 843 844

    """
    if success:
      self._WriteKVMRuntime(instance.name, info)
    else:
      self.StopInstance(instance, force=True)

845
  def MigrateInstance(self, instance, target, live):
Guido Trotter's avatar
Guido Trotter committed
846 847 848 849 850
    """Migrate an instance to a target node.

    The migration will not be attempted if the instance is not
    currently running.

851 852
    @type instance: L{objects.Instance}
    @param instance: the instance to be migrated
Guido Trotter's avatar
Guido Trotter committed
853 854 855 856 857 858
    @type target: string
    @param target: ip address of the target node
    @type live: boolean
    @param live: perform a live migration

    """
859
    instance_name = instance.name
860
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
Guido Trotter's avatar
Guido Trotter committed
861 862 863 864
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
    if not alive:
      raise errors.HypervisorError("Instance not running, cannot migrate")

865 866 867 868
    if not utils.TcpPing(target, port, live_port_needed=True):
      raise errors.HypervisorError("Remote host %s not listening on port"
                                   " %s, cannot migrate" % (target, port))

Guido Trotter's avatar
Guido Trotter committed
869 870 871
    if not live:
      self._CallMonitorCommand(instance_name, 'stop')

872 873 874 875 876 877 878 879
    migrate_command = ('migrate_set_speed %dm' %
        instance.hvparams[constants.HV_MIGRATION_BANDWIDTH])
    self._CallMonitorCommand(instance_name, migrate_command)

    migrate_command = ('migrate_set_downtime %dms' %
        instance.hvparams[constants.HV_MIGRATION_DOWNTIME])
    self._CallMonitorCommand(instance_name, migrate_command)

880
    migrate_command = 'migrate -d tcp:%s:%s' % (target, port)
Guido Trotter's avatar
Guido Trotter committed
881 882 883 884
    self._CallMonitorCommand(instance_name, migrate_command)

    info_command = 'info migrate'
    done = False
885
    broken_answers = 0
Guido Trotter's avatar
Guido Trotter committed
886 887 888 889
    while not done:
      result = self._CallMonitorCommand(instance_name, info_command)
      match = self._MIGRATION_STATUS_RE.search(result.stdout)
      if not match:
890 891 892 893
        broken_answers += 1
        if not result.stdout:
          logging.info("KVM: empty 'info migrate' result")
        else:
Guido Trotter's avatar
Guido Trotter committed
894
          logging.warning("KVM: unknown 'info migrate' result: %s",
895 896
                          result.stdout)
        time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
Guido Trotter's avatar
Guido Trotter committed
897 898 899 900 901
      else:
        status = match.group(1)
        if status == 'completed':
          done = True
        elif status == 'active':
902 903 904
          # reset the broken answers count
          broken_answers = 0
          time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
905 906 907 908 909
        elif status == 'failed' or status == 'cancelled':
          if not live:
            self._CallMonitorCommand(instance_name, 'cont')
          raise errors.HypervisorError("Migration %s at the kvm level" %
                                       status)
Guido Trotter's avatar
Guido Trotter committed
910
        else:
911 912 913 914 915
          logging.warning("KVM: unknown migration status '%s'", status)
          broken_answers += 1
          time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
      if broken_answers >= self._MIGRATION_INFO_MAX_BAD_ANSWERS:
        raise errors.HypervisorError("Too many 'info migrate' broken answers")
Guido Trotter's avatar
Guido Trotter committed
916 917

    utils.KillProcess(pid)
918
    self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
Guido Trotter's avatar
Guido Trotter committed
919

Guido Trotter's avatar
Guido Trotter committed
920 921 922
  def GetNodeInfo(self):
    """Return information about the node.

923 924
    This is just a wrapper over the base GetLinuxNodeInfo method.

Iustin Pop's avatar
Iustin Pop committed
925 926 927 928
    @return: a dict with the following keys (values in MiB):
          - memory_total: the total memory size on the node
          - memory_free: the available memory on the node for instances
          - memory_dom0: the memory used by the node itself, if available
Guido Trotter's avatar
Guido Trotter committed
929 930

    """
931
    return self.GetLinuxNodeInfo()
Guido Trotter's avatar
Guido Trotter committed
932

933
  @classmethod
934
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
Guido Trotter's avatar
Guido Trotter committed
935 936 937
    """Return a command for connecting to the console of an instance.

    """
938
    if hvparams[constants.HV_SERIAL_CONSOLE]:
939 940
      shell_command = ("%s STDIO,%s UNIX-CONNECT:%s" %
                       (constants.SOCAT_PATH, cls._SocatUnixConsoleParams(),
941 942 943
                        utils.ShellQuote(cls._InstanceSerial(instance.name))))
    else:
      shell_command = "echo 'No serial shell for instance %s'" % instance.name
944 945 946

    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
    if vnc_bind_address:
947 948
      if instance.network_port > constants.VNC_BASE_PORT:
        display = instance.network_port - constants.VNC_BASE_PORT
949 950 951 952 953 954
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
                       " (display: %d)'" % (vnc_bind_address,
                                            instance.network_port,
                                            display))
        shell_command = "%s; %s" % (vnc_command, shell_command)

955
    return shell_command
Guido Trotter's avatar
Guido Trotter committed
956 957 958 959 960 961 962 963 964

  def Verify(self):
    """Verify the hypervisor.

    Check that the binary exists.

    """
    if not os.path.exists(constants.KVM_PATH):
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
Guido Trotter's avatar
Guido Trotter committed
965 966 967
    if not os.path.exists(constants.SOCAT_PATH):
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH

968 969 970 971 972 973 974 975 976 977

  @classmethod
  def CheckParameterSyntax(cls, hvparams):
    """Check the given parameters for validity.

    @type hvparams:  dict
    @param hvparams: dictionary with parameter names/value
    @raise errors.HypervisorError: when a parameter is not valid

    """
978
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)