Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ d271c6fd

History | View | Annotate | Download (33.1 kB)

1
#
2
#
3

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

    
21

    
22
"""KVM hypervisor
23

24
"""
25

    
26
import os
27
import os.path
28
import re
29
import tempfile
30
import time
31
import logging
32
import pwd
33
from cStringIO import StringIO
34

    
35
from ganeti import utils
36
from ganeti import constants
37
from ganeti import errors
38
from ganeti import serializer
39
from ganeti import objects
40
from ganeti import uidpool
41
from ganeti import ssconf
42
from ganeti.hypervisor import hv_base
43

    
44

    
45
class KVMHypervisor(hv_base.BaseHypervisor):
46
  """KVM hypervisor interface"""
47
  CAN_MIGRATE = True
48

    
49
  _ROOT_DIR = constants.RUN_GANETI_DIR + "/kvm-hypervisor"
50
  _PIDS_DIR = _ROOT_DIR + "/pid" # contains live instances pids
51
  _UIDS_DIR = _ROOT_DIR + "/uid" # contains instances reserved uids
52
  _CTRL_DIR = _ROOT_DIR + "/ctrl" # contains instances control sockets
53
  _CONF_DIR = _ROOT_DIR + "/conf" # contains instances startup data
54
  _DIRS = [_ROOT_DIR, _PIDS_DIR, _UIDS_DIR, _CTRL_DIR, _CONF_DIR]
55

    
56
  PARAMETERS = {
57
    constants.HV_KERNEL_PATH: hv_base.OPT_FILE_CHECK,
58
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
59
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
60
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
61
    constants.HV_ACPI: hv_base.NO_CHECK,
62
    constants.HV_SERIAL_CONSOLE: hv_base.NO_CHECK,
63
    constants.HV_VNC_BIND_ADDRESS:
64
      (False, lambda x: (utils.IsValidIP(x) or utils.IsNormAbsPath(x)),
65
       "the VNC bind address must be either a valid IP address or an absolute"
66
       " pathname", None, None),
67
    constants.HV_VNC_TLS: hv_base.NO_CHECK,
68
    constants.HV_VNC_X509: hv_base.OPT_DIR_CHECK,
69
    constants.HV_VNC_X509_VERIFY: hv_base.NO_CHECK,
70
    constants.HV_VNC_PASSWORD_FILE: hv_base.OPT_FILE_CHECK,
71
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
72
    constants.HV_BOOT_ORDER:
73
      hv_base.ParamInSet(True, constants.HT_KVM_VALID_BO_TYPES),
74
    constants.HV_NIC_TYPE:
75
      hv_base.ParamInSet(True, constants.HT_KVM_VALID_NIC_TYPES),
76
    constants.HV_DISK_TYPE:
77
      hv_base.ParamInSet(True, constants.HT_KVM_VALID_DISK_TYPES),
78
    constants.HV_USB_MOUSE:
79
      hv_base.ParamInSet(False, constants.HT_KVM_VALID_MOUSE_TYPES),
80
    constants.HV_MIGRATION_PORT: hv_base.NET_PORT_CHECK,
81
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
82
    constants.HV_DISK_CACHE:
83
      hv_base.ParamInSet(True, constants.HT_VALID_CACHE_TYPES),
84
    constants.HV_SECURITY_MODEL:
85
      hv_base.ParamInSet(True, constants.HT_KVM_VALID_SM_TYPES),
86
    constants.HV_SECURITY_DOMAIN: hv_base.NO_CHECK,
87
    constants.HV_KVM_FLAG:
88
      hv_base.ParamInSet(False, constants.HT_KVM_FLAG_VALUES),
89
    }
90

    
91
  _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)',
92
                                    re.M | re.I)
93
  _MIGRATION_INFO_MAX_BAD_ANSWERS = 5
94
  _MIGRATION_INFO_RETRY_DELAY = 2
95

    
96
  _KVM_NETWORK_SCRIPT = constants.SYSCONFDIR + "/ganeti/kvm-vif-bridge"
97

    
98
  ANCILLARY_FILES = [
99
    _KVM_NETWORK_SCRIPT,
100
    ]
101

    
102
  def __init__(self):
103
    hv_base.BaseHypervisor.__init__(self)
104
    # Let's make sure the directories we need exist, even if the RUN_DIR lives
105
    # in a tmpfs filesystem or has been otherwise wiped out.
