Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 760570a8

History | View | Annotate | Download (28 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
from cStringIO import StringIO
33

    
34
from ganeti import utils
35
from ganeti import constants
36
from ganeti import errors
37
from ganeti import serializer
38
from ganeti import objects
39
from ganeti.hypervisor import hv_base
40

    
41

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

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

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

    
81
  _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)',
82
                                    re.M | re.I)
83
  _MIGRATION_INFO_MAX_BAD_ANSWERS = 5
84
  _MIGRATION_INFO_RETRY_DELAY = 2
85

    
86
  _KVM_NETWORK_SCRIPT = constants.SYSCONFDIR + "/ganeti/kvm-vif-bridge"
87

    
88
  ANCILLARY_FILES = [
89
    _KVM_NETWORK_SCRIPT,
90
    ]
91

    
92
  def __init__(self):
93
    hv_base.BaseHypervisor.__init__(self)
94
    # Let's make sure the directories we need exist, even if the RUN_DIR lives
95
    # in a tmpfs filesystem or has been otherwise wiped out.
96
    dirs = [(dname, constants.RUN_DIRS_MODE) for dname in self._DIRS]
97
    utils.EnsureDirs(dirs)
98

    
99
  @classmethod
100
  def _InstancePidFile(cls, instance_name):
101
    """Returns the instance pidfile.
102

103
    """
104
    return utils.PathJoin(cls._PIDS_DIR, instance_name)
105

    
106
  def _InstancePidAlive(self, instance_name):
107
    """Returns the instance pid and pidfile
108

109
    """
110
    pidfile = self._InstancePidFile(instance_name)
111
    pid = utils.ReadPidFile(pidfile)
112
    alive = utils.IsProcessAlive(pid)
113

    
114
    return (pidfile, pid, alive)
115

    
116
  def _CheckDown(self, instance_name):
117
    """Raises an error unless the given instance is down.
118

119
    """
120
    alive = self._InstancePidAlive(instance_name)[2]
121
    if alive:
122
      raise errors.HypervisorError("Failed to start instance %s: %s" %
123
                                   (instance_name, "already running"))
124

    
125
  @classmethod
126
  def _InstanceMonitor(cls, instance_name):
127
    """Returns the instance monitor socket name
128

129
    """
130
    return utils.PathJoin(cls._CTRL_DIR, "%s.monitor" % instance_name)
131

    
132
  @classmethod
133
  def _InstanceSerial(cls, instance_name):
134
    """Returns the instance serial socket name
135

136
    """
137
    return utils.PathJoin(cls._CTRL_DIR, "%s.serial" % instance_name)
138

    
139
  @staticmethod
140
  def _SocatUnixConsoleParams():
141
    """Returns the correct parameters for socat
142

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

145
    """
146
    if constants.SOCAT_USE_ESCAPE:
147
      return "raw,echo=0,escape=%s" % constants.SOCAT_ESCAPE_CODE
148
    else:
149
      return "echo=0,icanon=0"
150

    
151
  @classmethod
152
  def _InstanceKVMRuntime(cls, instance_name):
153
    """Returns the instance KVM runtime filename
154

155
    """
156
    return utils.PathJoin(cls._CONF_DIR, "%s.runtime" % instance_name)
157

    
158
  @classmethod
159
  def _RemoveInstanceRuntimeFiles(cls, pidfile, instance_name):
160
    """Removes an instance's rutime sockets/files.
161

162
    """
163
    utils.RemoveFile(pidfile)
164
    utils.RemoveFile(cls._InstanceMonitor(instance_name))
165
    utils.RemoveFile(cls._InstanceSerial(instance_name))
166
    utils.RemoveFile(cls._InstanceKVMRuntime(instance_name))
167

    
168
  def _WriteNetScript(self, instance, seq, nic):
169
    """Write a script to connect a net interface to the proper bridge.
170

171
    This can be used by any qemu-type hypervisor.
172

173
    @param instance: instance we're acting on
174
    @type instance: instance object
175
    @param seq: nic sequence number
176
    @type seq: int
177
    @param nic: nic we're acting on
178
    @type nic: nic object
179
    @return: netscript file name
180
    @rtype: string
181

182
    """
