Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ aa0b600b

History | View | Annotate | Download (32.7 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

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

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

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

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

    
97
  ANCILLARY_FILES = [
98
    _KVM_NETWORK_SCRIPT,
99
    ]
100

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

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

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

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

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

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

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

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

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

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

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

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

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

    
165
    return (instance, memory, vcpus)
166

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

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

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

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

    
186
    return (pidfile, pid, alive)
187

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
433
      drive_val = 'file=%s,format=raw%s%s%s' % (dev_path, if_val, boot_val,
434
                                                cache_val)
435
      kvm_cmd.extend(['-drive', drive_val])
436

    
437
    iso_image = hvp[constants.HV_CDROM_IMAGE_PATH]
438
    if iso_image:
439
      options = ',format=raw,media=cdrom'
440
      if boot_cdrom:
441
        kvm_cmd.extend(['-boot', 'd'])
442
        options = '%s,boot=on' % options
443
      else:
444
        options = '%s,if=virtio' % options
445
      drive_val = 'file=%s%s' % (iso_image, options)
446
      kvm_cmd.extend(['-drive', drive_val])
447

    
448
    kernel_path = hvp[constants.HV_KERNEL_PATH]
449
    if kernel_path:
450
      kvm_cmd.extend(['-kernel', kernel_path])
451
      initrd_path = hvp[constants.HV_INITRD_PATH]
452
      if initrd_path:
453
        kvm_cmd.extend(['-initrd', initrd_path])
454
      root_append = ['root=%s' % hvp[constants.HV_ROOT_PATH],
455
                     hvp[constants.HV_KERNEL_ARGS]]
456
      if hvp[constants.HV_SERIAL_CONSOLE]:
457
        root_append.append('console=ttyS0,38400')
458
      kvm_cmd.extend(['-append', ' '.join(root_append)])
459

    
460
    mouse_type = hvp[constants.HV_USB_MOUSE]
461
    if mouse_type:
462
      kvm_cmd.extend(['-usb'])
463
      kvm_cmd.extend(['-usbdevice', mouse_type])
464

    
465
    vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS]
466
    if vnc_bind_address:
467
      if utils.IsValidIP(vnc_bind_address):
468
        if instance.network_port > constants.VNC_BASE_PORT:
469
          display = instance.network_port - constants.VNC_BASE_PORT
470
          if vnc_bind_address == '0.0.0.0':
471
            vnc_arg = ':%d' % (display)
472
          else:
473
            vnc_arg = '%s:%d' % (vnc_bind_address, display)
474
        else:
475
          logging.error("Network port is not a valid VNC display (%d < %d)."
476
                        " Not starting VNC", instance.network_port,
477
                        constants.VNC_BASE_PORT)
478
          vnc_arg = 'none'
479

    
480
        # Only allow tls and other option when not binding to a file, for now.
481
        # kvm/qemu gets confused otherwise about the filename to use.
482
        vnc_append = ''
483
        if hvp[constants.HV_VNC_TLS]:
484
          vnc_append = '%s,tls' % vnc_append
485
          if hvp[constants.HV_VNC_X509_VERIFY]:
486
            vnc_append = '%s,x509verify=%s' % (vnc_append,
487
                                               hvp[constants.HV_VNC_X509])
488
          elif hvp[constants.HV_VNC_X509]:
489
            vnc_append = '%s,x509=%s' % (vnc_append,
490
                                         hvp[constants.HV_VNC_X509])
491
        if hvp[constants.HV_VNC_PASSWORD_FILE]:
492
          vnc_append = '%s,password' % vnc_append
493

    
494
        vnc_arg = '%s%s' % (vnc_arg, vnc_append)
495

    
496
      else:
497
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
498

    
499
      kvm_cmd.extend(['-vnc', vnc_arg])
500
    else:
501
      kvm_cmd.extend(['-nographic'])
502

    
503
    monitor_dev = ("unix:%s,server,nowait" %
504
                   self._InstanceMonitor(instance.name))
505
    kvm_cmd.extend(['-monitor', monitor_dev])
506
    if hvp[constants.HV_SERIAL_CONSOLE]:
507
      serial_dev = ('unix:%s,server,nowait' %
508
                    self._InstanceSerial(instance.name))
509
      kvm_cmd.extend(['-serial', serial_dev])
510
    else:
511
      kvm_cmd.extend(['-serial', 'none'])
512

    
513
    if hvp[constants.HV_USE_LOCALTIME]:
514
      kvm_cmd.extend(['-localtime'])
515

    
516
    # Save the current instance nics, but defer their expansion as parameters,
517
    # as we'll need to generate executable temp files for them.
518
    kvm_nics = instance.nics
