Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ e014f1d0

History | View | Annotate | Download (26.7 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/32 table $LINK"
177
                     " dev $INTERFACE\n")
178
      else:
179
        script.write("  /sbin/ip route replace $IP/32 dev $INTERFACE\n")
180
      interface_v4_conf = "/proc/sys/net/ipv4/conf/$INTERFACE"
181
      interface_v6_conf = "/proc/sys/net/ipv6/conf/$INTERFACE"
182
      script.write("  if [ -d %s ]; then\n" % interface_v4_conf)
183
      script.write("    echo 1 > %s/proxy_arp\n" % interface_v4_conf)
184
      script.write("    echo 1 > %s/forwarding\n" % interface_v4_conf)
185
      script.write("  fi\n")
186
      script.write("  if [ -d %s ]; then\n" % interface_v6_conf)
187
      script.write("    echo 1 > %s/proxy_ndp\n" % interface_v6_conf)
188
      script.write("    echo 1 > %s/forwarding\n" % interface_v6_conf)
189
      script.write("  fi\n")
190
    script.write("fi\n\n")
191
    # As much as we'd like to put this in our _ROOT_DIR, that will happen to be
192
    # mounted noexec sometimes, so we'll have to find another place.
193
    (tmpfd, tmpfile_name) = tempfile.mkstemp()
194
    tmpfile = os.fdopen(tmpfd, 'w')
195
    try:
196
      tmpfile.write(script.getvalue())
197
    finally:
198
      tmpfile.close()
199
    os.chmod(tmpfile_name, 0755)
200
    return tmpfile_name
201

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

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

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

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

219
    @param instance_name: the instance name
220

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

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

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

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

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

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

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

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

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

    
267
    return data
268

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
396
    return (kvm_cmd, kvm_nics, hvparams)
397

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

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

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

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

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

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

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

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

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

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

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

    
451
    temp_files = []
452

    
453
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
454

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

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

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

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

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

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

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

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

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

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

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

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

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

    
530
    return result
531

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

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

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

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

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

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

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

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

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

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

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

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

599
    Stop the incoming mode KVM.
600

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

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

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

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

616
    @type instance_name: string
617
    @param instance_name: name of the instance to be migrated
618
    @type target: string
619
    @param target: ip address of the target node
620
    @type live: boolean
621
    @param live: perform a live migration
622

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

    
628
    if not live:
629
      self._CallMonitorCommand(instance_name, 'stop')
630

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

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

    
658
    utils.KillProcess(pid)
659
    self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
660

    
661
  def GetNodeInfo(self):
662
    """Return information about the node.
663

664
    This is just a wrapper over the base GetLinuxNodeInfo method.
665

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

671
    """
672
    return self.GetLinuxNodeInfo()
673

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

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

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

    
703
    return shell_command
704

    
705
  def Verify(self):
706
    """Verify the hypervisor.
707

708
    Check that the binary exists.
709

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

    
716

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

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

725
    """
726
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
727

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

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

    
740
    boot_order = hvparams[constants.HV_BOOT_ORDER]
741

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

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

755
    """
756
    cls.LinuxPowercycle()