183
    script = StringIO()
184
    script.write("#!/bin/sh\n")
185
    script.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
186
    script.write("PATH=$PATH:/sbin:/usr/sbin\n")
187
    script.write("export INSTANCE=%s\n" % instance.name)
188
    script.write("export MAC=%s\n" % nic.mac)
189
    if nic.ip:
190
      script.write("export IP=%s\n" % nic.ip)
191
    script.write("export MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
192
    if nic.nicparams[constants.NIC_LINK]:
193
      script.write("export LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
194
    if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
195
      script.write("export BRIDGE=%s\n" % nic.nicparams[constants.NIC_LINK])
196
    script.write("export INTERFACE=$1\n")
197
    # TODO: make this configurable at ./configure time
198
    script.write("if [ -x '%s' ]; then\n" % self._KVM_NETWORK_SCRIPT)
199
    script.write("  # Execute the user-specific vif file\n")
200
    script.write("  %s\n" % self._KVM_NETWORK_SCRIPT)
201
    script.write("else\n")
202
    script.write("  ifconfig $INTERFACE 0.0.0.0 up\n")
203
    if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
204
      script.write("  # Connect the interface to the bridge\n")
205
      script.write("  brctl addif $BRIDGE $INTERFACE\n")
206
    elif nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_ROUTED:
207
      if not nic.ip:
208
        raise errors.HypervisorError("nic/%d is routed, but has no ip." % seq)
209
      script.write("  # Route traffic targeted at the IP to the interface\n")
210
      if nic.nicparams[constants.NIC_LINK]:
211
        script.write("  while ip rule del dev $INTERFACE; do :; done\n")
212
        script.write("  ip rule add dev $INTERFACE table $LINK\n")
213
        script.write("  ip route replace $IP table $LINK proto static"
214
                     " dev $INTERFACE\n")
215
      else:
216
        script.write("  ip route replace $IP proto static"
217
                     " dev $INTERFACE\n")
218
      interface_v4_conf = "/proc/sys/net/ipv4/conf/$INTERFACE"
219
      interface_v6_conf = "/proc/sys/net/ipv6/conf/$INTERFACE"
220
      script.write("  if [ -d %s ]; then\n" % interface_v4_conf)
221
      script.write("    echo 1 > %s/proxy_arp\n" % interface_v4_conf)
222
      script.write("    echo 1 > %s/forwarding\n" % interface_v4_conf)
223
      script.write("  fi\n")
224
      script.write("  if [ -d %s ]; then\n" % interface_v6_conf)
225
      script.write("    echo 1 > %s/proxy_ndp\n" % interface_v6_conf)
226
      script.write("    echo 1 > %s/forwarding\n" % interface_v6_conf)
227
      script.write("  fi\n")
228
    script.write("fi\n\n")
229
    # As much as we'd like to put this in our _ROOT_DIR, that will happen to be
230
    # mounted noexec sometimes, so we'll have to find another place.
231
    (tmpfd, tmpfile_name) = tempfile.mkstemp()
232
    tmpfile = os.fdopen(tmpfd, 'w')
233
    try:
234
      tmpfile.write(script.getvalue())
235
    finally:
236
      tmpfile.close()
237
    os.chmod(tmpfile_name, 0755)
238
    return tmpfile_name
239

    
240
  def ListInstances(self):
241
    """Get the list of running instances.
242

243
    We can do this by listing our live instances directory and
244
    checking whether the associated kvm process is still alive.
245

246
    """
247
    result = []
248
    for name in os.listdir(self._PIDS_DIR):
249
      filename = utils.PathJoin(self._PIDS_DIR, name)
250
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
251
        result.append(name)
252
    return result
253

    
254
  def GetInstanceInfo(self, instance_name):
255
    """Get instance properties.
256

257
    @param instance_name: the instance name
258

259
    @return: tuple (name, id, memory, vcpus, stat, times)
260

261
    """
262
    _, pid, alive = self._InstancePidAlive(instance_name)
263
    if not alive:
264
      return None
265

    
266
    cmdline_file = utils.PathJoin("/proc", str(pid), "cmdline")
267
    try:
268
      cmdline = utils.ReadFile(cmdline_file)
269
    except EnvironmentError, err:
270
      raise errors.HypervisorError("Failed to list instance %s: %s" %
271
                                   (instance_name, err))
272

    
273
    memory = 0
274
    vcpus = 0
275
    stat = "---b-"
276
    times = "0"
277

    
278
    arg_list = cmdline.split('\x00')
279
    while arg_list:
280
      arg =  arg_list.pop(0)
281
      if arg == '-m':
282
        memory = int(arg_list.pop(0))
283
      elif arg == '-smp':
284
        vcpus = int(arg_list.pop(0))
285

    
286
    return (instance_name, pid, memory, vcpus, stat, times)
287

    
288
  def GetAllInstancesInfo(self):
289
    """Get properties of all instances.
290

291
    @return: list of tuples (name, id, memory, vcpus, stat, times)
292

293
    """
294
    data = []
295
    for name in os.listdir(self._PIDS_DIR):
296
      filename = utils.PathJoin(self._PIDS_DIR, name)
297
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
298
        try:
299
          info = self.GetInstanceInfo(name)
300
        except errors.HypervisorError:
301
          continue
302
        if info:
303
          data.append(info)
304

    
305
    return data
306

    
307
  def _GenerateKVMRuntime(self, instance, block_devices):
308
    """Generate KVM information to start an instance.
309

310
    """
311
    pidfile  = self._InstancePidFile(instance.name)
312
    kvm = constants.KVM_PATH
313
    kvm_cmd = [kvm]
314
    kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
315
    kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
316
    kvm_cmd.extend(['-pidfile', pidfile])
317
    # used just by the vnc server, if enabled
318
    kvm_cmd.extend(['-name', instance.name])
319
    kvm_cmd.extend(['-daemonize'])
320
    if not instance.hvparams[constants.HV_ACPI]:
321
      kvm_cmd.extend(['-no-acpi'])
322

    
323
    hvp = instance.hvparams
324
    boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
325
    boot_cdrom = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_CDROM
326
    boot_network = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_NETWORK
327

    
328
    if boot_network:
329
      kvm_cmd.extend(['-boot', 'n'])
330

    
331
    disk_type = hvp[constants.HV_DISK_TYPE]
332
    if disk_type == constants.HT_DISK_PARAVIRTUAL:
333
      if_val = ',if=virtio'
334
    else:
335
      if_val = ',if=%s' % disk_type
336
    # Cache mode
337
    disk_cache = hvp[constants.HV_DISK_CACHE]
338
    if disk_cache != constants.HT_CACHE_DEFAULT:
339
      cache_val = ",cache=%s" % disk_cache
340
    else:
341
      cache_val = ""
342
    for cfdev, dev_path in block_devices:
343
      if cfdev.mode != constants.DISK_RDWR:
344
        raise errors.HypervisorError("Instance has read-only disks which"
345
                                     " are not supported by KVM")
346
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
347
      if boot_disk:
348
        kvm_cmd.extend(['-boot', 'c'])
349
        boot_val = ',boot=on'
350
        # We only boot from the first disk
351
        boot_disk = False
352
      else:
353
        boot_val = ''
354

    
355
      drive_val = 'file=%s,format=raw%s%s%s' % (dev_path, if_val, boot_val,
356
                                                cache_val)
357
      kvm_cmd.extend(['-drive', drive_val])
358

    
359
    iso_image = hvp[constants.HV_CDROM_IMAGE_PATH]
360
    if iso_image:
361
      options = ',format=raw,media=cdrom'
362
      if boot_cdrom:
363
        kvm_cmd.extend(['-boot', 'd'])
364
        options = '%s,boot=on' % options
365
      else:
366
        options = '%s,if=virtio' % options
367
      drive_val = 'file=%s%s' % (iso_image, options)
368
      kvm_cmd.extend(['-drive', drive_val])
369

    
370
    kernel_path = hvp[constants.HV_KERNEL_PATH]
371
    if kernel_path:
372
      kvm_cmd.extend(['-kernel', kernel_path])
373
      initrd_path = hvp[constants.HV_INITRD_PATH]
374
      if initrd_path:
375
        kvm_cmd.extend(['-initrd', initrd_path])
376
      root_append = ['root=%s' % hvp[constants.HV_ROOT_PATH],
377
                     hvp[constants.HV_KERNEL_ARGS]]
378
      if hvp[constants.HV_SERIAL_CONSOLE]:
379
        root_append.append('console=ttyS0,38400')
380
      kvm_cmd.extend(['-append', ' '.join(root_append)])
381

    
382
    mouse_type = hvp[constants.HV_USB_MOUSE]
383
    if mouse_type:
384
      kvm_cmd.extend(['-usb'])
385
      kvm_cmd.extend(['-usbdevice', mouse_type])
386

    
387
    vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS]
388
    if vnc_bind_address:
389
      if utils.IsValidIP(vnc_bind_address):
390
        if instance.network_port > constants.VNC_BASE_PORT:
391
          display = instance.network_port - constants.VNC_BASE_PORT
392
          if vnc_bind_address == '0.0.0.0':
393
            vnc_arg = ':%d' % (display)
394
          else:
395
            vnc_arg = '%s:%d' % (vnc_bind_address, display)
396
        else:
397
          logging.error("Network port is not a valid VNC display (%d < %d)."
398
                        " Not starting VNC", instance.network_port,
399
                        constants.VNC_BASE_PORT)
400
          vnc_arg = 'none'
401

    
402
        # Only allow tls and other option when not binding to a file, for now.
403
        # kvm/qemu gets confused otherwise about the filename to use.
404
        vnc_append = ''
405
        if hvp[constants.HV_VNC_TLS]:
406
          vnc_append = '%s,tls' % vnc_append
407
          if hvp[constants.HV_VNC_X509_VERIFY]:
408
            vnc_append = '%s,x509verify=%s' % (vnc_append,
409
                                               hvp[constants.HV_VNC_X509])
410
          elif hvp[constants.HV_VNC_X509]:
411
            vnc_append = '%s,x509=%s' % (vnc_append,
412
                                         hvp[constants.HV_VNC_X509])
413
        if hvp[constants.HV_VNC_PASSWORD_FILE]:
414
          vnc_append = '%s,password' % vnc_append
415

    
416
        vnc_arg = '%s%s' % (vnc_arg, vnc_append)
417

    
418
      else:
419
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
420

    
421
      kvm_cmd.extend(['-vnc', vnc_arg])
422
    else:
423
      kvm_cmd.extend(['-nographic'])
424

    
425
    monitor_dev = ("unix:%s,server,nowait" %
426
                   self._InstanceMonitor(instance.name))
427
    kvm_cmd.extend(['-monitor', monitor_dev])
428
    if hvp[constants.HV_SERIAL_CONSOLE]:
429
      serial_dev = ('unix:%s,server,nowait' %
430
                    self._InstanceSerial(instance.name))
431
      kvm_cmd.extend(['-serial', serial_dev])
432
    else:
433
      kvm_cmd.extend(['-serial', 'none'])
434

    
435
    if hvp[constants.HV_USE_LOCALTIME]:
436
      kvm_cmd.extend(['-localtime'])
437

    
438
    # Save the current instance nics, but defer their expansion as parameters,
439
    # as we'll need to generate executable temp files for them.
440
    kvm_nics = instance.nics
441
    hvparams = hvp
442

    
443
    return (kvm_cmd, kvm_nics, hvparams)
444

    
445
  def _WriteKVMRuntime(self, instance_name, data):
446
    """Write an instance's KVM runtime
447

448
    """
449
    try:
450
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
451
                      data=data)