519
    hvparams = hvp
520

    
521
    return (kvm_cmd, kvm_nics, hvparams)
522

    
523
  def _WriteKVMRuntime(self, instance_name, data):
524
    """Write an instance's KVM runtime
525

526
    """
527
    try:
528
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
529
                      data=data)
530
    except EnvironmentError, err:
531
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
532

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

536
    """
537
    try:
538
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
539
    except EnvironmentError, err:
540
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
541
    return file_content
542

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

546
    """
547
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
548
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
549
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
550
    self._WriteKVMRuntime(instance.name, serialized_form)
551

    
552
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
553
    """Load an instance's KVM runtime
554

555
    """
556
    if not serialized_runtime:
557
      serialized_runtime = self._ReadKVMRuntime(instance.name)
558
    loaded_runtime = serializer.Load(serialized_runtime)
559
    kvm_cmd, serialized_nics, hvparams = loaded_runtime
560
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
561
    return (kvm_cmd, kvm_nics, hvparams)
562

    
563
  def _RunKVMCmd(self, name, kvm_cmd):
564
    """Run the KVM cmd and check for errors
565

566
    @type name: string
567
    @param name: instance name
568
    @type kvm_cmd: list of strings
569
    @param kvm_cmd: runcmd input for kvm
570

571
    """
572
    result = utils.RunCmd(kvm_cmd)
573
    if result.failed:
574
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
575
                                   (name, result.fail_reason, result.output))
576
    if not self._InstancePidAlive(name)[2]:
577
      raise errors.HypervisorError("Failed to start instance %s" % name)
578

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

582
    @type incoming: tuple of strings
583
    @param incoming: (target_host_ip, port)
584

585
    """
586
    hvp = instance.hvparams
587
    name = instance.name
588
    self._CheckDown(name)
589

    
590
    temp_files = []
591

    
592
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
593

    
594
    security_model = hvp[constants.HV_SECURITY_MODEL]
595
    if security_model == constants.HT_SM_USER:
596
      kvm_cmd.extend(["-runas", hvp[constants.HV_SECURITY_DOMAIN]])
597

    
598
    if not kvm_nics:
599
      kvm_cmd.extend(['-net', 'none'])
600
    else:
601
      nic_type = hvparams[constants.HV_NIC_TYPE]
602
      if nic_type == constants.HT_NIC_PARAVIRTUAL:
603
        nic_model = "model=virtio"
604
      else:
605
        nic_model = "model=%s" % nic_type
606

    
607
      for nic_seq, nic in enumerate(kvm_nics):
608
        nic_val = "nic,vlan=%s,macaddr=%s,%s" % (nic_seq, nic.mac, nic_model)
609
        script = self._WriteNetScript(instance, nic_seq, nic)
610
        kvm_cmd.extend(['-net', nic_val])
611
        kvm_cmd.extend(['-net', 'tap,vlan=%s,script=%s' % (nic_seq, script)])
612
        temp_files.append(script)
613

    
614
    if incoming:
615
      target, port = incoming
616
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
617

    
618
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
619
    vnc_pwd = None
620
    if vnc_pwd_file:
621
      try:
622
        vnc_pwd = utils.ReadFile(vnc_pwd_file)
623
      except EnvironmentError, err:
624
        raise errors.HypervisorError("Failed to open VNC password file %s: %s"
625
                                     % (vnc_pwd_file, err))
626

    
627
    if security_model == constants.HT_SM_POOL:
628
      ss = ssconf.SimpleStore()
629
      uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\n")
630
      all_uids = set(uidpool.ExpandUidPool(uid_pool))
631
      uid = uidpool.RequestUnusedUid(all_uids)
632
      try:
633
        username = pwd.getpwuid(uid.GetUid()).pw_name
634
        kvm_cmd.extend(["-runas", username])
635
        self._RunKVMCmd(name, kvm_cmd)
636
      except:
637
        uidpool.ReleaseUid(uid)
638
        raise
639
      else:
640
        uid.Unlock()
641
        utils.WriteFile(self._InstanceUidFile(name), data=str(uid))
642
    else:
643
      self._RunKVMCmd(name, kvm_cmd)
644

    
645
    if vnc_pwd:
646
      change_cmd = 'change vnc password %s' % vnc_pwd
647
      self._CallMonitorCommand(instance.name, change_cmd)
648

    
649
    for filename in temp_files:
650
      utils.RemoveFile(filename)
651

    
652
  def StartInstance(self, instance, block_devices):
653
    """Start an instance.
654

