hv_kvm.py 39.1 KB
Newer Older
Guido Trotter's avatar
Guido Trotter committed
1
2
3
#
#

4
# Copyright (C) 2008, 2009, 2010 Google Inc.
Guido Trotter's avatar
Guido Trotter committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#
# 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
from ganeti.hypervisor import hv_base
44
from ganeti import netutils
Guido Trotter's avatar
Guido Trotter committed
45
46


47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
_KVM_NETWORK_SCRIPT = constants.SYSCONFDIR + "/ganeti/kvm-vif-bridge"


def _WriteNetScript(instance, nic, index):
  """Write a script to connect a net interface to the proper bridge.

  This can be used by any qemu-type hypervisor.

  @type instance: L{objects.Instance}
  @param instance: Instance object
  @type nic: L{objects.NIC}
  @param nic: NIC object
  @type index: int
  @param index: NIC index
  @return: Script
  @rtype: string

  """
  if instance.tags:
    tags = " ".join(instance.tags)
  else:
    tags = ""

  buf = StringIO()
  sw = utils.ShellWriter(buf)
  sw.Write("#!/bin/sh")
  sw.Write("# this is autogenerated by Ganeti, please do not edit")
  sw.Write("export PATH=$PATH:/sbin:/usr/sbin")
  sw.Write("export INSTANCE=%s", utils.ShellQuote(instance.name))
  sw.Write("export MAC=%s", utils.ShellQuote(nic.mac))
  sw.Write("export MODE=%s",
           utils.ShellQuote(nic.nicparams[constants.NIC_MODE]))
  sw.Write("export INTERFACE=\"$1\"")
  sw.Write("export TAGS=%s", utils.ShellQuote(tags))

  if nic.ip:
    sw.Write("export IP=%s", utils.ShellQuote(nic.ip))

  if nic.nicparams[constants.NIC_LINK]:
    sw.Write("export LINK=%s",
             utils.ShellQuote(nic.nicparams[constants.NIC_LINK]))

  if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
    sw.Write("export BRIDGE=%s",
             utils.ShellQuote(nic.nicparams[constants.NIC_LINK]))

  # TODO: make this configurable at ./configure time
  sw.Write("if [ -x %s ]; then", utils.ShellQuote(_KVM_NETWORK_SCRIPT))
  sw.IncIndent()
  try:
    sw.Write("# Execute the user-specific vif file")
    sw.Write(_KVM_NETWORK_SCRIPT)
  finally:
    sw.DecIndent()
  sw.Write("else")
  sw.IncIndent()
  try:
    sw.Write("ifconfig $INTERFACE 0.0.0.0 up")

    if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
      sw.Write("# Connect the interface to the bridge")
      sw.Write("brctl addif $BRIDGE $INTERFACE")

    elif nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_ROUTED:
      if not nic.ip:
        raise errors.HypervisorError("nic/%d is routed, but has no IP"
                                     " address" % index)

      sw.Write("# Route traffic targeted at the IP to the interface")
      if nic.nicparams[constants.NIC_LINK]:
        sw.Write("while ip rule del dev $INTERFACE; do :; done")
        sw.Write("ip rule add dev $INTERFACE table $LINK")
        sw.Write("ip route replace $IP table $LINK proto static"
                 " dev $INTERFACE")
      else:
        sw.Write("ip route replace $IP proto static dev $INTERFACE")

      interface_v4_conf = "/proc/sys/net/ipv4/conf/$INTERFACE"
      sw.Write(" if [ -d %s ]; then", interface_v4_conf)
      sw.IncIndent()
      try:
        sw.Write("echo 1 > %s/proxy_arp", interface_v4_conf)
        sw.Write("echo 1 > %s/forwarding", interface_v4_conf)
      finally:
        sw.DecIndent()
      sw.Write("fi")

      interface_v6_conf = "/proc/sys/net/ipv6/conf/$INTERFACE"
      sw.Write("if [ -d %s ]; then", interface_v6_conf)
      sw.IncIndent()
      try:
        sw.Write("echo 1 > %s/proxy_ndp", interface_v6_conf)
        sw.Write("echo 1 > %s/forwarding", interface_v6_conf)
      finally:
        sw.DecIndent()
      sw.Write("fi")
  finally:
    sw.DecIndent()
  sw.Write("fi")

  return buf.getvalue()