452
    except EnvironmentError, err:
453
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
454

    
455
  def _ReadKVMRuntime(self, instance_name):
456
    """Read an instance's KVM runtime
457

458
    """
459
    try:
460
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
461
    except EnvironmentError, err:
462
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
463
    return file_content
464

    
465
  def _SaveKVMRuntime(self, instance, kvm_runtime):
466
    """Save an instance's KVM runtime
467

468
    """
469
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
470
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
471
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
472
    self._WriteKVMRuntime(instance.name, serialized_form)
473

    
474
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
475
    """Load an instance's KVM runtime
476

477
    """
478
    if not serialized_runtime:
479
      serialized_runtime = self._ReadKVMRuntime(instance.name)
480
    loaded_runtime = serializer.Load(serialized_runtime)
481
    kvm_cmd, serialized_nics, hvparams = loaded_runtime
482
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
483
    return (kvm_cmd, kvm_nics, hvparams)
484

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

488
    @type incoming: tuple of strings
489
    @param incoming: (target_host_ip, port)
490

491
    """
492
    hvp = instance.hvparams
493
    name = instance.name
494
    self._CheckDown(name)
495

    
496
    temp_files = []
497

    
498
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
499

    
500
    if not kvm_nics:
501
      kvm_cmd.extend(['-net', 'none'])
502
    else:
503
      nic_type = hvparams[constants.HV_NIC_TYPE]
504
      if nic_type == constants.HT_NIC_PARAVIRTUAL:
505
        nic_model = "model=virtio"
506
      else:
507
        nic_model = "model=%s" % nic_type
508

    
509
      for nic_seq, nic in enumerate(kvm_nics):
510
        nic_val = "nic,vlan=%s,macaddr=%s,%s" % (nic_seq, nic.mac, nic_model)
511
        script = self._WriteNetScript(instance, nic_seq, nic)
512
        kvm_cmd.extend(['-net', nic_val])
513
        kvm_cmd.extend(['-net', 'tap,vlan=%s,script=%s' % (nic_seq, script)])
514
        temp_files.append(script)
515

    
516
    if incoming:
517
      target, port = incoming
518
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
519

    
520
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
521
    vnc_pwd = None
522
    if vnc_pwd_file:
523
      try:
524
        vnc_pwd = utils.ReadFile(vnc_pwd_file)
525
      except EnvironmentError, err:
526
        raise errors.HypervisorError("Failed to open VNC password file %s: %s"
527
                                     % (vnc_pwd_file, err))
528

    
529
    result = utils.RunCmd(kvm_cmd)
530
    if result.failed:
531
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
532
                                   (name, result.fail_reason, result.output))
533

    
534
    if not self._InstancePidAlive(name)[2]:
535
      raise errors.HypervisorError("Failed to start instance %s" % name)
536

    
537
    if vnc_pwd:
538
      change_cmd = 'change vnc password %s' % vnc_pwd
539
      self._CallMonitorCommand(instance.name, change_cmd)
540

    
541
    for filename in temp_files:
542
      utils.RemoveFile(filename)
543

    
544
  def StartInstance(self, instance, block_devices):
545
    """Start an instance.
