Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 58d38b02

History | View | Annotate | Download (26.8 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
    }
76

    
77
  _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)',
78
                                    re.M | re.I)
79

    
80
  _KVM_NETWORK_SCRIPT = constants.SYSCONFDIR + "/ganeti/kvm-vif-bridge"
81

    
82
  ANCILLARY_FILES = [
83
    _KVM_NETWORK_SCRIPT,
84
    ]
85

    
86
  def __init__(self):
87
    hv_base.BaseHypervisor.__init__(self)
88
    # Let's make sure the directories we need exist, even if the RUN_DIR lives
89
    # in a tmpfs filesystem or has been otherwise wiped out.
90
    dirs = [(dname, constants.RUN_DIRS_MODE) for dname in self._DIRS]
91
    utils.EnsureDirs(dirs)
92

    
93
  def _InstancePidAlive(self, instance_name):
94
    """Returns the instance pid and pidfile
95

96
    """
97
    pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
98
    pid = utils.ReadPidFile(pidfile)
99
    alive = utils.IsProcessAlive(pid)
100

    
101
    return (pidfile, pid, alive)
102

    
103
  @classmethod
104
  def _InstanceMonitor(cls, instance_name):
105
    """Returns the instance monitor socket name
106

107
    """
108
    return '%s/%s.monitor' % (cls._CTRL_DIR, instance_name)
109

    
110
  @classmethod
111
  def _InstanceSerial(cls, instance_name):
112
    """Returns the instance serial socket name
113

114
    """
115
    return '%s/%s.serial' % (cls._CTRL_DIR, instance_name)
116

    
117
  @classmethod
118
  def _InstanceKVMRuntime(cls, instance_name):
119
    """Returns the instance KVM runtime filename
120

121
    """
122
    return '%s/%s.runtime' % (cls._CONF_DIR, instance_name)
123

    
124
  @classmethod
125
  def _RemoveInstanceRuntimeFiles(cls, pidfile, instance_name):
126
    """Removes an instance's rutime sockets/files.
127

128
    """
129
    utils.RemoveFile(pidfile)
130
    utils.RemoveFile(cls._InstanceMonitor(instance_name))
131
    utils.RemoveFile(cls._InstanceSerial(instance_name))
132
    utils.RemoveFile(cls._InstanceKVMRuntime(instance_name))
133

    
134
  def _WriteNetScript(self, instance, seq, nic):
135
    """Write a script to connect a net interface to the proper bridge.
136

137
    This can be used by any qemu-type hypervisor.
138

139
    @param instance: instance we're acting on
140
    @type instance: instance object
141
    @param seq: nic sequence number
142
    @type seq: int
143
    @param nic: nic we're acting on
144
    @type nic: nic object
145
    @return: netscript file name
146
    @rtype: string
147

148
    """
149
    script = StringIO()
150
    script.write("#!/bin/sh\n")
151
    script.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
152
    script.write("export INSTANCE=%s\n" % instance.name)
153
    script.write("export MAC=%s\n" % nic.mac)
154
    if nic.ip:
155
      script.write("export IP=%s\n" % nic.ip)