Guido Trotter's avatar
Guido Trotter committed
150
class KVMHypervisor(hv_base.BaseHypervisor):
Guido Trotter's avatar
Guido Trotter committed
151
  """KVM hypervisor interface"""
152
  CAN_MIGRATE = True
Guido Trotter's avatar
Guido Trotter committed
153
154

  _ROOT_DIR = constants.RUN_GANETI_DIR + "/kvm-hypervisor"
Guido Trotter's avatar
Guido Trotter committed
155
  _PIDS_DIR = _ROOT_DIR + "/pid" # contains live instances pids
156
  _UIDS_DIR = _ROOT_DIR + "/uid" # contains instances reserved uids
Guido Trotter's avatar
Guido Trotter committed
157
158
  _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
159
160
161
162
163
164
165
166
167
168
  # 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
169

170
171
172
173
174
175
176
  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
177
    constants.HV_VNC_BIND_ADDRESS:
178
179
      (False, lambda x: (netutils.IP4Address.IsValid(x) or
                         utils.IsNormAbsPath(x)),
Michael Hanselmann's avatar
Michael Hanselmann committed
180
181
       "the VNC bind address must be either a valid IP address or an absolute"
       " pathname", None, None),
182
183
184
    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,
185
    constants.HV_VNC_PASSWORD_FILE: hv_base.OPT_FILE_CHECK,
186
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
Michael Hanselmann's avatar
Michael Hanselmann committed
187
188
189
190
191
192
193
194
    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),
195
    constants.HV_MIGRATION_PORT: hv_base.NET_PORT_CHECK,
196
197
    constants.HV_MIGRATION_BANDWIDTH: hv_base.NO_CHECK,
    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
201
    constants.HV_DISK_CACHE:
      hv_base.ParamInSet(True, constants.HT_VALID_CACHE_TYPES),
202
203
204
    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
205
206
    constants.HV_KVM_FLAG:
      hv_base.ParamInSet(False, constants.HT_KVM_FLAG_VALUES),
207
    constants.HV_VHOST_NET: hv_base.NO_CHECK,
Balazs Lecz's avatar
Balazs Lecz committed
208
    constants.HV_KVM_USE_CHROOT: hv_base.NO_CHECK,
209
    constants.HV_MEM_PATH: hv_base.OPT_DIR_CHECK,
210
    }
211

Guido Trotter's avatar
Guido Trotter committed
212
213
  _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)',
                                    re.M | re.I)
214
215
  _MIGRATION_INFO_MAX_BAD_ANSWERS = 5
  _MIGRATION_INFO_RETRY_DELAY = 2
Guido Trotter's avatar
Guido Trotter committed
216

217
218
  _VERSION_RE = re.compile(r"\b(\d+)\.(\d+)\.(\d+)\b")

219
220
221
222
  ANCILLARY_FILES = [
    _KVM_NETWORK_SCRIPT,
    ]

Guido Trotter's avatar
Guido Trotter committed
223
224
225
226
  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
227
    dirs = [(dname, constants.RUN_DIRS_MODE) for dname in self._DIRS]
Guido Trotter's avatar
Guido Trotter committed
228
    utils.EnsureDirs(dirs)
Guido Trotter's avatar
Guido Trotter committed
229

230
231
  @classmethod
  def _InstancePidFile(cls, instance_name):
232
233
234
    """Returns the instance pidfile.

    """
235
    return utils.PathJoin(cls._PIDS_DIR, instance_name)
236