546

547
    """
548
    self._CheckDown(instance.name)
549
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices)
550
    self._SaveKVMRuntime(instance, kvm_runtime)
551
    self._ExecuteKVMRuntime(instance, kvm_runtime)
552

    
553
  def _CallMonitorCommand(self, instance_name, command):
554
    """Invoke a command on the instance monitor.
555

556
    """
557
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
558
             (utils.ShellQuote(command),
559
              constants.SOCAT_PATH,
560
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
561
    result = utils.RunCmd(socat)
562
    if result.failed:
563
      msg = ("Failed to send command '%s' to instance %s."
564
             " output: %s, error: %s, fail_reason: %s" %
565
             (command, instance_name,
566
              result.stdout, result.stderr, result.fail_reason))
567
      raise errors.HypervisorError(msg)
568

    
569
    return result
570

    
571
  def StopInstance(self, instance, force=False, retry=False):
572
    """Stop an instance.
573

574
    """
575
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
576
    if pid > 0 and alive:
577
      if force or not instance.hvparams[constants.HV_ACPI]:
578
        utils.KillProcess(pid)
579
      else:
580
        self._CallMonitorCommand(instance.name, 'system_powerdown')
581

    
582
    if not utils.IsProcessAlive(pid):
583
      self._RemoveInstanceRuntimeFiles(pidfile, instance.name)
584
      return True
585
    else:
586
      return False
587

    
588
  def RebootInstance(self, instance):
589
    """Reboot an instance.