106
    dirs = [(dname, constants.RUN_DIRS_MODE) for dname in self._DIRS]
107
    utils.EnsureDirs(dirs)
108

    
109
  @classmethod
110
  def _InstancePidFile(cls, instance_name):
111
    """Returns the instance pidfile.
112

113
    """
114
    return utils.PathJoin(cls._PIDS_DIR, instance_name)
115

    
116
  @classmethod
117
  def _InstanceUidFile(cls, instance_name):
118
    """Returns the instance uidfile.
119

120
    """
121
    return utils.PathJoin(cls._UIDS_DIR, instance_name)
122

    
123
  @classmethod
124
  def _InstancePidInfo(cls, pid):
125
    """Check pid file for instance information.
126

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

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

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

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

    
148
    instance = None
149
    memory = 0
150
    vcpus = 0
151

    
152
    arg_list = cmdline.split('\x00')
153
    while arg_list:
154
      arg =  arg_list.pop(0)
155
      if arg == "-name":
156
        instance = arg_list.pop(0)
157
      elif arg == "-m":
158
        memory = int(arg_list.pop(0))
159
      elif arg == "-smp":
160
        vcpus = int(arg_list.pop(0))
161

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

    
166
    return (instance, memory, vcpus)
167

    
168
  def _InstancePidAlive(self, instance_name):
169
    """Returns the instance pidfile, pid, and liveness.
170

171
    @type instance_name: string
172
    @param instance_name: instance name
173
    @rtype: tuple
174
    @return: (pid file name, pid, liveness)
175

176
    """
177
    pidfile = self._InstancePidFile(instance_name)
178
    pid = utils.ReadPidFile(pidfile)
179

    
180
    alive = False
181
    try:
182
      cmd_instance = self._InstancePidInfo(pid)[0]
183
      alive = (cmd_instance == instance_name)
184
    except errors.HypervisorError:
185
      pass
186

    
187
    return (pidfile, pid, alive)
188

    
189
  def _CheckDown(self, instance_name):
190
    """Raises an error unless the given instance is down.
191

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

    
198
  @classmethod
199
  def _InstanceMonitor(cls, instance_name):
200
    """Returns the instance monitor socket name
201

202
    """
203
    return utils.PathJoin(cls._CTRL_DIR, "%s.monitor" % instance_name)
204

    
205
  @classmethod
206
  def _InstanceSerial(cls, instance_name):
207
    """Returns the instance serial socket name
208

209
    """
210
    return utils.PathJoin(cls._CTRL_DIR, "%s.serial" % instance_name)
211

    
212
  @staticmethod
213
  def _SocatUnixConsoleParams():
214
    """Returns the correct parameters for socat
215

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

218
    """
219
    if constants.SOCAT_USE_ESCAPE:
220
      return "raw,echo=0,escape=%s" % constants.SOCAT_ESCAPE_CODE
221
    else:
222
      return "echo=0,icanon=0"
223

    
224
  @classmethod
225
  def _InstanceKVMRuntime(cls, instance_name):
226
    """Returns the instance KVM runtime filename
227

228
    """
229
    return utils.PathJoin(cls._CONF_DIR, "%s.runtime" % instance_name)
230

    
231
  @classmethod
232
  def _TryReadUidFile(cls, uid_file):
233
    """Try to read a uid file
234

235
    """
236
    if os.path.exists(uid_file):
237
      try:
238
        uid = int(utils.ReadFile(uid_file))
239
        return uid
240
      except EnvironmentError:
241
        logging.warning("Can't read uid file", exc_info=True)
242
      except (TypeError, ValueError):
243
        logging.warning("Can't parse uid file contents", exc_info=True)
244
    return None
245

    
246
  @classmethod
247
  def _RemoveInstanceRuntimeFiles(cls, pidfile, instance_name):
248
    """Removes an instance's rutime sockets/files.
249

250
    """
251
    utils.RemoveFile(pidfile)
252
    utils.RemoveFile(cls._InstanceMonitor(instance_name))
253
    utils.RemoveFile(cls._InstanceSerial(instance_name))
254
    utils.RemoveFile(cls._InstanceKVMRuntime(instance_name))
255
    uid_file = cls._InstanceUidFile(instance_name)
256
    uid = cls._TryReadUidFile(uid_file)
257
    utils.RemoveFile(uid_file)
258
    if uid is not None:
259
      uidpool.ReleaseUid(uid)
260

    
261
  def _WriteNetScript(self, instance, seq, nic):
262
    """Write a script to connect a net interface to the proper bridge.
263

264
    This can be used by any qemu-type hypervisor.
265

266
    @param instance: instance we're acting on
267
    @type instance: instance object
268
    @param seq: nic sequence number
269
    @type seq: int
270
    @param nic: nic we're acting on
271
    @type nic: nic object
272
    @return: netscript file name
273
    @rtype: string
274

275
    """
276
    script = StringIO()
277
    script.write("#!/bin/sh\n")
278
    script.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
279
    script.write("PATH=$PATH:/sbin:/usr/sbin\n")
280
    script.write("export INSTANCE=%s\n" % instance.name)
281
    script.write("export MAC=%s\n" % nic.mac)
282
    if nic.ip:
283
      script.write("export IP=%s\n" % nic.ip)
284
    script.write("export MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
285
    if nic.nicparams[constants.NIC_LINK]:
286
      script.write("export LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
287
    if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
288
      script.write("export BRIDGE=%s\n" % nic.nicparams[constants.NIC_LINK])
289
    script.write("export INTERFACE=$1\n")
290
    # TODO: make this configurable at ./configure time
291
    script.write("if [ -x '%s' ]; then\n" % self._KVM_NETWORK_SCRIPT)
292
    script.write("  # Execute the user-specific vif file\n")
293
    script.write("  %s\n" % self._KVM_NETWORK_SCRIPT)
294
    script.write("else\n")
295
    script.write("  ifconfig $INTERFACE 0.0.0.0 up\n")
296
    if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
297
      script.write("  # Connect the interface to the bridge\n")
298
      script.write("  brctl addif $BRIDGE $INTERFACE\n")
299
    elif nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_ROUTED:
300
      if not nic.ip:
301
        raise errors.HypervisorError("nic/%d is routed, but has no ip." % seq)
302
      script.write("  # Route traffic targeted at the IP to the interface\n")
303
      if nic.nicparams[constants.NIC_LINK]:
304
        script.write("  while ip rule del dev $INTERFACE; do :; done\n")
305
        script.write("  ip rule add dev $INTERFACE table $LINK\n")
306
        script.write("  ip route replace $IP table $LINK proto static"
307
                     " dev $INTERFACE\n")
308
      else:
309
        script.write("  ip route replace $IP proto static"
310
                     " dev $INTERFACE\n")
311
      interface_v4_conf = "/proc/sys/net/ipv4/conf/$INTERFACE"
312
      interface_v6_conf = "/proc/sys/net/ipv6/conf/$INTERFACE"
313
      script.write("  if [ -d %s ]; then\n" % interface_v4_conf)
314
      script.write("    echo 1 > %s/proxy_arp\n" % interface_v4_conf)
315
      script.write("    echo 1 > %s/forwarding\n" % interface_v4_conf)
316
      script.write("  fi\n")
317
      script.write("  if [ -d %s ]; then\n" % interface_v6_conf)
318
      script.write("    echo 1 > %s/proxy_ndp\n" % interface_v6_conf)
319
      script.write("    echo 1 > %s/forwarding\n" % interface_v6_conf)
320
      script.write("  fi\n")
321
    script.write("fi\n\n")
322
    # As much as we'd like to put this in our _ROOT_DIR, that will happen to be
323
    # mounted noexec sometimes, so we'll have to find another place.
324
    (tmpfd, tmpfile_name) = tempfile.mkstemp()
325
    tmpfile = os.fdopen(tmpfd, 'w')
326
    try:
327
      tmpfile.write(script.getvalue())
328
    finally:
329
      tmpfile.close()
330
    os.chmod(tmpfile_name, 0755)
331
    return tmpfile_name
332

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

336
    We can do this by listing our live instances directory and
337
    checking whether the associated kvm process is still alive.
338

339
    """
340
    result = []
341
    for name in os.listdir(self._PIDS_DIR):
342
      if self._InstancePidAlive(name)[2]:
343
        result.append(name)
344
    return result
345

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

349
    @type instance_name: string
350
    @param instance_name: the instance name
351
    @rtype: tuple of strings
352
    @return: (name, id, memory, vcpus, stat, times)
353

354
    """
355
    _, pid, alive = self._InstancePidAlive(instance_name)
356
    if not alive:
357
      return None
358

    
359
    _, memory, vcpus = self._InstancePidInfo(pid)
360
    stat = "---b-"
361
    times = "0"
362

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

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

368
    @return: list of tuples (name, id, memory, vcpus, stat, times)
369

370
    """
371
    data = []
372
    for name in os.listdir(self._PIDS_DIR):
373
      try:
374
        info = self.GetInstanceInfo(name)
375
      except errors.HypervisorError:
376
        continue
377
      if info:
378
        data.append(info)
379
    return data
380

    
381
  def _GenerateKVMRuntime(self, instance, block_devices):
382
    """Generate KVM information to start an instance.
383

384
    """
385
    pidfile  = self._InstancePidFile(instance.name)
386
    kvm = constants.KVM_PATH
387
    kvm_cmd = [kvm]
388
    # used just by the vnc server, if enabled
389
    kvm_cmd.extend(['-name', instance.name])
390
    kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
391
    kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
392
    kvm_cmd.extend(['-pidfile', pidfile])
393
    kvm_cmd.extend(['-daemonize'])
394
    if not instance.hvparams[constants.HV_ACPI]:
395
      kvm_cmd.extend(['-no-acpi'])
396

    
397
    hvp = instance.hvparams
398
    boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
399
    boot_cdrom = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_CDROM
400
    boot_network = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_NETWORK
401

    
402
    if hvp[constants.HV_KVM_FLAG] == constants.HT_KVM_ENABLED:
403
      kvm_cmd.extend(["-enable-kvm"])
404
    elif hvp[constants.HV_KVM_FLAG] == constants.HT_KVM_DISABLED:
405
      kvm_cmd.extend(["-disable-kvm"])
406

    
407
    if boot_network:
408
      kvm_cmd.extend(['-boot', 'n'])
409

    
410
    disk_type = hvp[constants.HV_DISK_TYPE]
411
    if disk_type == constants.HT_DISK_PARAVIRTUAL:
412
      if_val = ',if=virtio'
413
    else:
414
      if_val = ',if=%s' % disk_type
415
    # Cache mode
416
    disk_cache = hvp[constants.HV_DISK_CACHE]
417
    if disk_cache != constants.HT_CACHE_DEFAULT:
418
      cache_val = ",cache=%s" % disk_cache
419
    else:
420
      cache_val = ""
421
    for cfdev, dev_path in block_devices:
422
      if cfdev.mode != constants.DISK_RDWR:
423
        raise errors.HypervisorError("Instance has read-only disks which"
424
                                     " are not supported by KVM")
425
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
426
      if boot_disk:
427
        kvm_cmd.extend(['-boot', 'c'])
428
        if disk_type != constants.HT_DISK_IDE:
429
          boot_val = ',boot=on'
430
        else:
431
          boot_val = ''
432
        # We only boot from the first disk
433
        boot_disk = False
434
      else:
435
        boot_val = ''
436

    
437
      drive_val = 'file=%s,format=raw%s%s%s' % (dev_path, if_val, boot_val,
438
                                                cache_val)
439
      kvm_cmd.extend(['-drive', drive_val])
440

    
441
    iso_image = hvp[constants.HV_CDROM_IMAGE_PATH]
442
    if iso_image:
443
      options = ',format=raw,media=cdrom'
444
      if boot_cdrom:
445
        kvm_cmd.extend(['-boot', 'd'])
446
        if disk_type != constants.HT_DISK_IDE:
447
          options = '%s,boot=on' % options
448
      else:
449
        if disk_type == constants.HT_DISK_PARAVIRTUAL:
450
          if_val = ',if=virtio'
451
        else:
452
          if_val = ',if=%s' % disk_type
453
        options = '%s%s' % (options, if_val)
454
      drive_val = 'file=%s%s' % (iso_image, options)
455
      kvm_cmd.extend(['-drive', drive_val])
456

    
457
    kernel_path = hvp[constants.HV_KERNEL_PATH]
458
    if kernel_path:
459
      kvm_cmd.extend(['-kernel', kernel_path])
460
      initrd_path = hvp[constants.HV_INITRD_PATH]
461
      if initrd_path:
462
        kvm_cmd.extend(['-initrd', initrd_path])
463
      root_append = ['root=%s' % hvp[constants.HV_ROOT_PATH],
464
                     hvp[constants.HV_KERNEL_ARGS]]
465
      if hvp[constants.HV_SERIAL_CONSOLE]:
466
        root_append.append('console=ttyS0,38400')
467
      kvm_cmd.extend(['-append', ' '.join(root_append)])
468

    
469
    mouse_type = hvp[constants.HV_USB_MOUSE]
470
    if mouse_type:
471
      kvm_cmd.extend(['-usb'])
472
      kvm_cmd.extend(['-usbdevice', mouse_type])
473

    
474
    vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS]
475
    if vnc_bind_address:
476
      if utils.IsValidIP(vnc_bind_address):
477
        if instance.network_port > constants.VNC_BASE_PORT:
478
          display = instance.network_port - constants.VNC_BASE_PORT
479
          if vnc_bind_address == '0.0.0.0':
480
            vnc_arg = ':%d' % (display)
481
          else:
482
            vnc_arg = '%s:%d' % (vnc_bind_address, display)
483
        else:
484
          logging.error("Network port is not a valid VNC display (%d < %d)."
485
                        " Not starting VNC", instance.network_port,
486
                        constants.VNC_BASE_PORT)
487
          vnc_arg = 'none'
488

    
489
        # Only allow tls and other option when not binding to a file, for now.
490
        # kvm/qemu gets confused otherwise about the filename to use.
491
        vnc_append = ''
492
        if hvp[constants.HV_VNC_TLS]:
493
          vnc_append = '%s,tls' % vnc_append
494
          if hvp[constants.HV_VNC_X509_VERIFY]:
495
            vnc_append = '%s,x509verify=%s' % (vnc_append,
496
                                               hvp[constants.HV_VNC_X509])
497
          elif hvp[constants.HV_VNC_X509]:
498
            vnc_append = '%s,x509=%s' % (vnc_append,
499
                                         hvp[constants.HV_VNC_X509])
500
        if hvp[constants.HV_VNC_PASSWORD_FILE]:
501
          vnc_append = '%s,password' % vnc_append
502

    
503
        vnc_arg = '%s%s' % (vnc_arg, vnc_append)
504

    
505
      else:
506
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
507

    
508
      kvm_cmd.extend(['-vnc', vnc_arg])
509

    
510
      # Also add a tablet USB device to act as a mouse
511
      # This solves various mouse alignment issues
512
      kvm_cmd.extend(['-usbdevice', 'tablet'])
513
    else:
514
      kvm_cmd.extend(['-nographic'])
515

    
516
    monitor_dev = ("unix:%s,server,nowait" %
517
                   self._InstanceMonitor(instance.name))
518
    kvm_cmd.extend(['-monitor', monitor_dev])
519
    if hvp[constants.HV_SERIAL_CONSOLE]:
520
      serial_dev = ('unix:%s,server,nowait' %
521
                    self._InstanceSerial(instance.name))
522
      kvm_cmd.extend(['-serial', serial_dev])
523
    else:
524
      kvm_cmd.extend(['-serial', 'none'])
525

    
526
    if hvp[constants.HV_USE_LOCALTIME]:
527
      kvm_cmd.extend(['-localtime'])
528

    
529
    # Save the current instance nics, but defer their expansion as parameters,
530
    # as we'll need to generate executable temp files for them.
531
    kvm_nics = instance.nics
532
    hvparams = hvp
533

    
534
    return (kvm_cmd, kvm_nics, hvparams)
535

    
536
  def _WriteKVMRuntime(self, instance_name, data):
537
    """Write an instance's KVM runtime
538

539
    """
540
    try:
541
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
542
                      data=data)
543
    except EnvironmentError, err:
544
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
545

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

549
    """
550
    try:
551
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
552
    except EnvironmentError, err:
553
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
554
    return file_content
555

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

559
    """
560
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
561
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
562
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
563
    self._WriteKVMRuntime(instance.name, serialized_form)
564

    
565
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
566
    """Load an instance's KVM runtime
567

568
    """
569
    if not serialized_runtime:
570
      serialized_runtime = self._ReadKVMRuntime(instance.name)
571
    loaded_runtime = serializer.Load(serialized_runtime)
572
    kvm_cmd, serialized_nics, hvparams = loaded_runtime
573
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
574
    return (kvm_cmd, kvm_nics, hvparams)
575

    
576
  def _RunKVMCmd(self, name, kvm_cmd):
577
    """Run the KVM cmd and check for errors
578

579
    @type name: string
580
    @param name: instance name
581
    @type kvm_cmd: list of strings
582
    @param kvm_cmd: runcmd input for kvm
583

584
    """
585
    result = utils.RunCmd(kvm_cmd)
586
    if result.failed:
587
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
588
                                   (name, result.fail_reason, result.output))
589
    if not self._InstancePidAlive(name)[2]:
590
      raise errors.HypervisorError("Failed to start instance %s" % name)
591

    
592
  def _ExecuteKVMRuntime(self, instance, kvm_runtime, incoming=None):
593
    """Execute a KVM cmd, after completing it with some last minute data
594

595
    @type incoming: tuple of strings
596
    @param incoming: (target_host_ip, port)
597

598
    """
599
    hvp = instance.hvparams
600
    name = instance.name
601
    self._CheckDown(name)
602

    
603
    temp_files = []
604

    
605
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
606

    
607
    security_model = hvp[constants.HV_SECURITY_MODEL]
608
    if security_model == constants.HT_SM_USER:
609
      kvm_cmd.extend(["-runas", hvp[constants.HV_SECURITY_DOMAIN]])
610

    
611
    if not kvm_nics:
612
      kvm_cmd.extend(['-net', 'none'])
613
    else:
614
      nic_type = hvparams[constants.HV_NIC_TYPE]
615
      if nic_type == constants.HT_NIC_PARAVIRTUAL:
616
        nic_model = "model=virtio"
617
      else:
618
        nic_model = "model=%s" % nic_type
619

    
620
      for nic_seq, nic in enumerate(kvm_nics):
621
        nic_val = "nic,vlan=%s,macaddr=%s,%s" % (nic_seq, nic.mac, nic_model)
622
        script = self._WriteNetScript(instance, nic_seq, nic)
623
        kvm_cmd.extend(['-net', nic_val])
624
        kvm_cmd.extend(['-net', 'tap,vlan=%s,script=%s' % (nic_seq, script)])
625
        temp_files.append(script)
626

    
627
    if incoming:
628
      target, port = incoming
629
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
630

    
631
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
632
    vnc_pwd = None
633
    if vnc_pwd_file:
634
      try:
635
        vnc_pwd = utils.ReadFile(vnc_pwd_file)
636
      except EnvironmentError, err:
637
        raise errors.HypervisorError("Failed to open VNC password file %s: %s"
638
                                     % (vnc_pwd_file, err))
639

    
640
    if security_model == constants.HT_SM_POOL:
641
      ss = ssconf.SimpleStore()
642
      uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\n")
643
      all_uids = set(uidpool.ExpandUidPool(uid_pool))
644
      uid = uidpool.RequestUnusedUid(all_uids)
645
      try:
646
        username = pwd.getpwuid(uid.GetUid()).pw_name
647
        kvm_cmd.extend(["-runas", username])
648
        self._RunKVMCmd(name, kvm_cmd)
649
      except:
650
        uidpool.ReleaseUid(uid)
651
        raise
652
      else:
653
        uid.Unlock()
654
        utils.WriteFile(self._InstanceUidFile(name), data=str(uid))
655
    else:
656
      self._RunKVMCmd(name, kvm_cmd)
657

    
658
    if vnc_pwd:
659
      change_cmd = 'change vnc password %s' % vnc_pwd
660
      self._CallMonitorCommand(instance.name, change_cmd)
661

    
662
    for filename in temp_files:
663
      utils.RemoveFile(filename)
664

    
665
  def StartInstance(self, instance, block_devices):
666
    """Start an instance.
667

668
    """
669
    self._CheckDown(instance.name)
670
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices)
671
    self._SaveKVMRuntime(instance, kvm_runtime)
672
    self._ExecuteKVMRuntime(instance, kvm_runtime)
673

    
674
  def _CallMonitorCommand(self, instance_name, command):
675
    """Invoke a command on the instance monitor.
676

677
    """
678
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
679
             (utils.ShellQuote(command),
680
              constants.SOCAT_PATH,
681
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
682
    result = utils.RunCmd(socat)
683
    if result.failed:
684
      msg = ("Failed to send command '%s' to instance %s."
685
             " output: %s, error: %s, fail_reason: %s" %
686
             (command, instance_name,
687
              result.stdout, result.stderr, result.fail_reason))
688
      raise errors.HypervisorError(msg)
689

    
690
    return result
691

    
692
  def StopInstance(self, instance, force=False, retry=False, name=None):
693
    """Stop an instance.
694

695
    """
696
    if name is not None and not force:
697
      raise errors.HypervisorError("Cannot shutdown cleanly by name only")
698
    if name is None:
699
      name = instance.name
700
      acpi = instance.hvparams[constants.HV_ACPI]
701
    else:
702
      acpi = False
703
    _, pid, alive = self._InstancePidAlive(name)
704
    if pid > 0 and alive:
705
      if force or not acpi:
706
        utils.KillProcess(pid)
707
      else:
708
        self._CallMonitorCommand(name, 'system_powerdown')
709

    
710
  def CleanupInstance(self, instance_name):
711
    """Cleanup after a stopped instance
712

713
    """
714
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
715
    if pid > 0 and alive:
716
      raise errors.HypervisorError("Cannot cleanup a live instance")
717
    self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
718

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

722
    """
723
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
724
    # socket the instance will stop, but now power up again. So we'll resort
725
    # to shutdown and restart.
726
    _, _, alive = self._InstancePidAlive(instance.name)
727
    if not alive:
728
      raise errors.HypervisorError("Failed to reboot instance %s:"
729
                                   " not running" % instance.name)
730
    # StopInstance will delete the saved KVM runtime so:
731
    # ...first load it...
732
    kvm_runtime = self._LoadKVMRuntime(instance)
733
    # ...now we can safely call StopInstance...
734
    if not self.StopInstance(instance):
735
      self.StopInstance(instance, force=True)
736
    # ...and finally we can save it again, and execute it...
737
    self._SaveKVMRuntime(instance, kvm_runtime)
738
    self._ExecuteKVMRuntime(instance, kvm_runtime)
739

    
740
  def MigrationInfo(self, instance):
741
    """Get instance information to perform a migration.
742

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

748
    """
749
    return self._ReadKVMRuntime(instance.name)
750

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

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

761
    """
762
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
763
    incoming_address = (target, instance.hvparams[constants.HV_MIGRATION_PORT])
764
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
765

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

769
    Stop the incoming mode KVM.
770

771
    @type instance: L{objects.Instance}
772
    @param instance: instance whose migration is being aborted
773

774
    """
775
    if success:
776
      self._WriteKVMRuntime(instance.name, info)
777
    else:
778
      self.StopInstance(instance, force=True)
779

    
780
  def MigrateInstance(self, instance, target, live):
781
    """Migrate an instance to a target node.
782

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

786
    @type instance: L{objects.Instance}
787
    @param instance: the instance to be migrated
788
    @type target: string
789
    @param target: ip address of the target node
790
    @type live: boolean
791
    @param live: perform a live migration
792

793
    """
794
    instance_name = instance.name
795
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
796
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
797
    if not alive:
798
      raise errors.HypervisorError("Instance not running, cannot migrate")
799

    
800
    if not utils.TcpPing(target, port, live_port_needed=True):
801
      raise errors.HypervisorError("Remote host %s not listening on port"
802
                                   " %s, cannot migrate" % (target, port))
803

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

    
807
    migrate_command = 'migrate -d tcp:%s:%s' % (target, port)
808
    self._CallMonitorCommand(instance_name, migrate_command)
809

    
810
    info_command = 'info migrate'
811
    done = False
812
    broken_answers = 0
813
    while not done:
814
      result = self._CallMonitorCommand(instance_name, info_command)
815
      match = self._MIGRATION_STATUS_RE.search(result.stdout)
816
      if not match:
817
        broken_answers += 1
818
        if not result.stdout:
819
          logging.info("KVM: empty 'info migrate' result")
820
        else:
821
          logging.warning("KVM: unknown 'info migrate' result: %s",
822
                          result.stdout)
823
        time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
824
      else:
825
        status = match.group(1)
826
        if status == 'completed':
827
          done = True
828
        elif status == 'active':
829
          # reset the broken answers count
830
          broken_answers = 0
831
          time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
832
        elif status == 'failed' or status == 'cancelled':
833
          if not live:
834
            self._CallMonitorCommand(instance_name, 'cont')
835
          raise errors.HypervisorError("Migration %s at the kvm level" %
836
                                       status)
837
        else:
838
          logging.warning("KVM: unknown migration status '%s'", status)
839
          broken_answers += 1
840
          time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
841
      if broken_answers >= self._MIGRATION_INFO_MAX_BAD_ANSWERS:
842
        raise errors.HypervisorError("Too many 'info migrate' broken answers")
843

    
844
    utils.KillProcess(pid)
845
    self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
846

    
847
  def GetNodeInfo(self):
848
    """Return information about the node.
849

850
    This is just a wrapper over the base GetLinuxNodeInfo method.
851

852
    @return: a dict with the following keys (values in MiB):
853
          - memory_total: the total memory size on the node
854
          - memory_free: the available memory on the node for instances
855
          - memory_dom0: the memory used by the node itself, if available
856

857
    """
858
    return self.GetLinuxNodeInfo()
859

    
860
  @classmethod
861
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
862
    """Return a command for connecting to the console of an instance.
863

864
    """
865
    if hvparams[constants.HV_SERIAL_CONSOLE]:
866
      shell_command = ("%s STDIO,%s UNIX-CONNECT:%s" %
867
                       (constants.SOCAT_PATH, cls._SocatUnixConsoleParams(),
868
                        utils.ShellQuote(cls._InstanceSerial(instance.name))))
869
    else:
870
      shell_command = "echo 'No serial shell for instance %s'" % instance.name
871

    
872
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
873
    if vnc_bind_address:
874
      if instance.network_port > constants.VNC_BASE_PORT:
875
        display = instance.network_port - constants.VNC_BASE_PORT
876
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
877
                       " (display: %d)'" % (vnc_bind_address,
878
                                            instance.network_port,
879
                                            display))
880
        shell_command = "%s; %s" % (vnc_command, shell_command)
881

    
882
    return shell_command
883

    
884
  def Verify(self):
885
    """Verify the hypervisor.
886

887
    Check that the binary exists.
888

889
    """
890
    if not os.path.exists(constants.KVM_PATH):
891
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
892
    if not os.path.exists(constants.SOCAT_PATH):
893
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
894

    
895

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

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

904
    """
905
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
906

    
907
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
908
    if kernel_path:
909
      if not hvparams[constants.HV_ROOT_PATH]:
910
        raise errors.HypervisorError("Need a root partition for the instance,"
911
                                     " if a kernel is defined")
912

    
913
    if (hvparams[constants.HV_VNC_X509_VERIFY] and
914
        not hvparams[constants.HV_VNC_X509]):
915
      raise errors.HypervisorError("%s must be defined, if %s is" %
916
                                   (constants.HV_VNC_X509,
917
                                    constants.HV_VNC_X509_VERIFY))
918

    
919
    boot_order = hvparams[constants.HV_BOOT_ORDER]
920
    if (boot_order == constants.HT_BO_CDROM and
921
        not hvparams[constants.HV_CDROM_IMAGE_PATH]):
922
      raise errors.HypervisorError("Cannot boot from cdrom without an"
923
                                   " ISO path")
924

    
925
    security_model = hvparams[constants.HV_SECURITY_MODEL]
926
    if security_model == constants.HT_SM_USER:
927
      if not hvparams[constants.HV_SECURITY_DOMAIN]:
928
        raise errors.HypervisorError("A security domain (user to run kvm as)"
929
                                     " must be specified")
930
    elif (security_model == constants.HT_SM_NONE or
931
          security_model == constants.HT_SM_POOL):
932
      if hvparams[constants.HV_SECURITY_DOMAIN]:
933
        raise errors.HypervisorError("Cannot have a security domain when the"
934
                                     " security model is 'none' or 'pool'")
935

    
936
  @classmethod
937
  def ValidateParameters(cls, hvparams):
938
    """Check the given parameters for validity.
939

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

944
    """
945
    super(KVMHypervisor, cls).ValidateParameters(hvparams)
946

    
947
    security_model = hvparams[constants.HV_SECURITY_MODEL]
948
    if security_model == constants.HT_SM_USER:
949
      username = hvparams[constants.HV_SECURITY_DOMAIN]
950
      try:
951
        pwd.getpwnam(username)
952
      except KeyError:
953
        raise errors.HypervisorError("Unknown security domain user %s"
954
                                     % username)
955

    
956
  @classmethod
957
  def PowercycleNode(cls):
958
    """KVM powercycle, just a wrapper over Linux powercycle.
959

960
    """
961
    cls.LinuxPowercycle()