237
238
239
240
241
242
243
  @classmethod
  def _InstanceUidFile(cls, instance_name):
    """Returns the instance uidfile.

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

244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
  @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)

289
  def _InstancePidAlive(self, instance_name):
290
291
292
293
294
295
    """Returns the instance pidfile, pid, and liveness.

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

    """
298
    pidfile = self._InstancePidFile(instance_name)
299
    pid = utils.ReadPidFile(pidfile)
300
301
302
303
304
305
306

    alive = False
    try:
      cmd_instance = self._InstancePidInfo(pid)[0]
      alive = (cmd_instance == instance_name)
    except errors.HypervisorError:
      pass
307
308
309

    return (pidfile, pid, alive)

310
311
312
313
314
315
316
317
318
  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"))

319
320
  @classmethod
  def _InstanceMonitor(cls, instance_name):
321
322
323
    """Returns the instance monitor socket name

    """
324
    return utils.PathJoin(cls._CTRL_DIR, "%s.monitor" % instance_name)
325

326
327
  @classmethod
  def _InstanceSerial(cls, instance_name):
328
329
330
    """Returns the instance serial socket name

    """
331
    return utils.PathJoin(cls._CTRL_DIR, "%s.serial" % instance_name)
332

333
334
335
336
337
338
339
  @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.

    """
340
    if constants.SOCAT_USE_ESCAPE:
341
342
343
344
      return "raw,echo=0,escape=%s" % constants.SOCAT_ESCAPE_CODE
    else:
      return "echo=0,icanon=0"

345
346
  @classmethod
  def _InstanceKVMRuntime(cls, instance_name):
347
348
349
    """Returns the instance KVM runtime filename

    """
350
    return utils.PathJoin(cls._CONF_DIR, "%s.runtime" % instance_name)
351

Balazs Lecz's avatar
Balazs Lecz committed
352
353
354
355
356
357
358
  @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)

359
360
361
362
363
364
365
  @classmethod
  def _TryReadUidFile(cls, uid_file):
    """Try to read a uid file

    """
    if os.path.exists(uid_file):
      try:
366
        uid = int(utils.ReadOneLineFile(uid_file))
367
        return uid
368
369
370
371
      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)
372
    return None
373

374
375
  @classmethod
  def _RemoveInstanceRuntimeFiles(cls, pidfile, instance_name):
Balazs Lecz's avatar
Balazs Lecz committed
376
    """Removes an instance's rutime sockets/files/dirs.
377
378
379
380
381
382

    """
    utils.RemoveFile(pidfile)
    utils.RemoveFile(cls._InstanceMonitor(instance_name))
    utils.RemoveFile(cls._InstanceSerial(instance_name))
    utils.RemoveFile(cls._InstanceKVMRuntime(instance_name))
383
384
385
386
387
    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
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
    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
406

407
408
  @staticmethod
  def _WriteNetScriptFile(instance, seq, nic):
Guido Trotter's avatar
Guido Trotter committed
409
410
411
412
413
414
415
416
417
418
419
420
421
422
    """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

    """
423
424
    script = _WriteNetScript(instance, nic, seq)

Guido Trotter's avatar
Guido Trotter committed
425
426
427
428
    # 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
429
    try:
430
      tmpfile.write(script)
Michael Hanselmann's avatar
Michael Hanselmann committed
431
432
    finally:
      tmpfile.close()
Guido Trotter's avatar
Guido Trotter committed
433
434
435
436
437
438
    os.chmod(tmpfile_name, 0755)
    return tmpfile_name

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

Iustin Pop's avatar
Iustin Pop committed
439
440
    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
441
442
443
444

    """
    result = []
    for name in os.listdir(self._PIDS_DIR):
445
      if self._InstancePidAlive(name)[2]:
Guido Trotter's avatar
Guido Trotter committed
446
447
448
449
450
451
        result.append(name)
    return result

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

452
    @type instance_name: string
Iustin Pop's avatar
Iustin Pop committed
453
    @param instance_name: the instance name
454
455
    @rtype: tuple of strings
    @return: (name, id, memory, vcpus, stat, times)
Guido Trotter's avatar
Guido Trotter committed
456
457

    """
458
    _, pid, alive = self._InstancePidAlive(instance_name)