590

591
    """
592
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
593
    # socket the instance will stop, but now power up again. So we'll resort
594
    # to shutdown and restart.
595
    _, _, alive = self._InstancePidAlive(instance.name)
596
    if not alive:
597
      raise errors.HypervisorError("Failed to reboot instance %s:"
598
                                   " not running" % instance.name)
599
    # StopInstance will delete the saved KVM runtime so:
600
    # ...first load it...
601
    kvm_runtime = self._LoadKVMRuntime(instance)
602
    # ...now we can safely call StopInstance...
603
    if not self.StopInstance(instance):
604
      self.StopInstance(instance, force=True)
605
    # ...and finally we can save it again, and execute it...
606
    self._SaveKVMRuntime(instance, kvm_runtime)
607
    self._ExecuteKVMRuntime(instance, kvm_runtime)
608

    
609
  def MigrationInfo(self, instance):
610
    """Get instance information to perform a migration.
611

612
    @type instance: L{objects.Instance}
613
    @param instance: instance to be migrated
614
    @rtype: string
615
    @return: content of the KVM runtime file
616

617
    """
618
    return self._ReadKVMRuntime(instance.name)
619

    
620
  def AcceptInstance(self, instance, info, target):
621
    """Prepare to accept an instance.
622

623
    @type instance: L{objects.Instance}