156
    script.write("export MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
157
    if nic.nicparams[constants.NIC_LINK]:
158
      script.write("export LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
159
    if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
160
      script.write("export BRIDGE=%s\n" % nic.nicparams[constants.NIC_LINK])
161
    script.write("export INTERFACE=$1\n")
162
    # TODO: make this configurable at ./configure time
163
    script.write("if [ -x '%s' ]; then\n" % self._KVM_NETWORK_SCRIPT)
164
    script.write("  # Execute the user-specific vif file\n")
165
    script.write("  %s\n" % self._KVM_NETWORK_SCRIPT)
166
    script.write("else\n")
167
    script.write("  /sbin/ifconfig $INTERFACE 0.0.0.0 up\n")
168
    if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
169
      script.write("  # Connect the interface to the bridge\n")
170
      script.write("  /usr/sbin/brctl addif $BRIDGE $INTERFACE\n")
171
    elif nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_ROUTED:
172
      script.write("  # Route traffic targeted at the IP to the interface\n")
173
      if nic.nicparams[constants.NIC_LINK]:
174
        script.write("  while /sbin/ip rule del dev $INTERFACE; do :; done\n")
175
        script.write("  /sbin/ip rule add dev $INTERFACE table $LINK\n")
176
        script.write("  /sbin/ip route replace $IP table $LINK proto static"
177
                     " dev $INTERFACE\n")
178
      else:
179
        script.write("  /sbin/ip route replace $IP proto static"
180
                     " dev $INTERFACE\n")
181
      interface_v4_conf = "/proc/sys/net/ipv4/conf/$INTERFACE"
182
      interface_v6_conf = "/proc/sys/net/ipv6/conf/$INTERFACE"
183
      script.write("  if [ -d %s ]; then\n" % interface_v4_conf)
184
      script.write("    echo 1 > %s/proxy_arp\n" % interface_v4_conf)
185
      script.write("    echo 1 > %s/forwarding\n" % interface_v4_conf)
186
      script.write("  fi\n")
187
      script.write("  if [ -d %s ]; then\n" % interface_v6_conf)
188
      script.write("    echo 1 > %s/proxy_ndp\n" % interface_v6_conf)
189
      script.write("    echo 1 > %s/forwarding\n" % interface_v6_conf)
190
      script.write("  fi\n")
191
    script.write("fi\n\n")
192
    # As much as we'd like to put this in our _ROOT_DIR, that will happen to be
193
    # mounted noexec sometimes, so we'll have to find another place.
194
    (tmpfd, tmpfile_name) = tempfile.mkstemp()
195
    tmpfile = os.fdopen(tmpfd, 'w')
196
    try:
197
      tmpfile.write(script.getvalue())
198
    finally:
199
      tmpfile.close()
200
    os.chmod(tmpfile_name, 0755)
201
    return tmpfile_name
202

    
203
  def ListInstances(self):
204
    """Get the list of running instances.
205

206
    We can do this by listing our live instances directory and
207
    checking whether the associated kvm process is still alive.
208

209
    """
210
    result = []
211
    for name in os.listdir(self._PIDS_DIR):
212
      filename = "%s/%s" % (self._PIDS_DIR, name)
213
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
214
        result.append(name)
215
    return result
216

    
217
  def GetInstanceInfo(self, instance_name):
218
    """Get instance properties.
219

220
    @param instance_name: the instance name
221

222
    @return: tuple (name, id, memory, vcpus, stat, times)
223

224
    """
225
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
226
    if not alive:
227
      return None
228

    
229
    cmdline_file = "/proc/%s/cmdline" % pid
230
    try:
231
      cmdline = utils.ReadFile(cmdline_file)
232
    except EnvironmentError, err:
233
      raise errors.HypervisorError("Failed to list instance %s: %s" %
234
                                   (instance_name, err))
235

    
236
    memory = 0
237
    vcpus = 0
238
    stat = "---b-"
239
    times = "0"
240

    
241
    arg_list = cmdline.split('\x00')
242
    while arg_list:
243
      arg =  arg_list.pop(0)
244
      if arg == '-m':
245
        memory = int(arg_list.pop(0))
246
      elif arg == '-smp':
247
        vcpus = int(arg_list.pop(0))
248

    
249
    return (instance_name, pid, memory, vcpus, stat, times)
250

    
251
  def GetAllInstancesInfo(self):
252
    """Get properties of all instances.
253

254
    @return: list of tuples (name, id, memory, vcpus, stat, times)
255

256
    """
257
    data = []
258
    for name in os.listdir(self._PIDS_DIR):
259
      filename = "%s/%s" % (self._PIDS_DIR, name)
260
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
261
        try:
262
          info = self.GetInstanceInfo(name)
263
        except errors.HypervisorError, err:
264
          continue
265
        if info:
266
          data.append(info)
267

    
268
    return data
269

    
270
  def _GenerateKVMRuntime(self, instance, block_devices):
271
    """Generate KVM information to start an instance.
272

273
    """
274
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
275
    kvm = constants.KVM_PATH
276
    kvm_cmd = [kvm]
277
    kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
278
    kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
279
    kvm_cmd.extend(['-pidfile', pidfile])
280
    # used just by the vnc server, if enabled
281
    kvm_cmd.extend(['-name', instance.name])
282
    kvm_cmd.extend(['-daemonize'])
283
    if not instance.hvparams[constants.HV_ACPI]:
284
      kvm_cmd.extend(['-no-acpi'])
285

    
286
    hvp = instance.hvparams
287
    boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
288
    boot_cdrom = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_CDROM
289
    boot_network = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_NETWORK
290

    
291
    if boot_network:
292
      kvm_cmd.extend(['-boot', 'n'])
293

    
294
    disk_type = hvp[constants.HV_DISK_TYPE]
295
    if disk_type == constants.HT_DISK_PARAVIRTUAL:
296
      if_val = ',if=virtio'
297
    else:
298
      if_val = ',if=%s' % disk_type
299
    for cfdev, dev_path in block_devices:
300
      if cfdev.mode != constants.DISK_RDWR:
301
        raise errors.HypervisorError("Instance has read-only disks which"
302
                                     " are not supported by KVM")
303
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
304
      if boot_disk:
305
        kvm_cmd.extend(['-boot', 'c'])
306
        boot_val = ',boot=on'
307
        # We only boot from the first disk
308
        boot_disk = False
309
      else:
310
        boot_val = ''
311

    
312
      drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
313
      kvm_cmd.extend(['-drive', drive_val])
314

    
315
    iso_image = hvp[constants.HV_CDROM_IMAGE_PATH]
316
    if iso_image:
317
      options = ',format=raw,media=cdrom'
318
      if boot_cdrom:
319
        kvm_cmd.extend(['-boot', 'd'])
320
        options = '%s,boot=on' % options
321
      else:
322
        options = '%s,if=virtio' % options
323
      drive_val = 'file=%s%s' % (iso_image, options)
324
      kvm_cmd.extend(['-drive', drive_val])
325

    
326
    kernel_path = hvp[constants.HV_KERNEL_PATH]
327
    if kernel_path:
328
      kvm_cmd.extend(['-kernel', kernel_path])
329
      initrd_path = hvp[constants.HV_INITRD_PATH]
330
      if initrd_path:
331
        kvm_cmd.extend(['-initrd', initrd_path])
332
      root_append = ['root=%s' % hvp[constants.HV_ROOT_PATH],
333
                     hvp[constants.HV_KERNEL_ARGS]]
334
      if hvp[constants.HV_SERIAL_CONSOLE]:
335
        root_append.append('console=ttyS0,38400')
336
      kvm_cmd.extend(['-append', ' '.join(root_append)])
337

    
338
    mouse_type = hvp[constants.HV_USB_MOUSE]
339
    if mouse_type:
340
      kvm_cmd.extend(['-usb'])
341
      kvm_cmd.extend(['-usbdevice', mouse_type])
342

    
343
    vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS]
344
    if vnc_bind_address:
345
      if utils.IsValidIP(vnc_bind_address):
346
        if instance.network_port > constants.VNC_BASE_PORT:
347
          display = instance.network_port - constants.VNC_BASE_PORT
348
          if vnc_bind_address == '0.0.0.0':
349
            vnc_arg = ':%d' % (display)
350
          else:
351
            vnc_arg = '%s:%d' % (vnc_bind_address, display)
352
        else:
353
          logging.error("Network port is not a valid VNC display (%d < %d)."
354
                        " Not starting VNC" %
355
                        (instance.network_port,
356
                         constants.VNC_BASE_PORT))
357
          vnc_arg = 'none'
358

    
359
        # Only allow tls and other option when not binding to a file, for now.
360
        # kvm/qemu gets confused otherwise about the filename to use.
361
        vnc_append = ''
362
        if hvp[constants.HV_VNC_TLS]:
363
          vnc_append = '%s,tls' % vnc_append
364
          if hvp[constants.HV_VNC_X509_VERIFY]:
365
            vnc_append = '%s,x509verify=%s' % (vnc_append,
366
                                               hvp[constants.HV_VNC_X509])
367
          elif hvp[constants.HV_VNC_X509]:
368
            vnc_append = '%s,x509=%s' % (vnc_append,
369
                                         hvp[constants.HV_VNC_X509])
370
        if hvp[constants.HV_VNC_PASSWORD_FILE]:
371
          vnc_append = '%s,password' % vnc_append
372

    
373
        vnc_arg = '%s%s' % (vnc_arg, vnc_append)
374

    
375
      else:
376
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
377

    
378
      kvm_cmd.extend(['-vnc', vnc_arg])
379
    else:
380
      kvm_cmd.extend(['-nographic'])
381

    
382
    monitor_dev = ("unix:%s,server,nowait" %
383
                   self._InstanceMonitor(instance.name))
384
    kvm_cmd.extend(['-monitor', monitor_dev])
385
    if hvp[constants.HV_SERIAL_CONSOLE]:
386
      serial_dev = ('unix:%s,server,nowait' %
387
                    self._InstanceSerial(instance.name))
388
      kvm_cmd.extend(['-serial', serial_dev])
389
    else:
390
      kvm_cmd.extend(['-serial', 'none'])
391

    
392
    # Save the current instance nics, but defer their expansion as parameters,
393
    # as we'll need to generate executable temp files for them.
394
    kvm_nics = instance.nics
395
    hvparams = hvp
396

    
397
    return (kvm_cmd, kvm_nics, hvparams)
398

    
399
  def _WriteKVMRuntime(self, instance_name, data):
400
    """Write an instance's KVM runtime
401

402
    """
403
    try:
404
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
405
                      data=data)
