Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 1cc831ea

History | View | Annotate | Download (33.2 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
    if instance.tags:
291
      script.write("export TAGS=\"%s\"\n" % " ".join(instance.tags))
292
    # TODO: make this configurable at ./configure time
293
    script.write("if [ -x '%s' ]; then\n" % self._KVM_NETWORK_SCRIPT)
294
    script.write("  # Execute the user-specific vif file\n")
295
    script.write("  %s\n" % self._KVM_NETWORK_SCRIPT)
296
    script.write("else\n")
297
    script.write("  ifconfig $INTERFACE 0.0.0.0 up\n")
298
    if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
299
      script.write("  # Connect the interface to the bridge\n")
300
      script.write("  brctl addif $BRIDGE $INTERFACE\n")
301
    elif nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_ROUTED:
302
      if not nic.ip:
303
        raise errors.HypervisorError("nic/%d is routed, but has no ip." % seq)
304
      script.write("  # Route traffic targeted at the IP to the interface\n")
305
      if nic.nicparams[constants.NIC_LINK]:
306
        script.write("  while ip rule del dev $INTERFACE; do :; done\n")
307
        script.write("  ip rule add dev $INTERFACE table $LINK\n")
308
        script.write("  ip route replace $IP table $LINK proto static"
309
                     " dev $INTERFACE\n")
310
      else:
311
        script.write("  ip route replace $IP proto static"
312
                     " dev $INTERFACE\n")
313
      interface_v4_conf = "/proc/sys/net/ipv4/conf/$INTERFACE"
314
      interface_v6_conf = "/proc/sys/net/ipv6/conf/$INTERFACE"
315
      script.write("  if [ -d %s ]; then\n" % interface_v4_conf)
316
      script.write("    echo 1 > %s/proxy_arp\n" % interface_v4_conf)
317
      script.write("    echo 1 > %s/forwarding\n" % interface_v4_conf)
318
      script.write("  fi\n")
319
      script.write("  if [ -d %s ]; then\n" % interface_v6_conf)
320
      script.write("    echo 1 > %s/proxy_ndp\n" % interface_v6_conf)
321
      script.write("    echo 1 > %s/forwarding\n" % interface_v6_conf)
322
      script.write("  fi\n")
323
    script.write("fi\n\n")
324
    # As much as we'd like to put this in our _ROOT_DIR, that will happen to be
325
    # mounted noexec sometimes, so we'll have to find another place.
326
    (tmpfd, tmpfile_name) = tempfile.mkstemp()
327
    tmpfile = os.fdopen(tmpfd, 'w')
328
    try:
329
      tmpfile.write(script.getvalue())
330
    finally:
331
      tmpfile.close()
332
    os.chmod(tmpfile_name, 0755)
333
    return tmpfile_name
334

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

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

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

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

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

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

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

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

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

370
    @return: list of tuples (name, id, memory, vcpus, stat, times)
371

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

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

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

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

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

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

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

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

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

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

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

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

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

    
505
        vnc_arg = '%s%s' % (vnc_arg, vnc_append)
506

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

    
510
      kvm_cmd.extend(['-vnc', vnc_arg])
511

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

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

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

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

    
536
    return (kvm_cmd, kvm_nics, hvparams)
537

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

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

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

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

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

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

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

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

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

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

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

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

597
    @type incoming: tuple of strings
598
    @param incoming: (target_host_ip, port)
599

600
    """
601
    hvp = instance.hvparams
602
    name = instance.name
603
    self._CheckDown(name)
604

    
605
    temp_files = []
606

    
607
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
608

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

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

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

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

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

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

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

    
664
    for filename in temp_files:
665
      utils.RemoveFile(filename)
666

    
667
  def StartInstance(self, instance, block_devices):
668
    """Start an instance.
669

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

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

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

    
692
    return result
693

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

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

    
712
  def CleanupInstance(self, instance_name):
713
    """Cleanup after a stopped instance
714

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

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

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

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

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

750
    """
751
    return self._ReadKVMRuntime(instance.name)
752

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

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

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

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

771
    Stop the incoming mode KVM.
772

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

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

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

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

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

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

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

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

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

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

    
846
    utils.KillProcess(pid)
847
    self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
848

    
849
  def GetNodeInfo(self):
850
    """Return information about the node.
851

852
    This is just a wrapper over the base GetLinuxNodeInfo method.
853

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

859
    """
860
    return self.GetLinuxNodeInfo()
861

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

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

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

    
884
    return shell_command
885

    
886
  def Verify(self):
887
    """Verify the hypervisor.
888

889
    Check that the binary exists.
890

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

    
897

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

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

906
    """
907
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
908

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

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

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

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

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

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

946
    """
947
    super(KVMHypervisor, cls).ValidateParameters(hvparams)
948

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

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

962
    """
963
    cls.LinuxPowercycle()