655
    """
656
    self._CheckDown(instance.name)
657
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices)
658
    self._SaveKVMRuntime(instance, kvm_runtime)
659
    self._ExecuteKVMRuntime(instance, kvm_runtime)
660

    
661
  def _CallMonitorCommand(self, instance_name, command):
662
    """Invoke a command on the instance monitor.
663

664
    """
665
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
666
             (utils.ShellQuote(command),
667
              constants.SOCAT_PATH,
668
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
669
    result = utils.RunCmd(socat)
670
    if result.failed:
671
      msg = ("Failed to send command '%s' to instance %s."
672
             " output: %s, error: %s, fail_reason: %s" %
673
             (command, instance_name,
674
              result.stdout, result.stderr, result.fail_reason))
675
      raise errors.HypervisorError(msg)
676

    
677
    return result
678

    
679
  def StopInstance(self, instance, force=False, retry=False, name=None):
680
    """Stop an instance.
681

682
    """
683
    if name is not None and not force:
684
      raise errors.HypervisorError("Cannot shutdown cleanly by name only")
685
    if name is None:
686
      name = instance.name
687
      acpi = instance.hvparams[constants.HV_ACPI]
688
    else:
689
      acpi = False
690
    _, pid, alive = self._InstancePidAlive(name)
691
    if pid > 0 and alive:
692
      if force or not acpi:
693
        utils.KillProcess(pid)
694
      else:
695
        self._CallMonitorCommand(name, 'system_powerdown')
696

    
697
  def CleanupInstance(self, instance_name):
698
    """Cleanup after a stopped instance
699

700
    """
701
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
702
    if pid > 0 and alive:
703
      raise errors.HypervisorError("Cannot cleanup a live instance")
704
    self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
705

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

709
    """
710
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
711
    # socket the instance will stop, but now power up again. So we'll resort
712
    # to shutdown and restart.
713
    _, _, alive = self._InstancePidAlive(instance.name)
714
    if not alive:
715
      raise errors.HypervisorError("Failed to reboot instance %s:"
716
                                   " not running" % instance.name)
717
    # StopInstance will delete the saved KVM runtime so:
718
    # ...first load it...
719
    kvm_runtime = self._LoadKVMRuntime(instance)
720
    # ...now we can safely call StopInstance...
721
    if not self.StopInstance(instance):
722
      self.StopInstance(instance, force=True)
723
    # ...and finally we can save it again, and execute it...
724
    self._SaveKVMRuntime(instance, kvm_runtime)
725
    self._ExecuteKVMRuntime(instance, kvm_runtime)
726

    
727
  def MigrationInfo(self, instance):
728
    """Get instance information to perform a migration.
729

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

735
    """
736
    return self._ReadKVMRuntime(instance.name)
737

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

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

748
    """
749
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
750
    incoming_address = (target, instance.hvparams[constants.HV_MIGRATION_PORT])
751
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
752

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

756
    Stop the incoming mode KVM.
757

758
    @type instance: L{objects.Instance}
759
    @param instance: instance whose migration is being aborted
760

761
    """
762
    if success:
763
      self._WriteKVMRuntime(instance.name, info)
764
    else:
765
      self.StopInstance(instance, force=True)
766

    
767
  def MigrateInstance(self, instance, target, live):
768
    """Migrate an instance to a target node.
769

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

773
    @type instance: L{objects.Instance}
774
    @param instance: the instance to be migrated
775
    @type target: string
776
    @param target: ip address of the target node
777
    @type live: boolean
778
    @param live: perform a live migration
779

780
    """
781
    instance_name = instance.name
782
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
783
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
784
    if not alive:
785
      raise errors.HypervisorError("Instance not running, cannot migrate")
786

    
787
    if not utils.TcpPing(target, port, live_port_needed=True):
788
      raise errors.HypervisorError("Remote host %s not listening on port"
789
                                   " %s, cannot migrate" % (target, port))
790

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

    
794
    migrate_command = 'migrate -d tcp:%s:%s' % (target, port)
795
    self._CallMonitorCommand(instance_name, migrate_command)
796

    
797
    info_command = 'info migrate'
798
    done = False
799
    broken_answers = 0
800
    while not done:
801
      result = self._CallMonitorCommand(instance_name, info_command)
802
      match = self._MIGRATION_STATUS_RE.search(result.stdout)
803
      if not match:
804
        broken_answers += 1
805
        if not result.stdout:
806
          logging.info("KVM: empty 'info migrate' result")
807
        else:
808
          logging.warning("KVM: unknown 'info migrate' result: %s",
809
                          result.stdout)
810
        time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
811
      else:
812
        status = match.group(1)
813
        if status == 'completed':
814
          done = True
815
        elif status == 'active':
816
          # reset the broken answers count
817
          broken_answers = 0
818
          time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
