Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 7548396c

History | View | Annotate | Download (31.9 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.hypervisor import hv_base
41

    
42

    
43
class KVMHypervisor(hv_base.BaseHypervisor):
44
  """KVM hypervisor interface"""
45

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

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

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

    
93
  _KVM_NETWORK_SCRIPT = constants.SYSCONFDIR + "/ganeti/kvm-vif-bridge"
94

    
95
  ANCILLARY_FILES = [
96
    _KVM_NETWORK_SCRIPT,
97
    ]
98

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

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

110
    """
111
    return utils.PathJoin(cls._PIDS_DIR, instance_name)
112

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

117
    """
118
    return utils.PathJoin(cls._UIDS_DIR, instance_name)
119

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

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

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

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

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

    
145
    instance = None
146
    memory = 0
147
    vcpus = 0
148

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

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

    
163
    return (instance, memory, vcpus)
164

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

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

173
    """
174
    pidfile = self._InstancePidFile(instance_name)
175
    pid = utils.ReadPidFile(pidfile)
176

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

    
184
    return (pidfile, pid, alive)
185

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

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

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

199
    """
200
    return utils.PathJoin(cls._CTRL_DIR, "%s.monitor" % instance_name)
201

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

206
    """
207
    return utils.PathJoin(cls._CTRL_DIR, "%s.serial" % instance_name)
208

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

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

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

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

225
    """
226
    return utils.PathJoin(cls._CONF_DIR, "%s.runtime" % instance_name)
227

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
520
    return (kvm_cmd, kvm_nics, hvparams)
521

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

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

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

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

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

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

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

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

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

565
    @type incoming: tuple of strings
566
    @param incoming: (target_host_ip, port)
567

568
    """
569
    hvp = instance.hvparams
570
    name = instance.name
571
    self._CheckDown(name)
572

    
573
    temp_files = []
574

    
575
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
576

    
577
    security_model = hvp[constants.HV_SECURITY_MODEL]
578
    if security_model == constants.HT_SM_USER:
579
      kvm_cmd.extend(["-runas", hvp[constants.HV_SECURITY_DOMAIN]])
580

    
581
    if not kvm_nics:
582
      kvm_cmd.extend(['-net', 'none'])
583
    else:
584
      nic_type = hvparams[constants.HV_NIC_TYPE]
585
      if nic_type == constants.HT_NIC_PARAVIRTUAL:
586
        nic_model = "model=virtio"
587
      else:
588
        nic_model = "model=%s" % nic_type
589

    
590
      for nic_seq, nic in enumerate(kvm_nics):
591
        nic_val = "nic,vlan=%s,macaddr=%s,%s" % (nic_seq, nic.mac, nic_model)
592
        script = self._WriteNetScript(instance, nic_seq, nic)
593
        kvm_cmd.extend(['-net', nic_val])
594
        kvm_cmd.extend(['-net', 'tap,vlan=%s,script=%s' % (nic_seq, script)])
595
        temp_files.append(script)
596

    
597
    if incoming:
598
      target, port = incoming
599
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
600

    
601
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
602
    vnc_pwd = None
603
    if vnc_pwd_file:
604
      try:
605
        vnc_pwd = utils.ReadFile(vnc_pwd_file)
606
      except EnvironmentError, err:
607
        raise errors.HypervisorError("Failed to open VNC password file %s: %s"
608
                                     % (vnc_pwd_file, err))
609

    
610
    result = utils.RunCmd(kvm_cmd)
611
    if result.failed:
612
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
613
                                   (name, result.fail_reason, result.output))
614

    
615
    if not self._InstancePidAlive(name)[2]:
616
      raise errors.HypervisorError("Failed to start instance %s" % name)
617

    
618
    if vnc_pwd:
619
      change_cmd = 'change vnc password %s' % vnc_pwd
620
      self._CallMonitorCommand(instance.name, change_cmd)
621

    
622
    for filename in temp_files:
623
      utils.RemoveFile(filename)
624

    
625
  def StartInstance(self, instance, block_devices):
626
    """Start an instance.
627

628
    """
629
    self._CheckDown(instance.name)
630
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices)
631
    self._SaveKVMRuntime(instance, kvm_runtime)
632
    self._ExecuteKVMRuntime(instance, kvm_runtime)
633

    
634
  def _CallMonitorCommand(self, instance_name, command):
635
    """Invoke a command on the instance monitor.
636

637
    """
638
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
639
             (utils.ShellQuote(command),
640
              constants.SOCAT_PATH,
641
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
642
    result = utils.RunCmd(socat)
643
    if result.failed:
644
      msg = ("Failed to send command '%s' to instance %s."
645
             " output: %s, error: %s, fail_reason: %s" %
646
             (command, instance_name,
647
              result.stdout, result.stderr, result.fail_reason))
648
      raise errors.HypervisorError(msg)
649

    
650
    return result
651

    
652
  def StopInstance(self, instance, force=False, retry=False, name=None):
653
    """Stop an instance.
654

655
    """
656
    if name is not None and not force:
657
      raise errors.HypervisorError("Cannot shutdown cleanly by name only")
658
    if name is None:
659
      name = instance.name
660
      acpi = instance.hvparams[constants.HV_ACPI]
661
    else:
662
      acpi = False
663
    _, pid, alive = self._InstancePidAlive(name)
664
    if pid > 0 and alive:
665
      if force or not acpi:
666
        utils.KillProcess(pid)
667
      else:
668
        self._CallMonitorCommand(name, 'system_powerdown')
669

    
670
  def CleanupInstance(self, instance_name):
671
    """Cleanup after a stopped instance
672

673
    """
674
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
675
    if pid > 0 and alive:
676
      raise errors.HypervisorError("Cannot cleanup a live instance")
677
    self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
678

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

682
    """
683
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
684
    # socket the instance will stop, but now power up again. So we'll resort
685
    # to shutdown and restart.
686
    _, _, alive = self._InstancePidAlive(instance.name)
687
    if not alive:
688
      raise errors.HypervisorError("Failed to reboot instance %s:"
689
                                   " not running" % instance.name)
690
    # StopInstance will delete the saved KVM runtime so:
691
    # ...first load it...
692
    kvm_runtime = self._LoadKVMRuntime(instance)
693
    # ...now we can safely call StopInstance...
694
    if not self.StopInstance(instance):
695
      self.StopInstance(instance, force=True)
696
    # ...and finally we can save it again, and execute it...
697
    self._SaveKVMRuntime(instance, kvm_runtime)
698
    self._ExecuteKVMRuntime(instance, kvm_runtime)
699

    
700
  def MigrationInfo(self, instance):
701
    """Get instance information to perform a migration.
702

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

708
    """
709
    return self._ReadKVMRuntime(instance.name)
710

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

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

721
    """
722
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
723
    incoming_address = (target, instance.hvparams[constants.HV_MIGRATION_PORT])
724
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
725

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

729
    Stop the incoming mode KVM.
730

731
    @type instance: L{objects.Instance}
732
    @param instance: instance whose migration is being aborted
733

734
    """
735
    if success:
736
      self._WriteKVMRuntime(instance.name, info)
737
    else:
738
      self.StopInstance(instance, force=True)
739

    
740
  def MigrateInstance(self, instance, target, live):
741
    """Migrate an instance to a target node.
742

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

746
    @type instance: L{objects.Instance}
747
    @param instance: the instance to be migrated
748
    @type target: string
749
    @param target: ip address of the target node
750
    @type live: boolean
751
    @param live: perform a live migration
752

753
    """
754
    instance_name = instance.name
755
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
756
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
757
    if not alive:
758
      raise errors.HypervisorError("Instance not running, cannot migrate")
759

    
760
    if not utils.TcpPing(target, port, live_port_needed=True):
761
      raise errors.HypervisorError("Remote host %s not listening on port"
762
                                   " %s, cannot migrate" % (target, port))
763

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

    
767
    migrate_command = 'migrate -d tcp:%s:%s' % (target, port)
768
    self._CallMonitorCommand(instance_name, migrate_command)
769

    
770
    info_command = 'info migrate'
771
    done = False
772
    broken_answers = 0
773
    while not done:
774
      result = self._CallMonitorCommand(instance_name, info_command)
775
      match = self._MIGRATION_STATUS_RE.search(result.stdout)
776
      if not match:
777
        broken_answers += 1
778
        if not result.stdout:
779
          logging.info("KVM: empty 'info migrate' result")
780
        else:
781
          logging.warning("KVM: unknown 'info migrate' result: %s",
782
                          result.stdout)
783
        time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
784
      else:
785
        status = match.group(1)
786
        if status == 'completed':
787
          done = True
788
        elif status == 'active':
789
          # reset the broken answers count
790
          broken_answers = 0
791
          time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
792
        elif status == 'failed' or status == 'cancelled':
793
          if not live:
794
            self._CallMonitorCommand(instance_name, 'cont')
795
          raise errors.HypervisorError("Migration %s at the kvm level" %
796
                                       status)
797
        else:
798
          logging.warning("KVM: unknown migration status '%s'", status)
799
          broken_answers += 1
800
          time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
801
      if broken_answers >= self._MIGRATION_INFO_MAX_BAD_ANSWERS:
802
        raise errors.HypervisorError("Too many 'info migrate' broken answers")
803

    
804
    utils.KillProcess(pid)
805
    self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
806

    
807
  def GetNodeInfo(self):
808
    """Return information about the node.
809

810
    This is just a wrapper over the base GetLinuxNodeInfo method.
811

812
    @return: a dict with the following keys (values in MiB):
813
          - memory_total: the total memory size on the node
814
          - memory_free: the available memory on the node for instances
815
          - memory_dom0: the memory used by the node itself, if available
816

817
    """
818
    return self.GetLinuxNodeInfo()
819

    
820
  @classmethod
821
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
822
    """Return a command for connecting to the console of an instance.
823

824
    """
825
    if hvparams[constants.HV_SERIAL_CONSOLE]:
826
      shell_command = ("%s STDIO,%s UNIX-CONNECT:%s" %
827
                       (constants.SOCAT_PATH, cls._SocatUnixConsoleParams(),
828
                        utils.ShellQuote(cls._InstanceSerial(instance.name))))
829
    else:
830
      shell_command = "echo 'No serial shell for instance %s'" % instance.name
831

    
832
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
833
    if vnc_bind_address:
834
      if instance.network_port > constants.VNC_BASE_PORT:
835
        display = instance.network_port - constants.VNC_BASE_PORT
836
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
837
                       " (display: %d)'" % (vnc_bind_address,
838
                                            instance.network_port,
839
                                            display))
