Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 7c60f7a2

History | View | Annotate | Download (25.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_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
66
    constants.HV_BOOT_ORDER:
67
      hv_base.ParamInSet(True, constants.HT_KVM_VALID_BO_TYPES),
68
    constants.HV_NIC_TYPE:
69
      hv_base.ParamInSet(True, constants.HT_KVM_VALID_NIC_TYPES),
70
    constants.HV_DISK_TYPE:
71
      hv_base.ParamInSet(True, constants.HT_KVM_VALID_DISK_TYPES),
72
    constants.HV_USB_MOUSE:
73
      hv_base.ParamInSet(False, constants.HT_KVM_VALID_MOUSE_TYPES),
74
    }
75

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

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

    
81
  ANCILLARY_FILES = [
82
    _KVM_NETWORK_SCRIPT,
83
    ]
84

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

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

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

    
100
    return (pidfile, pid, alive)
101

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

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

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

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

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

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

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

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

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

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

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

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

    
190
  def ListInstances(self):
191
    """Get the list of running instances.
192

193
    We can do this by listing our live instances directory and
194
    checking whether the associated kvm process is still alive.
195

196
    """
197
    result = []
198
    for name in os.listdir(self._PIDS_DIR):
199
      filename = "%s/%s" % (self._PIDS_DIR, name)
200
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
201
        result.append(name)
202
    return result
203

    
204
  def GetInstanceInfo(self, instance_name):
205
    """Get instance properties.
206

207
    @param instance_name: the instance name
208

209
    @return: tuple (name, id, memory, vcpus, stat, times)
210

211
    """
212
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
213
    if not alive:
214
      return None
215

    
216
    cmdline_file = "/proc/%s/cmdline" % pid
217
    try:
218
      cmdline = utils.ReadFile(cmdline_file)
219
    except EnvironmentError, err:
220
      raise errors.HypervisorError("Failed to list instance %s: %s" %
221
                                   (instance_name, err))
222

    
223
    memory = 0
224
    vcpus = 0
225
    stat = "---b-"
226
    times = "0"
227

    
228
    arg_list = cmdline.split('\x00')
229
    while arg_list:
230
      arg =  arg_list.pop(0)
231
      if arg == '-m':
232
        memory = int(arg_list.pop(0))
233
      elif arg == '-smp':
234
        vcpus = int(arg_list.pop(0))
235

    
236
    return (instance_name, pid, memory, vcpus, stat, times)
237

    
238
  def GetAllInstancesInfo(self):
239
    """Get properties of all instances.
240

241
    @return: list of tuples (name, id, memory, vcpus, stat, times)
242

243
    """
244
    data = []
245
    for name in os.listdir(self._PIDS_DIR):
246
      filename = "%s/%s" % (self._PIDS_DIR, name)
247
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
248
        try:
249
          info = self.GetInstanceInfo(name)
250
        except errors.HypervisorError, err:
251
          continue
252
        if info:
253
          data.append(info)
254

    
255
    return data
256

    
257
  def _GenerateKVMRuntime(self, instance, block_devices):
258
    """Generate KVM information to start an instance.
259

260
    """
261
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
262
    kvm = constants.KVM_PATH
263
    kvm_cmd = [kvm]
264
    kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
265
    kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
266
    kvm_cmd.extend(['-pidfile', pidfile])
267
    # used just by the vnc server, if enabled
268
    kvm_cmd.extend(['-name', instance.name])
269
    kvm_cmd.extend(['-daemonize'])
270
    if not instance.hvparams[constants.HV_ACPI]:
271
      kvm_cmd.extend(['-no-acpi'])
272

    
273
    hvp = instance.hvparams
274
    boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
275
    boot_cdrom = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_CDROM
276
    boot_network = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_NETWORK
277

    
278
    if boot_network:
279
      kvm_cmd.extend(['-boot', 'n'])
280

    
281
    disk_type = hvp[constants.HV_DISK_TYPE]
282
    if disk_type == constants.HT_DISK_PARAVIRTUAL:
283
      if_val = ',if=virtio'
284
    else:
285
      if_val = ',if=%s' % disk_type
286
    for cfdev, dev_path in block_devices:
287
      if cfdev.mode != constants.DISK_RDWR:
288
        raise errors.HypervisorError("Instance has read-only disks which"
289
                                     " are not supported by KVM")
290
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
291
      if boot_disk:
292
        kvm_cmd.extend(['-boot', 'c'])
293
        boot_val = ',boot=on'
294
        # We only boot from the first disk
295
        boot_disk = False
296
      else:
297
        boot_val = ''
298

    
299
      drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
300
      kvm_cmd.extend(['-drive', drive_val])
301

    
302
    iso_image = hvp[constants.HV_CDROM_IMAGE_PATH]
303
    if iso_image:
304
      options = ',format=raw,media=cdrom'
305
      if boot_cdrom:
306
        kvm_cmd.extend(['-boot', 'd'])
307
        options = '%s,boot=on' % options
308
      else:
309
        options = '%s,if=virtio' % options
310
      drive_val = 'file=%s%s' % (iso_image, options)
311
      kvm_cmd.extend(['-drive', drive_val])
312

    
313
    kernel_path = hvp[constants.HV_KERNEL_PATH]
314
    if kernel_path:
315
      kvm_cmd.extend(['-kernel', kernel_path])
316
      initrd_path = hvp[constants.HV_INITRD_PATH]
317
      if initrd_path:
318
        kvm_cmd.extend(['-initrd', initrd_path])
319
      root_append = ['root=%s' % hvp[constants.HV_ROOT_PATH],
320
                     hvp[constants.HV_KERNEL_ARGS]]
321
      if hvp[constants.HV_SERIAL_CONSOLE]:
322
        root_append.append('console=ttyS0,38400')
323
      kvm_cmd.extend(['-append', ' '.join(root_append)])
324

    
325
    mouse_type = hvp[constants.HV_USB_MOUSE]
326
    if mouse_type:
327
      kvm_cmd.extend(['-usb'])
328
      kvm_cmd.extend(['-usbdevice', mouse_type])
329

    
330
    # FIXME: handle vnc password
331
    vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS]
332
    if vnc_bind_address:
333
      if utils.IsValidIP(vnc_bind_address):
334
        if instance.network_port > constants.VNC_BASE_PORT:
335
          display = instance.network_port - constants.VNC_BASE_PORT
336
          if vnc_bind_address == '0.0.0.0':
337
            vnc_arg = ':%d' % (display)
338
          else:
339
            vnc_arg = '%s:%d' % (vnc_bind_address, display)
340
        else:
341
          logging.error("Network port is not a valid VNC display (%d < %d)."
342
                        " Not starting VNC" %
343
                        (instance.network_port,
344
                         constants.VNC_BASE_PORT))
345
          vnc_arg = 'none'
346

    
347
        # Only allow tls and other option when not binding to a file, for now.
348
        # kvm/qemu gets confused otherwise about the filename to use.
349
        vnc_append = ''
350
        if hvp[constants.HV_VNC_TLS]:
351
          vnc_append = '%s,tls' % vnc_append
352
          if hvp[constants.HV_VNC_X509_VERIFY]:
353
            vnc_append = '%s,x509verify=%s' % (vnc_append,
354
                                               hvp[constants.HV_VNC_X509])
355
          elif hvp[constants.HV_VNC_X509]:
356
            vnc_append = '%s,x509=%s' % (vnc_append,
357
                                         hvp[constants.HV_VNC_X509])
358
        vnc_arg = '%s%s' % (vnc_arg, vnc_append)
359

    
360
      else:
361
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
362

    
363
      kvm_cmd.extend(['-vnc', vnc_arg])
364
    else:
365
      kvm_cmd.extend(['-nographic'])
366

    
367
    monitor_dev = 'unix:%s,server,nowait' % \
368
      self._InstanceMonitor(instance.name)
369
    kvm_cmd.extend(['-monitor', monitor_dev])
370
    if hvp[constants.HV_SERIAL_CONSOLE]:
371
      serial_dev = ('unix:%s,server,nowait' %
372
                    self._InstanceSerial(instance.name))
373
      kvm_cmd.extend(['-serial', serial_dev])
374
    else:
375
      kvm_cmd.extend(['-serial', 'none'])
376

    
377
    # Save the current instance nics, but defer their expansion as parameters,