406
    except EnvironmentError, err:
407
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
408

    
409
  def _ReadKVMRuntime(self, instance_name):
410
    """Read an instance's KVM runtime
411

412
    """
413
    try:
414
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
415
    except EnvironmentError, err:
416
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
417
    return file_content
418

    
419
  def _SaveKVMRuntime(self, instance, kvm_runtime):
420
    """Save an instance's KVM runtime
421

422
    """
423
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
424
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
425
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
426
    self._WriteKVMRuntime(instance.name, serialized_form)
427

    
428
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
429
    """Load an instance's KVM runtime
430

431
    """
432
    if not serialized_runtime:
433
      serialized_runtime = self._ReadKVMRuntime(instance.name)
434
    loaded_runtime = serializer.Load(serialized_runtime)
435
    kvm_cmd, serialized_nics, hvparams = loaded_runtime
436
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
437
    return (kvm_cmd, kvm_nics, hvparams)
438

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

442
    @type incoming: tuple of strings
443
    @param incoming: (target_host_ip, port)
444

445
    """
446
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
447
    hvp = instance.hvparams
448
    if alive:
449
      raise errors.HypervisorError("Failed to start instance %s: %s" %
450
                                   (instance.name, "already running"))
451

    
452
    temp_files = []
453

    
454
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
455

    
456
    if not kvm_nics:
457
      kvm_cmd.extend(['-net', 'none'])
458
    else:
459
      nic_type = hvparams[constants.HV_NIC_TYPE]
460
      if nic_type == constants.HT_NIC_PARAVIRTUAL:
461
        nic_model = "model=virtio"
462
      else:
463
        nic_model = "model=%s" % nic_type
464

    
465
      for nic_seq, nic in enumerate(kvm_nics):
466
        nic_val = "nic,macaddr=%s,%s" % (nic.mac, nic_model)
467
        script = self._WriteNetScript(instance, nic_seq, nic)
468
        kvm_cmd.extend(['-net', nic_val])
469
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
470
        temp_files.append(script)
471

    
472
    if incoming:
473
      target, port = incoming
474
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
475

    
476
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
477
    vnc_pwd = None
478
    if vnc_pwd_file:
479
      try:
480
        vnc_pwd = utils.ReadFile(vnc_pwd_file)
481
      except EnvironmentError, err:
482
        raise errors.HypervisorError("Failed to open VNC password file %s: %s"
483
                                     % (vnc_pwd_file, err))
484

    
485
    result = utils.RunCmd(kvm_cmd)
486
    if result.failed:
487
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
488
                                   (instance.name, result.fail_reason,
489
                                    result.output))
490

    
491
    if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
492
      raise errors.HypervisorError("Failed to start instance %s: %s" %
493
                                   (instance.name))
494

    
495
    if vnc_pwd:
496
      change_cmd = 'change vnc password %s' % vnc_pwd
497
      self._CallMonitorCommand(instance.name, change_cmd)
498

    
499
    for filename in temp_files:
500
      utils.RemoveFile(filename)
501

    
502
  def StartInstance(self, instance, block_devices):
503
    """Start an instance.