459
    if not alive:
Guido Trotter's avatar
Guido Trotter committed
460
461
      return None

462
    _, memory, vcpus = self._InstancePidInfo(pid)
Guido Trotter's avatar
Guido Trotter committed
463
464
465
466
467
468
469
470
    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
471
472
    @return: list of tuples (name, id, memory, vcpus, stat, times)

Guido Trotter's avatar
Guido Trotter committed
473
474
475
    """
    data = []
    for name in os.listdir(self._PIDS_DIR):
476
477
478
479
480
481
      try:
        info = self.GetInstanceInfo(name)
      except errors.HypervisorError:
        continue
      if info:
        data.append(info)
Guido Trotter's avatar
Guido Trotter committed
482
483
    return data

484
  def _GenerateKVMRuntime(self, instance, block_devices):
485
    """Generate KVM information to start an instance.
Guido Trotter's avatar
Guido Trotter committed
486
487

    """
488
    pidfile  = self._InstancePidFile(instance.name)
Guido Trotter's avatar
Guido Trotter committed
489
490
    kvm = constants.KVM_PATH
    kvm_cmd = [kvm]
491
492
    # used just by the vnc server, if enabled
    kvm_cmd.extend(['-name', instance.name])
493
494
    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
495
496
    kvm_cmd.extend(['-pidfile', pidfile])
    kvm_cmd.extend(['-daemonize'])
497
    if not instance.hvparams[constants.HV_ACPI]:
Guido Trotter's avatar
Guido Trotter committed
498
499
      kvm_cmd.extend(['-no-acpi'])

500
    hvp = instance.hvparams
501
502
503
    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
504

Guido Trotter's avatar
Guido Trotter committed
505
506
507
508
509
    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
510
511
    if boot_network:
      kvm_cmd.extend(['-boot', 'n'])
512

513
    disk_type = hvp[constants.HV_DISK_TYPE]
514
515
516
517
    if disk_type == constants.HT_DISK_PARAVIRTUAL:
      if_val = ',if=virtio'
    else:
      if_val = ',if=%s' % disk_type
518
519
520
521
522
523
    # 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 = ""
524
    for cfdev, dev_path in block_devices:
525
526
527
      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
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:
Guido Trotter's avatar
Guido Trotter committed
532
          boot_val = ',boot=on'
533
        else:
Guido Trotter's avatar
Guido Trotter committed
534
          boot_val = ''
Guido Trotter's avatar
Guido Trotter committed
535
        # We only boot from the first disk
536
        boot_disk = False
Guido Trotter's avatar
Guido Trotter committed
537
538
539
      else:
        boot_val = ''

540
541
      drive_val = 'file=%s,format=raw%s%s%s' % (dev_path, if_val, boot_val,
                                                cache_val)
Guido Trotter's avatar
Guido Trotter committed
542
543
      kvm_cmd.extend(['-drive', drive_val])

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:
Guido Trotter's avatar
Guido Trotter committed
550
          options = '%s,boot=on' % options
551
      else:
552
553
554
555
556
        if disk_type == constants.HT_DISK_PARAVIRTUAL:
          if_val = ',if=virtio'
        else:
          if_val = ',if=%s' % disk_type
        options = '%s%s' % (options, if_val)
557
558
559
      drive_val = 'file=%s%s' % (iso_image, options)
      kvm_cmd.extend(['-drive', drive_val])

560
    kernel_path = hvp[constants.HV_KERNEL_PATH]
561
562
    if kernel_path:
      kvm_cmd.extend(['-kernel', kernel_path])
563
      initrd_path = hvp[constants.HV_INITRD_PATH]
564
565
      if initrd_path:
        kvm_cmd.extend(['-initrd', initrd_path])
566
567
568
569
570
      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
571

572
573
    mem_path = hvp[constants.HV_MEM_PATH]
    if mem_path:
574
      kvm_cmd.extend(["-mem-path", mem_path, "-mem-prealloc"])
575

576
    mouse_type = hvp[constants.HV_USB_MOUSE]
577
578
    vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS]

579
580
581
    if mouse_type:
      kvm_cmd.extend(['-usb'])
      kvm_cmd.extend(['-usbdevice', mouse_type])
582
583
    elif vnc_bind_address:
      kvm_cmd.extend(['-usbdevice', constants.HT_MOUSE_TABLET])
584

585
    if vnc_bind_address:
586
      if netutils.IP4Address.IsValid(vnc_bind_address):
587
588
        if instance.network_port > constants.VNC_BASE_PORT:
          display = instance.network_port - constants.VNC_BASE_PORT
589
          if vnc_bind_address == constants.IP4_ADDRESS_ANY:
590
591
            vnc_arg = ':%d' % (display)
          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)."
Iustin Pop's avatar
Iustin Pop committed
595
596
                        " Not starting VNC", instance.network_port,
                        constants.VNC_BASE_PORT)
597
          vnc_arg = 'none'
598
599
600
601

        # 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 = ''
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
607
                                               hvp[constants.HV_VNC_X509])
          elif hvp[constants.HV_VNC_X509]:
608
            vnc_append = '%s,x509=%s' % (vnc_append,
609
                                         hvp[constants.HV_VNC_X509])
610
611
612
        if hvp[constants.HV_VNC_PASSWORD_FILE]:
          vnc_append = '%s,password' % vnc_append

613
614
        vnc_arg = '%s%s' % (vnc_arg, vnc_append)

615
      else:
616
617
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)

618
      kvm_cmd.extend(['-vnc', vnc_arg])
619
620
621
    else:
      kvm_cmd.extend(['-nographic'])

622
623
    monitor_dev = ("unix:%s,server,nowait" %
                   self._InstanceMonitor(instance.name))
Guido Trotter's avatar
Guido Trotter committed
624
    kvm_cmd.extend(['-monitor', monitor_dev])
625
626
627
    if hvp[constants.HV_SERIAL_CONSOLE]:
      serial_dev = ('unix:%s,server,nowait' %
                    self._InstanceSerial(instance.name))
628
629
630
      kvm_cmd.extend(['-serial', serial_dev])
    else:
      kvm_cmd.extend(['-serial', 'none'])
Guido Trotter's avatar
Guido Trotter committed
631

632
633
634
    if hvp[constants.HV_USE_LOCALTIME]:
      kvm_cmd.extend(['-localtime'])

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

638
639
640
    # 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
641
    hvparams = hvp
642

643
    return (kvm_cmd, kvm_nics, hvparams)
644

645
646
647
648
649
650
651
  def _WriteKVMRuntime(self, instance_name, data):
    """Write an instance's KVM runtime

    """
    try:
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
                      data=data)
652
    except EnvironmentError, err:
653
654
655
656
657
658
659
660
      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))
661
    except EnvironmentError, err:
662
663
664
665
666
667
668
      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

    """
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
673
    self._WriteKVMRuntime(instance.name, serialized_form)