378
    # as we'll need to generate executable temp files for them.
379
    kvm_nics = instance.nics
380
    hvparams = hvp
381

    
382
    return (kvm_cmd, kvm_nics, hvparams)
383

    
384
  def _WriteKVMRuntime(self, instance_name, data):
385
    """Write an instance's KVM runtime
386

387
    """
388
    try:
389
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
390
                      data=data)
391
    except EnvironmentError, err:
392
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
393

    
394
  def _ReadKVMRuntime(self, instance_name):
395
    """Read an instance's KVM runtime
396

397
    """
398
    try:
399
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
400
    except EnvironmentError, err:
401
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
402
    return file_content
403

    
404
  def _SaveKVMRuntime(self, instance, kvm_runtime):
405
    """Save an instance's KVM runtime
406

407
    """
408
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
409
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
410
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
411
    self._WriteKVMRuntime(instance.name, serialized_form)
412

    
413
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
414
    """Load an instance's KVM runtime
415

416
    """
417
    if not serialized_runtime:
418
      serialized_runtime = self._ReadKVMRuntime(instance.name)
419
    loaded_runtime = serializer.Load(serialized_runtime)
420
    kvm_cmd, serialized_nics, hvparams = loaded_runtime
421
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
422
    return (kvm_cmd, kvm_nics, hvparams)
423

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

427
    @type incoming: tuple of strings
428
    @param incoming: (target_host_ip, port)
429

430
    """
431
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
432
    if alive:
433
      raise errors.HypervisorError("Failed to start instance %s: %s" %
434
                                   (instance.name, "already running"))
435

    
436
    temp_files = []
437

    
438
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
439

    
440
    if not kvm_nics:
441
      kvm_cmd.extend(['-net', 'none'])
442
    else:
443
      nic_type = hvparams[constants.HV_NIC_TYPE]
444
      if nic_type == constants.HT_NIC_PARAVIRTUAL:
445
        nic_model = "model=virtio"
446
      else:
447
        nic_model = "model=%s" % nic_type
448

    
449
      for nic_seq, nic in enumerate(kvm_nics):
450
        nic_val = "nic,macaddr=%s,%s" % (nic.mac, nic_model)
451
        script = self._WriteNetScript(instance, nic_seq, nic)
452
        kvm_cmd.extend(['-net', nic_val])
453
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
454
        temp_files.append(script)
455

    
456
    if incoming:
457
      target, port = incoming
458
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
459

    
460
    result = utils.RunCmd(kvm_cmd)
461
    if result.failed:
462
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
463
                                   (instance.name, result.fail_reason,
464
                                    result.output))
465

    
466
    if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
467
      raise errors.HypervisorError("Failed to start instance %s: %s" %
468
                                   (instance.name))
469

    
470
    for filename in temp_files:
471
      utils.RemoveFile(filename)
472

    
473
  def StartInstance(self, instance, block_devices):
474
    """Start an instance.
475

476
    """
477
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
478
    if alive:
479
      raise errors.HypervisorError("Failed to start instance %s: %s" %
480
                                   (instance.name, "already running"))
481

    
482
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices)
483
    self._SaveKVMRuntime(instance, kvm_runtime)
484
    self._ExecuteKVMRuntime(instance, kvm_runtime)
485

    
486
  def _CallMonitorCommand(self, instance_name, command):
487
    """Invoke a command on the instance monitor.
488

489
    """
490
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
491
             (utils.ShellQuote(command),
492
              constants.SOCAT_PATH,
493
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
494
    result = utils.RunCmd(socat)
495
    if result.failed:
496
      msg = ("Failed to send command '%s' to instance %s."
497
             " output: %s, error: %s, fail_reason: %s" %
498
             (command, instance_name,
499
              result.stdout, result.stderr, result.fail_reason))
500
      raise errors.HypervisorError(msg)
501

    
502
    return result
503

    
504
  def _RetryInstancePowerdown(self, instance, pid, timeout=30):
505
    """Wait for an instance  to power down.
506