504

505
    """
506
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
507
    if alive:
508
      raise errors.HypervisorError("Failed to start instance %s: %s" %
509
                                   (instance.name, "already running"))
510

    
511
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices)
512
    self._SaveKVMRuntime(instance, kvm_runtime)
513
    self._ExecuteKVMRuntime(instance, kvm_runtime)
514

    
515
  def _CallMonitorCommand(self, instance_name, command):
516
    """Invoke a command on the instance monitor.
517

518
    """
519
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
520
             (utils.ShellQuote(command),
521
              constants.SOCAT_PATH,
522
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
523
    result = utils.RunCmd(socat)
524
    if result.failed:
525
      msg = ("Failed to send command '%s' to instance %s."
526
             " output: %s, error: %s, fail_reason: %s" %
527
             (command, instance_name,
528
              result.stdout, result.stderr, result.fail_reason))
529
      raise errors.HypervisorError(msg)
530

    
531
    return result
532

    
533
  def StopInstance(self, instance, force=False, retry=False):
534
    """Stop an instance.
535

536
    """
537
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
538
    if pid > 0 and alive:
539
      if force or not instance.hvparams[constants.HV_ACPI]:
540
        utils.KillProcess(pid)
541
      else:
542
        self._CallMonitorCommand(instance.name, 'system_powerdown')
543

    
544
    if not utils.IsProcessAlive(pid):
545
      self._RemoveInstanceRuntimeFiles(pidfile, instance.name)
546
      return True
547
    else:
548
      return False
549

    
550
  def RebootInstance(self, instance):
551
    """Reboot an instance.