Guido Trotter's avatar
Guido Trotter committed
674
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
675
676
677
    """Load an instance's KVM runtime

    """
Guido Trotter's avatar
Guido Trotter committed
678
679
680
    if not serialized_runtime:
      serialized_runtime = self._ReadKVMRuntime(instance.name)
    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
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
  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
701
  def _ExecuteKVMRuntime(self, instance, kvm_runtime, incoming=None):
702
703
    """Execute a KVM cmd, after completing it with some last minute data

Guido Trotter's avatar
Guido Trotter committed
704
705
706
    @type incoming: tuple of strings
    @param incoming: (target_host_ip, port)

707
    """
708
709
710
711
712
713
714
715
716
    # 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
717
718
    name = instance.name
    self._CheckDown(name)
719
720
721

    temp_files = []

722
723
    kvm_cmd, kvm_nics, up_hvp = kvm_runtime
    up_hvp = objects.FillDict(conf_hvp, up_hvp)
724

725
726
    kvm_version = self._GetKVMVersion()
    if kvm_version:
727
      _, v_major, v_min, _ = kvm_version
728
729
730
    else:
      raise errors.HypervisorError("Unable to get KVM version")

731
732
733
    # 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]
734
    if security_model == constants.HT_SM_USER:
735
      kvm_cmd.extend(["-runas", conf_hvp[constants.HV_SECURITY_DOMAIN]])