507
    """
508
    # Wait up to $timeout seconds
509
    end = time.time() + timeout
510
    wait = 1
511
    while time.time() < end and utils.IsProcessAlive(pid):
512
      self._CallMonitorCommand(instance.name, 'system_powerdown')
513
      time.sleep(wait)
514
      # Make wait time longer for next try
515
      if wait < 5:
516
        wait *= 1.3
517

    
518
  def StopInstance(self, instance, force=False):
519
    """Stop an instance.
520

521
    """
522
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
523
    if pid > 0 and alive:
524
      if force or not instance.hvparams[constants.HV_ACPI]:
525
        utils.KillProcess(pid)
526
      else:
527
        self._RetryInstancePowerdown(instance, pid)
528

    
529
    if not utils.IsProcessAlive(pid):
530
      self._RemoveInstanceRuntimeFiles(pidfile, instance.name)
531
      return True
532
    else:
533
      return False
534

    
535
  def RebootInstance(self, instance):
536
    """Reboot an instance.
537

538
    """
539
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
540
    # socket the instance will stop, but now power up again. So we'll resort
541
    # to shutdown and restart.
542
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
543
    if not alive:
544
      raise errors.HypervisorError("Failed to reboot instance %s: not running" %
545
                                             (instance.name))
546
    # StopInstance will delete the saved KVM runtime so:
547
    # ...first load it...
548
    kvm_runtime = self._LoadKVMRuntime(instance)
549
    # ...now we can safely call StopInstance...
550
    if not self.StopInstance(instance):
551
      self.StopInstance(instance, force=True)
552
    # ...and finally we can save it again, and execute it...
553
    self._SaveKVMRuntime(instance, kvm_runtime)
554
    self._ExecuteKVMRuntime(instance, kvm_runtime)
555

    
556
  def MigrationInfo(self, instance):
557
    """Get instance information to perform a migration.
558

559
    @type instance: L{objects.Instance}
560
    @param instance: instance to be migrated
561
    @rtype: string
562
    @return: content of the KVM runtime file
563

564
    """
565
    return self._ReadKVMRuntime(instance.name)
566

    
567
  def AcceptInstance(self, instance, info, target):
568
    """Prepare to accept an instance.
569

570
    @type instance: L{objects.Instance}
571
    @param instance: instance to be accepted
572
    @type info: string
573
    @param info: content of the KVM runtime file on the source node
574
    @type target: string
575
    @param target: target host (usually ip), on this node
576

577
    """
578
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
579
    incoming_address = (target, constants.KVM_MIGRATION_PORT)
580
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
581

    
582
  def FinalizeMigration(self, instance, info, success):
583
    """Finalize an instance migration.
584

585
    Stop the incoming mode KVM.
586

587
    @type instance: L{objects.Instance}
588
    @param instance: instance whose migration is being aborted
589

590
    """
591
    if success:
592
      self._WriteKVMRuntime(instance.name, info)
593
    else:
594
      self.StopInstance(instance, force=True)
595

    
596
  def MigrateInstance(self, instance_name, target, live):
597
    """Migrate an instance to a target node.
598

599
    The migration will not be attempted if the instance is not
600
    currently running.
601

602
    @type instance_name: string
603
    @param instance_name: name of the instance to be migrated
604
    @type target: string
605
    @param target: ip address of the target node
606
    @type live: boolean
607
    @param live: perform a live migration
608

609
    """
610
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
611
    if not alive:
612
      raise errors.HypervisorError("Instance not running, cannot migrate")
613

    
614
    if not live:
615
      self._CallMonitorCommand(instance_name, 'stop')
616

    
617
    migrate_command = ('migrate -d tcp:%s:%s' %
618
                       (target, constants.KVM_MIGRATION_PORT))
619
    self._CallMonitorCommand(instance_name, migrate_command)
620

    
621
    info_command = 'info migrate'
622
    done = False
623
    while not done:
624
      result = self._CallMonitorCommand(instance_name, info_command)
625
      match = self._MIGRATION_STATUS_RE.search(result.stdout)
626
      if not match:
627
        raise errors.HypervisorError("Unknown 'info migrate' result: %s" %
628
                                     result.stdout)
629
      else:
630
        status = match.group(1)
631
        if status == 'completed':
632
          done = True
633
        elif status == 'active':
634
          time.sleep(2)
635
        elif status == 'failed' or status == 'cancelled':
636
          if not live:
637
            self._CallMonitorCommand(instance_name, 'cont')
638
          raise errors.HypervisorError("Migration %s at the kvm level" %
639
                                       status)
640
        else:
641
          logging.info("KVM: unknown migration status '%s'" % status)
642
          time.sleep(2)
643

    
644
    utils.KillProcess(pid)
645
    self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
646

    
647
  def GetNodeInfo(self):
648
    """Return information about the node.