552

553
    """
554
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
555
    # socket the instance will stop, but now power up again. So we'll resort
556
    # to shutdown and restart.
557
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
558
    if not alive:
559
      raise errors.HypervisorError("Failed to reboot instance %s: not running" %
560
                                             (instance.name))
561
    # StopInstance will delete the saved KVM runtime so:
562
    # ...first load it...
563
    kvm_runtime = self._LoadKVMRuntime(instance)
564
    # ...now we can safely call StopInstance...
565
    if not self.StopInstance(instance):
566
      self.StopInstance(instance, force=True)
567
    # ...and finally we can save it again, and execute it...
568
    self._SaveKVMRuntime(instance, kvm_runtime)
569
    self._ExecuteKVMRuntime(instance, kvm_runtime)
570

    
571
  def MigrationInfo(self, instance):
572
    """Get instance information to perform a migration.
573

574
    @type instance: L{objects.Instance}
575
    @param instance: instance to be migrated
576
    @rtype: string
577
    @return: content of the KVM runtime file
578

579
    """
580
    return self._ReadKVMRuntime(instance.name)
581

    
582
  def AcceptInstance(self, instance, info, target):
583
    """Prepare to accept an instance.
584

585
    @type instance: L{objects.Instance}
586
    @param instance: instance to be accepted
587
    @type info: string
588
    @param info: content of the KVM runtime file on the source node
589
    @type target: string
590
    @param target: target host (usually ip), on this node
591

592
    """
593
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
594
    incoming_address = (target, constants.KVM_MIGRATION_PORT)
595
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
596

    
597
  def FinalizeMigration(self, instance, info, success):
598
    """Finalize an instance migration.
599

600
    Stop the incoming mode KVM.
601

602
    @type instance: L{objects.Instance}
603
    @param instance: instance whose migration is being aborted
604

605
    """
606
    if success:
607
      self._WriteKVMRuntime(instance.name, info)
608
    else:
609
      self.StopInstance(instance, force=True)
610

    
611
  def MigrateInstance(self, instance, target, live):
612
    """Migrate an instance to a target node.
613

614
    The migration will not be attempted if the instance is not
615
    currently running.
616

617
    @type instance: L{objects.Instance}
618
    @param instance: the instance to be migrated
619
    @type target: string
620
    @param target: ip address of the target node
621
    @type live: boolean
622
    @param live: perform a live migration
623

624
    """
625
    instance_name = instance.name
626
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
627
    if not alive:
628
      raise errors.HypervisorError("Instance not running, cannot migrate")
629

    
630
    if not live:
631
      self._CallMonitorCommand(instance_name, 'stop')
632

    
633
    migrate_command = ('migrate -d tcp:%s:%s' %
634
                       (target, constants.KVM_MIGRATION_PORT))
635
    self._CallMonitorCommand(instance_name, migrate_command)
636

    
637
    info_command = 'info migrate'
638
    done = False
639
    while not done:
640
      result = self._CallMonitorCommand(instance_name, info_command)
641
      match = self._MIGRATION_STATUS_RE.search(result.stdout)
642
      if not match:
643
        raise errors.HypervisorError("Unknown 'info migrate' result: %s" %
644
                                     result.stdout)
645
      else:
646
        status = match.group(1)
647
        if status == 'completed':
648
          done = True
649
        elif status == 'active':
650
          time.sleep(2)
651
        elif status == 'failed' or status == 'cancelled':
652
          if not live:
653
            self._CallMonitorCommand(instance_name, 'cont')
654
          raise errors.HypervisorError("Migration %s at the kvm level" %
655
                                       status)
656
        else:
657
          logging.info("KVM: unknown migration status '%s'" % status)
658
          time.sleep(2)
659

    
660
    utils.KillProcess(pid)
661
    self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
662

    
663
  def GetNodeInfo(self):
664
    """Return information about the node.