736

737
738
739
    # 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
740
    if not kvm_nics:
741
      kvm_cmd.extend(["-net", "none"])
742
    else:
743
      tap_extra = ""
744
      nic_type = up_hvp[constants.HV_NIC_TYPE]
745
      if nic_type == constants.HT_NIC_PARAVIRTUAL:
746
        # From version 0.12.0, kvm uses a new sintax for network configuration.
747
        if (v_major, v_min) >= (0, 12):
748
749
750
751
          nic_model = "virtio-net-pci"
        else:
          nic_model = "virtio"

752
        if up_hvp[constants.HV_VHOST_NET]:
753
          # vhost_net is only available from version 0.13.0 or newer
754
          if (v_major, v_min) >= (0, 13):
755
756
757
758
            tap_extra = ",vhost=on"
          else:
            raise errors.HypervisorError("vhost_net is configured"
                                        " but it is not available")
759
      else:
760
        nic_model = nic_type
761

762
      for nic_seq, nic in enumerate(kvm_nics):
763
        script = self._WriteNetScriptFile(instance, nic_seq, nic)
764
        temp_files.append(script)
765
        if (v_major, v_min) >= (0, 12):
766
          nic_val = "%s,mac=%s,netdev=netdev%s" % (nic_model, nic.mac, nic_seq)
767
768
          tap_val = "type=tap,id=netdev%s,script=%s%s" % (nic_seq,
                                                          script, tap_extra)
769
770
          kvm_cmd.extend(["-netdev", tap_val, "-device", nic_val])
        else:
771
772
          nic_val = "nic,vlan=%s,macaddr=%s,model=%s" % (nic_seq,
                                                         nic.mac, nic_model)
773
774
          tap_val = "tap,vlan=%s,script=%s" % (nic_seq, script)
          kvm_cmd.extend(["-net", tap_val, "-net", nic_val])
775

Guido Trotter's avatar
Guido Trotter committed
776
777
778
779
    if incoming:
      target, port = incoming
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])

780
781
782
783
    # 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]
784
785
786
    vnc_pwd = None
    if vnc_pwd_file:
      try:
787
        vnc_pwd = utils.ReadOneLineFile(vnc_pwd_file, strict=True)
788
789
790
791
      except EnvironmentError, err:
        raise errors.HypervisorError("Failed to open VNC password file %s: %s"
                                     % (vnc_pwd_file, err))

792
    if conf_hvp[constants.HV_KVM_USE_CHROOT]:
Balazs Lecz's avatar
Balazs Lecz committed
793
794
795
      utils.EnsureDirs([(self._InstanceChrootDir(name),
                         constants.SECURE_DIR_MODE)])

796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
    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
813

814
815
816
817
    if vnc_pwd:
      change_cmd = 'change vnc password %s' % vnc_pwd
      self._CallMonitorCommand(instance.name, change_cmd)

818
819
    for filename in temp_files:
      utils.RemoveFile(filename)
Guido Trotter's avatar
Guido Trotter committed
820

821
  def StartInstance(self, instance, block_devices):
822
823
824
    """Start an instance.

    """
825
    self._CheckDown(instance.name)
826
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices)
827
    self._SaveKVMRuntime(instance, kvm_runtime)
828
829
    self._ExecuteKVMRuntime(instance, kvm_runtime)

830
831
832
833
834
835
836
837
838
839
840
841
  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" %
842
843
             (command, instance_name,
              result.stdout, result.stderr, result.fail_reason))
844
845
846
847
      raise errors.HypervisorError(msg)

    return result