624
    @param instance: instance to be accepted
625
    @type info: string
626
    @param info: content of the KVM runtime file on the source node
627
    @type target: string
628
    @param target: target host (usually ip), on this node
629

630
    """
631
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
632
    incoming_address = (target, instance.hvparams[constants.HV_MIGRATION_PORT])
633
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
634

    
635
  def FinalizeMigration(self, instance, info, success):
636
    """Finalize an instance migration.
637

638
    Stop the incoming mode KVM.
639

640
    @type instance: L{objects.Instance}
641
    @param instance: instance whose migration is being aborted
642

643
    """
644
    if success:
645
      self._WriteKVMRuntime(instance.name, info)
646
    else:
647
      self.StopInstance(instance, force=True)
648

    
649
  def MigrateInstance(self, instance, target, live):
650
    """Migrate an instance to a target node.
651

652
    The migration will not be attempted if the instance is not
653
    currently running.
654

655
    @type instance: L{objects.Instance}
656
    @param instance: the instance to be migrated
657
    @type target: string
658
    @param target: ip address of the target node
659
    @type live: boolean
660
    @param live: perform a live migration
661

662
    """
663
    instance_name = instance.name
664
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
665
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
666
    if not alive:
667
      raise errors.HypervisorError("Instance not running, cannot migrate")
668

    
669
    if not utils.TcpPing(target, port, live_port_needed=True):
670
      raise errors.HypervisorError("Remote host %s not listening on port"
671
                                   " %s, cannot migrate" % (target, port))
672

    
673
    if not live:
674
      self._CallMonitorCommand(instance_name, 'stop')
675

    
676
    migrate_command = 'migrate -d tcp:%s:%s' % (target, port)
677
    self._CallMonitorCommand(instance_name, migrate_command)
678

    
679
    info_command = 'info migrate'
680
    done = False
681
    broken_answers = 0
682
    while not done:
683
      result = self._CallMonitorCommand(instance_name, info_command)
684
      match = self._MIGRATION_STATUS_RE.search(result.stdout)
685
      if not match:
686
        broken_answers += 1
687
        if not result.stdout:
688
          logging.info("KVM: empty 'info migrate' result")
689
        else:
690
          logging.warning("KVM: unknown 'info migrate' result: %s",
691
                          result.stdout)
692
        time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
693
      else:
694
        status = match.group(1)
695
        if status == 'completed':
696
          done = True
697
        elif status == 'active':
698
          # reset the broken answers count
699
          broken_answers = 0
700
          time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
701
        elif status == 'failed' or status == 'cancelled':
702
          if not live:
703
            self._CallMonitorCommand(instance_name, 'cont')
704
          raise errors.HypervisorError("Migration %s at the kvm level" %
705
                                       status)
706
        else:
707
          logging.warning("KVM: unknown migration status '%s'", status)
708
          broken_answers += 1
709
          time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
710
      if broken_answers >= self._MIGRATION_INFO_MAX_BAD_ANSWERS:
711
        raise errors.HypervisorError("Too many 'info migrate' broken answers")
712

    
713
    utils.KillProcess(pid)
714
    self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
715

    
716
  def GetNodeInfo(self):
717
    """Return information about the node.