819
        elif status == 'failed' or status == 'cancelled':
820
          if not live:
821
            self._CallMonitorCommand(instance_name, 'cont')
822
          raise errors.HypervisorError("Migration %s at the kvm level" %
823
                                       status)
824
        else:
825
          logging.warning("KVM: unknown migration status '%s'", status)
826
          broken_answers += 1
827
          time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
828
      if broken_answers >= self._MIGRATION_INFO_MAX_BAD_ANSWERS:
829
        raise errors.HypervisorError("Too many 'info migrate' broken answers")
830

    
831
    utils.KillProcess(pid)
832
    self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
833

    
834
  def GetNodeInfo(self):
835
    """Return information about the node.
836

837
    This is just a wrapper over the base GetLinuxNodeInfo method.
838

839
    @return: a dict with the following keys (values in MiB):
840
          - memory_total: the total memory size on the node
841
          - memory_free: the available memory on the node for instances
842
          - memory_dom0: the memory used by the node itself, if available
843

844
    """
845
    return self.GetLinuxNodeInfo()
846

    
847
  @classmethod
848
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
849
    """Return a command for connecting to the console of an instance.
850

851
    """
852
    if hvparams[constants.HV_SERIAL_CONSOLE]:
853
      shell_command = ("%s STDIO,%s UNIX-CONNECT:%s" %
854
                       (constants.SOCAT_PATH, cls._SocatUnixConsoleParams(),
855
                        utils.ShellQuote(cls._InstanceSerial(instance.name))))
856
    else:
857
      shell_command = "echo 'No serial shell for instance %s'" % instance.name
858

    
859
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
860
    if vnc_bind_address:
861
      if instance.network_port > constants.VNC_BASE_PORT:
862
        display = instance.network_port - constants.VNC_BASE_PORT
863
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
864
                       " (display: %d)'" % (vnc_bind_address,
865
                                            instance.network_port,
866
                                            display))
867
        shell_command = "%s; %s" % (vnc_command, shell_command)
868

    
869
    return shell_command
870

    
871
  def Verify(self):
872
    """Verify the hypervisor.
873

874
    Check that the binary exists.
875

876
    """
877
    if not os.path.exists(constants.KVM_PATH):
878
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
879
    if not os.path.exists(constants.SOCAT_PATH):
880
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
881

    
882

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

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

891
    """
892
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
893

    
894
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
895
    if kernel_path:
896
      if not hvparams[constants.HV_ROOT_PATH]:
897
        raise errors.HypervisorError("Need a root partition for the instance,"
898
                                     " if a kernel is defined")
899

    
900
    if (hvparams[constants.HV_VNC_X509_VERIFY] and
901
        not hvparams[constants.HV_VNC_X509]):
902
      raise errors.HypervisorError("%s must be defined, if %s is" %
903
                                   (constants.HV_VNC_X509,
904
                                    constants.HV_VNC_X509_VERIFY))
905

    
906
    boot_order = hvparams[constants.HV_BOOT_ORDER]
907
    if (boot_order == constants.HT_BO_CDROM and
908
        not hvparams[constants.HV_CDROM_IMAGE_PATH]):
909
      raise errors.HypervisorError("Cannot boot from cdrom without an"
910
                                   " ISO path")
911

    
912
    security_model = hvparams[constants.HV_SECURITY_MODEL]
913
    if security_model == constants.HT_SM_USER:
914
      if not hvparams[constants.HV_SECURITY_DOMAIN]:
915
        raise errors.HypervisorError("A security domain (user to run kvm as)"
916
                                     " must be specified")
917
    elif (security_model == constants.HT_SM_NONE or
918
          security_model == constants.HT_SM_POOL):
919
      if hvparams[constants.HV_SECURITY_DOMAIN]:
920
        raise errors.HypervisorError("Cannot have a security domain when the"
921
                                     " security model is 'none' or 'pool'")
922

    
923
  @classmethod
924
  def ValidateParameters(cls, hvparams):
925
    """Check the given parameters for validity.
926

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

931
    """
932
    super(KVMHypervisor, cls).ValidateParameters(hvparams)
933

    
934
    security_model = hvparams[constants.HV_SECURITY_MODEL]
935
    if security_model == constants.HT_SM_USER:
936
      username = hvparams[constants.HV_SECURITY_DOMAIN]
937
      try:
938
        pwd.getpwnam(username)
939
      except KeyError:
940
        raise errors.HypervisorError("Unknown security domain user %s"
941
                                     % username)
942

    
943
  @classmethod
944
  def PowercycleNode(cls):
945
    """KVM powercycle, just a wrapper over Linux powercycle.
946

947
    """
948
    cls.LinuxPowercycle()