Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 87e510fd

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
535
    return (kvm_cmd, kvm_nics, hvparams)
536

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
604
    temp_files = []
605

    
606
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
607

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

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

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

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

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

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

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

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

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

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

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

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

    
691
    return result
692

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

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

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

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

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

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

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

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

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

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

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

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

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

770
    Stop the incoming mode KVM.
771

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
883
    return shell_command
884

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

888
    Check that the binary exists.
889

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

    
896

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

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

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

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

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

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

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

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

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

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

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

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

961
    """
962
    cls.LinuxPowercycle()