848
849
850
851
852
853
854
855
856
857
858
859
860
  @classmethod
  def _GetKVMVersion(cls):
    """Return the installed KVM version

    @return: (version, v_maj, v_min, v_rev), or None

    """
    result = utils.RunCmd([constants.KVM_PATH, "--help"])
    if result.failed:
      return None
    match = cls._VERSION_RE.search(result.output.splitlines()[0])
    if not match:
      return None
861
862
863

    return (match.group(0), int(match.group(1)), int(match.group(2)),
            int(match.group(3)))
864

865
  def StopInstance(self, instance, force=False, retry=False, name=None):
Guido Trotter's avatar
Guido Trotter committed
866
867
868
    """Stop an instance.

    """
869
870
871
872
873
874
875
    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
876
    _, pid, alive = self._InstancePidAlive(name)
877
    if pid > 0 and alive:
878
      if force or not acpi:
Guido Trotter's avatar
Guido Trotter committed
879
880
        utils.KillProcess(pid)
      else:
881
        self._CallMonitorCommand(name, 'system_powerdown')
Guido Trotter's avatar
Guido Trotter committed
882

883
884
885
886
887
888
889
890
  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
891
892
893
894
895
896
897
898

  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.
899
    _, _, alive = self._InstancePidAlive(instance.name)
900
    if not alive:
901
902
      raise errors.HypervisorError("Failed to reboot instance %s:"
                                   " not running" % instance.name)
Guido Trotter's avatar
Guido Trotter committed
903
904
905
906
907
908
909
910
911
    # 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
912

Guido Trotter's avatar
Guido Trotter committed
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
  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)
936
    incoming_address = (target, instance.hvparams[constants.HV_MIGRATION_PORT])
Guido Trotter's avatar
Guido Trotter committed
937
938
939
940
941
942
943
944
    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}
945
    @param instance: instance whose migration is being finalized
Guido Trotter's avatar
Guido Trotter committed
946
947
948
949
950
951
952

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

953
  def MigrateInstance(self, instance, target, live):
Guido Trotter's avatar
Guido Trotter committed
954
955
956
957
958
    """Migrate an instance to a target node.

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

959
960
    @type instance: L{objects.Instance}
    @param instance: the instance to be migrated
Guido Trotter's avatar
Guido Trotter committed
961
962
963
964
965
966
    @type target: string
    @param target: ip address of the target node
    @type live: boolean
    @param live: perform a live migration

    """
967
    instance_name = instance.name
968
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
Guido Trotter's avatar
Guido Trotter committed
969
970
971
972
973
974
975
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
    if not alive:
      raise errors.HypervisorError("Instance not running, cannot migrate")

    if not live:
      self._CallMonitorCommand(instance_name, 'stop')

976
977
978
979
980
981
982
983
    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)

984
    migrate_command = 'migrate -d tcp:%s:%s' % (target, port)
Guido Trotter's avatar
Guido Trotter committed
985
986
987
988
    self._CallMonitorCommand(instance_name, migrate_command)

    info_command = 'info migrate'
    done = False
989
    broken_answers = 0
Guido Trotter's avatar
Guido Trotter committed
990
991
992
993
    while not done:
      result = self._CallMonitorCommand(instance_name, info_command)
      match = self._MIGRATION_STATUS_RE.search(result.stdout)
      if not match:
994
995
996
997
        broken_answers += 1
        if not result.stdout:
          logging.info("KVM: empty 'info migrate' result")
        else:
Guido Trotter's avatar
Guido Trotter committed
998
          logging.warning("KVM: unknown 'info migrate' result: %s",
999
1000
                          result.stdout)
        time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
Guido Trotter's avatar
Guido Trotter committed
1001
1002
1003
1004
1005
      else:
        status = match.group(1)
        if status == 'completed':
          done = True
        elif status == 'active':
1006
1007
1008
          # reset the broken answers count
          broken_answers = 0
          time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
1009
1010
1011
1012
1013
        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
1014
        else:
1015
1016
1017
1018
1019
          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
1020
1021

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