718

719
    This is just a wrapper over the base GetLinuxNodeInfo method.
720

721
    @return: a dict with the following keys (values in MiB):
722
          - memory_total: the total memory size on the node
723
          - memory_free: the available memory on the node for instances
724
          - memory_dom0: the memory used by the node itself, if available
725

726
    """
727
    return self.GetLinuxNodeInfo()
728

    
729
  @classmethod
730
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
731
    """Return a command for connecting to the console of an instance.
732

733
    """
734
    if hvparams[constants.HV_SERIAL_CONSOLE]:
735
      shell_command = ("%s STDIO,%s UNIX-CONNECT:%s" %
736
                       (constants.SOCAT_PATH, cls._SocatUnixConsoleParams(),
737
                        utils.ShellQuote(cls._InstanceSerial(instance.name))))
738
    else:
739
      shell_command = "echo 'No serial shell for instance %s'" % instance.name
740

    
741
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
742
    if vnc_bind_address:
743
      if instance.network_port > constants.VNC_BASE_PORT:
744
        display = instance.network_port - constants.VNC_BASE_PORT
745
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
746
                       " (display: %d)'" % (vnc_bind_address,
747
                                            instance.network_port,
748
                                            display))
749
        shell_command = "%s; %s" % (vnc_command, shell_command)
750

    
751
    return shell_command
752

    
753
  def Verify(self):
754
    """Verify the hypervisor.
755

756
    Check that the binary exists.
757

758
    """
759
    if not os.path.exists(constants.KVM_PATH):
760
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
761
    if not os.path.exists(constants.SOCAT_PATH):
762
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
763

    
764

    
765
  @classmethod
766
  def CheckParameterSyntax(cls, hvparams):
767
    """Check the given parameters for validity.
768

769
    @type hvparams:  dict
770
    @param hvparams: dictionary with parameter names/value
771
    @raise errors.HypervisorError: when a parameter is not valid
772

773
    """
774
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
775

    
776
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
777
    if kernel_path:
778
      if not hvparams[constants.HV_ROOT_PATH]:
779
        raise errors.HypervisorError("Need a root partition for the instance,"
780
                                     " if a kernel is defined")
781

    
782
    if (hvparams[constants.HV_VNC_X509_VERIFY] and
783
        not hvparams[constants.HV_VNC_X509]):
784
      raise errors.HypervisorError("%s must be defined, if %s is" %
785
                                   (constants.HV_VNC_X509,
786
                                    constants.HV_VNC_X509_VERIFY))
787

    
788
    boot_order = hvparams[constants.HV_BOOT_ORDER]
789

    
790
    if (boot_order == constants.HT_BO_CDROM and
791
        not hvparams[constants.HV_CDROM_IMAGE_PATH]):
792
      raise errors.HypervisorError("Cannot boot from cdrom without an"
793
                                   " ISO path")
794
    if (boot_order == constants.HT_BO_NETWORK and
795
        hvparams[constants.HV_NIC_TYPE] == constants.HT_NIC_PARAVIRTUAL):
796
      raise errors.HypervisorError("Cannot boot from a paravirtual NIC. Please"
797
                                   " change the NIC type.")
798

    
799
  @classmethod
800
  def PowercycleNode(cls):
801
    """KVM powercycle, just a wrapper over Linux powercycle.
802

803
    """
804
    cls.LinuxPowercycle()