649

650
    This is just a wrapper over the base GetLinuxNodeInfo method.
651

652
    @return: a dict with the following keys (values in MiB):
653
          - memory_total: the total memory size on the node
654
          - memory_free: the available memory on the node for instances
655
          - memory_dom0: the memory used by the node itself, if available
656

657
    """
658
    return self.GetLinuxNodeInfo()
659

    
660
  @classmethod
661
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
662
    """Return a command for connecting to the console of an instance.
663

664
    """
665
    if hvparams[constants.HV_SERIAL_CONSOLE]:
666
      # FIXME: The socat shell is not perfect. In particular the way we start
667
      # it ctrl+c will close it, rather than being passed to the other end.
668
      # On the other hand if we pass the option 'raw' (or ignbrk=1) there
669
      # will be no way of exiting socat (except killing it from another shell)
670
      # and ctrl+c doesn't work anyway, printing ^C rather than being
671
      # interpreted by kvm. For now we'll leave it this way, which at least
672
      # allows a minimal interaction and changes on the machine.
673
      shell_command = ("%s STDIO,echo=0,icanon=0 UNIX-CONNECT:%s" %
674
                       (constants.SOCAT_PATH,
675
                        utils.ShellQuote(cls._InstanceSerial(instance.name))))
676
    else:
677
      shell_command = "echo 'No serial shell for instance %s'" % instance.name
678

    
679
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
680
    if vnc_bind_address:
681
      if instance.network_port > constants.VNC_BASE_PORT:
682
        display = instance.network_port - constants.VNC_BASE_PORT
683
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
684
                       " (display: %d)'" % (vnc_bind_address,
685
                                            instance.network_port,
686
                                            display))
687
        shell_command = "%s; %s" % (vnc_command, shell_command)
688

    
689
    return shell_command
690

    
691
  def Verify(self):
692
    """Verify the hypervisor.
693

694
    Check that the binary exists.
695

696
    """
697
    if not os.path.exists(constants.KVM_PATH):
698
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
699
    if not os.path.exists(constants.SOCAT_PATH):
700
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
701

    
702

    
703
  @classmethod
704
  def CheckParameterSyntax(cls, hvparams):
705
    """Check the given parameters for validity.
706

707
    @type hvparams:  dict
708
    @param hvparams: dictionary with parameter names/value
709
    @raise errors.HypervisorError: when a parameter is not valid
710

711
    """
712
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
713

    
714
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
715
    if kernel_path:
716
      if not hvparams[constants.HV_ROOT_PATH]:
717
        raise errors.HypervisorError("Need a root partition for the instance,"
718
                                     " if a kernel is defined")
719

    
720
    if (hvparams[constants.HV_VNC_X509_VERIFY] and
721
        not hvparams[constants.HV_VNC_X509]):
722
      raise errors.HypervisorError("%s must be defined, if %s is" %
723
                                   (constants.HV_VNC_X509,
724
                                    constants.HV_VNC_X509_VERIFY))
725

    
726
    boot_order = hvparams[constants.HV_BOOT_ORDER]
727

    
728
    if (boot_order == constants.HT_BO_CDROM and
729
        not hvparams[constants.HV_CDROM_IMAGE_PATH]):
730
      raise errors.HypervisorError("Cannot boot from cdrom without an"
731
                                   " ISO path")
732
    if (boot_order == constants.HT_BO_NETWORK and
733
        hvparams[constants.HV_NIC_TYPE] == constants.HT_NIC_PARAVIRTUAL):
734
      raise errors.HypervisorError("Cannot boot from a paravirtual NIC. Please"
735
                                   " change the NIC type.")
736

    
737
  @classmethod
738
  def PowercycleNode(cls):
739
    """KVM powercycle, just a wrapper over Linux powercycle.
740

741
    """
742
    cls.LinuxPowercycle()