Guido Trotter's avatar
Guido Trotter committed
1024
1025
1026
  def GetNodeInfo(self):
    """Return information about the node.

1027
1028
    This is just a wrapper over the base GetLinuxNodeInfo method.

Iustin Pop's avatar
Iustin Pop committed
1029
1030
1031
1032
    @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
1033
1034

    """
1035
    return self.GetLinuxNodeInfo()
Guido Trotter's avatar
Guido Trotter committed
1036

1037
  @classmethod
1038
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
Guido Trotter's avatar
Guido Trotter committed
1039
1040
1041
    """Return a command for connecting to the console of an instance.

    """
1042
    if hvparams[constants.HV_SERIAL_CONSOLE]:
1043
1044
      shell_command = ("%s STDIO,%s UNIX-CONNECT:%s" %
                       (constants.SOCAT_PATH, cls._SocatUnixConsoleParams(),
1045
1046
1047
                        utils.ShellQuote(cls._InstanceSerial(instance.name))))
    else:
      shell_command = "echo 'No serial shell for instance %s'" % instance.name
1048
1049
1050

    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
    if vnc_bind_address:
1051
1052
      if instance.network_port > constants.VNC_BASE_PORT:
        display = instance.network_port - constants.VNC_BASE_PORT
1053
1054
1055
1056
1057
1058
        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)

1059
    return shell_command
Guido Trotter's avatar
Guido Trotter committed
1060
1061
1062
1063
1064
1065
1066
1067
1068

  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
1069
1070
1071
    if not os.path.exists(constants.SOCAT_PATH):
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH

1072
1073
1074
1075
1076
1077
1078
1079
1080
1081

  @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

    """
1082
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
1083

1084
1085
1086
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
    if kernel_path:
      if not hvparams[constants.HV_ROOT_PATH]:
1087
1088
        raise errors.HypervisorError("Need a root partition for the instance,"
                                     " if a kernel is defined")
1089

1090
1091
1092
1093
1094
    if (hvparams[constants.HV_VNC_X509_VERIFY] and
        not hvparams[constants.HV_VNC_X509]):
      raise errors.HypervisorError("%s must be defined, if %s is" %
                                   (constants.HV_VNC_X509,
                                    constants.HV_VNC_X509_VERIFY))
1095
1096

    boot_order = hvparams[constants.HV_BOOT_ORDER]
1097
1098
    if (boot_order == constants.HT_BO_CDROM and
        not hvparams[constants.HV_CDROM_IMAGE_PATH]):
1099
1100
      raise errors.HypervisorError("Cannot boot from cdrom without an"
                                   " ISO path")
Iustin Pop's avatar
Iustin Pop committed
1101

1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
    security_model = hvparams[constants.HV_SECURITY_MODEL]
    if security_model == constants.HT_SM_USER:
      if not hvparams[constants.HV_SECURITY_DOMAIN]:
        raise errors.HypervisorError("A security domain (user to run kvm as)"
                                     " must be specified")
    elif (security_model == constants.HT_SM_NONE or
          security_model == constants.HT_SM_POOL):
      if hvparams[constants.HV_SECURITY_DOMAIN]:
        raise errors.HypervisorError("Cannot have a security domain when the"
                                     " security model is 'none' or 'pool'")

  @classmethod
  def ValidateParameters(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

    """
    super(KVMHypervisor, cls).ValidateParameters(hvparams)

    security_model = hvparams[constants.HV_SECURITY_MODEL]
    if security_model == constants.HT_SM_USER:
      username = hvparams[constants.HV_SECURITY_DOMAIN]
      try:
Guido Trotter's avatar
Guido Trotter committed
1128
        pwd.getpwnam(username)
1129
1130
1131
1132
      except KeyError:
        raise errors.HypervisorError("Unknown security domain user %s"
                                     % username)

Iustin Pop's avatar
Iustin Pop committed
1133
1134
1135
1136
1137
1138
  @classmethod
  def PowercycleNode(cls):
    """KVM powercycle, just a wrapper over Linux powercycle.

    """
    cls.LinuxPowercycle()