840
        shell_command = "%s; %s" % (vnc_command, shell_command)
841

    
842
    return shell_command
843

    
844
  def Verify(self):
845
    """Verify the hypervisor.
846

847
    Check that the binary exists.
848

849
    """
850
    if not os.path.exists(constants.KVM_PATH):
851
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
852
    if not os.path.exists(constants.SOCAT_PATH):
853
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
854

    
855

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

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

864
    """
865
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
866

    
867
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
868
    if kernel_path:
869
      if not hvparams[constants.HV_ROOT_PATH]:
870
        raise errors.HypervisorError("Need a root partition for the instance,"
871
                                     " if a kernel is defined")
872

    
873
    if (hvparams[constants.HV_VNC_X509_VERIFY] and
874
        not hvparams[constants.HV_VNC_X509]):
875
      raise errors.HypervisorError("%s must be defined, if %s is" %
876
                                   (constants.HV_VNC_X509,
877
                                    constants.HV_VNC_X509_VERIFY))
878

    
879
    boot_order = hvparams[constants.HV_BOOT_ORDER]
880
    if (boot_order == constants.HT_BO_CDROM and
881
        not hvparams[constants.HV_CDROM_IMAGE_PATH]):
882
      raise errors.HypervisorError("Cannot boot from cdrom without an"
883
                                   " ISO path")
884

    
885
    security_model = hvparams[constants.HV_SECURITY_MODEL]
886
    if security_model == constants.HT_SM_USER:
887
      if not hvparams[constants.HV_SECURITY_DOMAIN]:
888
        raise errors.HypervisorError("A security domain (user to run kvm as)"
889
                                     " must be specified")
890
    elif (security_model == constants.HT_SM_NONE or
891
          security_model == constants.HT_SM_POOL):
892
      if hvparams[constants.HV_SECURITY_DOMAIN]:
893
        raise errors.HypervisorError("Cannot have a security domain when the"
894
                                     " security model is 'none' or 'pool'")
895
    if security_model == constants.HT_SM_POOL:
896
      raise errors.HypervisorError("Security model pool is not supported yet")
897

    
898
  @classmethod
899
  def ValidateParameters(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).ValidateParameters(hvparams)
908

    
909
    security_model = hvparams[constants.HV_SECURITY_MODEL]
910
    if security_model == constants.HT_SM_USER:
911
      username = hvparams[constants.HV_SECURITY_DOMAIN]
912
      try:
913
        pwd.getpwnam(username)
914
      except KeyError:
915
        raise errors.HypervisorError("Unknown security domain user %s"
916
                                     % username)
917

    
918
  @classmethod
919
  def PowercycleNode(cls):
920
    """KVM powercycle, just a wrapper over Linux powercycle.
921

922
    """
923
    cls.LinuxPowercycle()