Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 74a4c11b

History | View | Annotate | Download (33.1 kB)

1
#
2
#
3

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

    
21

    
22
"""KVM hypervisor
23

24
"""
25

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

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

    
44

    
45
class KVMHypervisor(hv_base.BaseHypervisor):
46
  """KVM hypervisor interface"""
47

    
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
        if disk_type != constants.HT_DISK_IDE:
428
          boot_val = ',boot=on'
429
        else:
430
          boot_val = ''
431
        # We only boot from the first disk
432
        boot_disk = False
433
      else:
434
        boot_val = ''
435

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

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

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

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

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

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

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

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

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

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

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

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

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

    
533
    return (kvm_cmd, kvm_nics, hvparams)
534

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
602
    temp_files = []
603

    
604
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
605

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

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

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

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

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

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

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

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

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

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

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

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

    
689
    return result
690

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

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

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

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

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

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

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

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

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

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

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

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

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

768
    Stop the incoming mode KVM.
769

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
881
    return shell_command
882

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

886
    Check that the binary exists.
887

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

    
894

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

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

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

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

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

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

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

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

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

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

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

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

959
    """
960
    cls.LinuxPowercycle()