665

666
    This is just a wrapper over the base GetLinuxNodeInfo method.
667

668
    @return: a dict with the following keys (values in MiB):
669
          - memory_total: the total memory size on the node
670
          - memory_free: the available memory on the node for instances
671
          - memory_dom0: the memory used by the node itself, if available
672

673
    """
674
    return self.GetLinuxNodeInfo()
675

    
676
  @classmethod
677
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
678
    """Return a command for connecting to the console of an instance.
679

680
    """
681
    if hvparams[constants.HV_SERIAL_CONSOLE]:
682
      # FIXME: The socat shell is not perfect. In particular the way we start
683
      # it ctrl+c will close it, rather than being passed to the other end.
684
      # On the other hand if we pass the option 'raw' (or ignbrk=1) there
685
      # will be no way of exiting socat (except killing it from another shell)
686
      # and ctrl+c doesn't work anyway, printing ^C rather than being
687
      # interpreted by kvm. For now we'll leave it this way, which at least
688
      # allows a minimal interaction and changes on the machine.
689
      shell_command = ("%s STDIO,echo=0,icanon=0 UNIX-CONNECT:%s" %
690
                       (constants.SOCAT_PATH,
691
                        utils.ShellQuote(cls._InstanceSerial(instance.name))))
692
    else:
693
      shell_command = "echo 'No serial shell for instance %s'" % instance.name
694

    
695
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
696
    if vnc_bind_address:
697
      if instance.network_port > constants.VNC_BASE_PORT:
698
        display = instance.network_port - constants.VNC_BASE_PORT
699
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
700
                       " (display: %d)'" % (vnc_bind_address,
701
                                            instance.network_port,
702
                                            display))
703
        shell_command = "%s; %s" % (vnc_command, shell_command)
704

    
705
    return shell_command
706

    
707
  def Verify(self):
708
    """Verify the hypervisor.
709

710
    Check that the binary exists.
711

712
    """
713
    if not os.path.exists(constants.KVM_PATH):
714
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
715
    if not os.path.exists(constants.SOCAT_PATH):
716
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
717

    
718

    
719
  @classmethod
720
  def CheckParameterSyntax(cls, hvparams):
721
    """Check the given parameters for validity.
722

723
    @type hvparams:  dict
724
    @param hvparams: dictionary with parameter names/value
725
    @raise errors.HypervisorError: when a parameter is not valid
726

727
    """
728
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
729

    
730
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
731
    if kernel_path:
732
      if not hvparams[constants.HV_ROOT_PATH]:
733
        raise errors.HypervisorError("Need a root partition for the instance,"
734
                                     " if a kernel is defined")
735

    
736
    if (hvparams[constants.HV_VNC_X509_VERIFY] and
737
        not hvparams[constants.HV_VNC_X509]):
738
      raise errors.HypervisorError("%s must be defined, if %s is" %
739
                                   (constants.HV_VNC_X509,
740
                                    constants.HV_VNC_X509_VERIFY))
741

    
742
    boot_order = hvparams[constants.HV_BOOT_ORDER]
743

    
744
    if (boot_order == constants.HT_BO_CDROM and
745
        not hvparams[constants.HV_CDROM_IMAGE_PATH]):
746
      raise errors.HypervisorError("Cannot boot from cdrom without an"
747
                                   " ISO path")
748
    if (boot_order == constants.HT_BO_NETWORK and
749
        hvparams[constants.HV_NIC_TYPE] == constants.HT_NIC_PARAVIRTUAL):
750
      raise errors.HypervisorError("Cannot boot from a paravirtual NIC. Please"
751
                                   " change the NIC type.")
752

    
753
  @classmethod
754
  def PowercycleNode(cls):
755
    """KVM powercycle, just a wrapper over Linux powercycle.
756

757
    """
758
    cls.LinuxPowercycle()