Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 07813a9e

History | View | Annotate | Download (29.3 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,
53
    constants.HV_INITRD_PATH,
54
    constants.HV_ROOT_PATH,
55
    constants.HV_KERNEL_ARGS,
56
    constants.HV_ACPI,
57
    constants.HV_SERIAL_CONSOLE,
58
    constants.HV_VNC_BIND_ADDRESS,
59
    constants.HV_VNC_TLS,
60
    constants.HV_VNC_X509,
61
    constants.HV_VNC_X509_VERIFY,
62
    constants.HV_CDROM_IMAGE_PATH,
63
    constants.HV_BOOT_ORDER,
64
    constants.HV_NIC_TYPE,
65
    constants.HV_DISK_TYPE,
66
    constants.HV_USB_MOUSE,
67
    ]
68

    
69
  _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)',
70
                                    re.M | re.I)
71

    
72
  def __init__(self):
73
    hv_base.BaseHypervisor.__init__(self)
74
    # Let's make sure the directories we need exist, even if the RUN_DIR lives
75
    # in a tmpfs filesystem or has been otherwise wiped out.
76
    for mydir in self._DIRS:
77
      if not os.path.exists(mydir):
78
        os.mkdir(mydir)
79

    
80
  def _InstancePidAlive(self, instance_name):
81
    """Returns the instance pid and pidfile
82

83
    """
84
    pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
85
    pid = utils.ReadPidFile(pidfile)
86
    alive = utils.IsProcessAlive(pid)
87

    
88
    return (pidfile, pid, alive)
89

    
90
  @classmethod
91
  def _InstanceMonitor(cls, instance_name):
92
    """Returns the instance monitor socket name
93

94
    """
95
    return '%s/%s.monitor' % (cls._CTRL_DIR, instance_name)
96

    
97
  @classmethod
98
  def _InstanceSerial(cls, instance_name):
99
    """Returns the instance serial socket name
100

101
    """
102
    return '%s/%s.serial' % (cls._CTRL_DIR, instance_name)
103

    
104
  @classmethod
105
  def _InstanceKVMRuntime(cls, instance_name):
106
    """Returns the instance KVM runtime filename
107

108
    """
109
    return '%s/%s.runtime' % (cls._CONF_DIR, instance_name)
110

    
111
  def _WriteNetScript(self, instance, seq, nic):
112
    """Write a script to connect a net interface to the proper bridge.
113

114
    This can be used by any qemu-type hypervisor.
115

116
    @param instance: instance we're acting on
117
    @type instance: instance object
118
    @param seq: nic sequence number
119
    @type seq: int
120
    @param nic: nic we're acting on
121
    @type nic: nic object
122
    @return: netscript file name
123
    @rtype: string
124

125
    """
126
    script = StringIO()
127
    script.write("#!/bin/sh\n")
128
    script.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
129
    script.write("export INSTANCE=%s\n" % instance.name)
130
    script.write("export MAC=%s\n" % nic.mac)
131
    script.write("export IP=%s\n" % nic.ip)
132
    script.write("export BRIDGE=%s\n" % nic.bridge)
133
    script.write("export INTERFACE=$1\n")
134
    # TODO: make this configurable at ./configure time
135
    script.write("if [ -x /etc/ganeti/kvm-vif-bridge ]; then\n")
136
    script.write("  # Execute the user-specific vif file\n")
137
    script.write("  /etc/ganeti/kvm-vif-bridge\n")
138
    script.write("else\n")
139
    script.write("  # Connect the interface to the bridge\n")
140
    script.write("  /sbin/ifconfig $INTERFACE 0.0.0.0 up\n")
141
    script.write("  /usr/sbin/brctl addif $BRIDGE $INTERFACE\n")
142
    script.write("fi\n\n")
143
    # As much as we'd like to put this in our _ROOT_DIR, that will happen to be
144
    # mounted noexec sometimes, so we'll have to find another place.
145
    (tmpfd, tmpfile_name) = tempfile.mkstemp()
146
    tmpfile = os.fdopen(tmpfd, 'w')
147
    tmpfile.write(script.getvalue())
148
    tmpfile.close()
149
    os.chmod(tmpfile_name, 0755)
150
    return tmpfile_name
151

    
152
  def ListInstances(self):
153
    """Get the list of running instances.
154

155
    We can do this by listing our live instances directory and
156
    checking whether the associated kvm process is still alive.
157

158
    """
159
    result = []
160
    for name in os.listdir(self._PIDS_DIR):
161
      filename = "%s/%s" % (self._PIDS_DIR, name)
162
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
163
        result.append(name)
164
    return result
165

    
166
  def GetInstanceInfo(self, instance_name):
167
    """Get instance properties.
168

169
    @param instance_name: the instance name
170

171
    @return: tuple (name, id, memory, vcpus, stat, times)
172

173
    """
174
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
175
    if not alive:
176
      return None
177

    
178
    cmdline_file = "/proc/%s/cmdline" % pid
179
    try:
180
      fh = open(cmdline_file, 'r')
181
      try:
182
        cmdline = fh.read()
183
      finally:
184
        fh.close()
185
    except EnvironmentError, err:
186
      raise errors.HypervisorError("Failed to list instance %s: %s" %
187
                                   (instance_name, err))
188

    
189
    memory = 0
190
    vcpus = 0
191
    stat = "---b-"
192
    times = "0"
193

    
194
    arg_list = cmdline.split('\x00')
195
    while arg_list:
196
      arg =  arg_list.pop(0)
197
      if arg == '-m':
198
        memory = arg_list.pop(0)
199
      elif arg == '-smp':
200
        vcpus = arg_list.pop(0)
201

    
202
    return (instance_name, pid, memory, vcpus, stat, times)
203

    
204
  def GetAllInstancesInfo(self):
205
    """Get properties of all instances.
206

207
    @return: list of tuples (name, id, memory, vcpus, stat, times)
208

209
    """
210
    data = []
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
        try:
215
          info = self.GetInstanceInfo(name)
216
        except errors.HypervisorError, err:
217
          continue
218
        if info:
219
          data.append(info)
220

    
221
    return data
222

    
223
  def _GenerateKVMRuntime(self, instance, block_devices):
224
    """Generate KVM information to start an instance.
225

226
    """
227
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
228
    kvm = constants.KVM_PATH
229
    kvm_cmd = [kvm]
230
    kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
231
    kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
232
    kvm_cmd.extend(['-pidfile', pidfile])
233
    # used just by the vnc server, if enabled
234
    kvm_cmd.extend(['-name', instance.name])
235
    kvm_cmd.extend(['-daemonize'])
236
    if not instance.hvparams[constants.HV_ACPI]:
237
      kvm_cmd.extend(['-no-acpi'])
238

    
239
    hvp = instance.hvparams
240
    boot_disk = hvp[constants.HV_BOOT_ORDER] == "disk"
241
    boot_cdrom = hvp[constants.HV_BOOT_ORDER] == "cdrom"
242
    boot_network = hvp[constants.HV_BOOT_ORDER] == "network"
243

    
244
    if boot_network:
245
      kvm_cmd.extend(['-boot', 'n'])
246

    
247
    disk_type = hvp[constants.HV_DISK_TYPE]
248
    if disk_type == constants.HT_DISK_PARAVIRTUAL:
249
      if_val = ',if=virtio'
250
    else:
251
      if_val = ',if=%s' % disk_type
252
    for cfdev, dev_path in block_devices:
253
      if cfdev.mode != constants.DISK_RDWR:
254
        raise errors.HypervisorError("Instance has read-only disks which"
255
                                     " are not supported by KVM")
256
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
257
      if boot_disk:
258
        kvm_cmd.extend(['-boot', 'c'])
259
        boot_val = ',boot=on'
260
        # We only boot from the first disk
261
        boot_disk = False
262
      else:
263
        boot_val = ''
264

    
265
      drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
266
      kvm_cmd.extend(['-drive', drive_val])
267

    
268
    iso_image = hvp[constants.HV_CDROM_IMAGE_PATH]
269
    if iso_image:
270
      options = ',format=raw,media=cdrom'
271
      if boot_cdrom:
272
        kvm_cmd.extend(['-boot', 'd'])
273
        options = '%s,boot=on' % options
274
      else:
275
        options = '%s,if=virtio' % options
276
      drive_val = 'file=%s%s' % (iso_image, options)
277
      kvm_cmd.extend(['-drive', drive_val])
278

    
279
    kernel_path = hvp[constants.HV_KERNEL_PATH]
280
    if kernel_path:
281
      kvm_cmd.extend(['-kernel', kernel_path])
282
      initrd_path = hvp[constants.HV_INITRD_PATH]
283
      if initrd_path:
284
        kvm_cmd.extend(['-initrd', initrd_path])
285
      root_append = ['root=%s' % hvp[constants.HV_ROOT_PATH],
286
                     hvp[constants.HV_KERNEL_ARGS]]
287
      if hvp[constants.HV_SERIAL_CONSOLE]:
288
        root_append.append('console=ttyS0,38400')
289
      kvm_cmd.extend(['-append', ' '.join(root_append)])
290

    
291
    mouse_type = hvp[constants.HV_USB_MOUSE]
292
    if mouse_type:
293
      kvm_cmd.extend(['-usb'])
294
      kvm_cmd.extend(['-usbdevice', mouse_type])
295

    
296
    # FIXME: handle vnc password
297
    vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS]
298
    if vnc_bind_address:
299
      if utils.IsValidIP(vnc_bind_address):
300
        if instance.network_port > constants.VNC_BASE_PORT:
301
          display = instance.network_port - constants.VNC_BASE_PORT
302
          if vnc_bind_address == '0.0.0.0':
303
            vnc_arg = ':%d' % (display)
304
          else:
305
            vnc_arg = '%s:%d' % (constants.HV_VNC_BIND_ADDRESS, display)
306
        else:
307
          logging.error("Network port is not a valid VNC display (%d < %d)."
308
                        " Not starting VNC" %
309
                        (instance.network_port,
310
                         constants.VNC_BASE_PORT))
311
          vnc_arg = 'none'
312

    
313
        # Only allow tls and other option when not binding to a file, for now.
314
        # kvm/qemu gets confused otherwise about the filename to use.
315
        vnc_append = ''
316
        if hvp[constants.HV_VNC_TLS]:
317
          vnc_append = '%s,tls' % vnc_append
318
          if hvp[constants.HV_VNC_X509_VERIFY]:
319
            vnc_append = '%s,x509verify=%s' % (vnc_append,
320
                                               hvp[constants.HV_VNC_X509])
321
          elif hvp[constants.HV_VNC_X509]:
322
            vnc_append = '%s,x509=%s' % (vnc_append,
323
                                         hvp[constants.HV_VNC_X509])
324
        vnc_arg = '%s%s' % (vnc_arg, vnc_append)
325

    
326
      else:
327
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
328

    
329
      kvm_cmd.extend(['-vnc', vnc_arg])
330
    else:
331
      kvm_cmd.extend(['-nographic'])
332

    
333
    monitor_dev = 'unix:%s,server,nowait' % \
334
      self._InstanceMonitor(instance.name)
335
    kvm_cmd.extend(['-monitor', monitor_dev])
336
    if hvp[constants.HV_SERIAL_CONSOLE]:
337
      serial_dev = ('unix:%s,server,nowait' %
338
                    self._InstanceSerial(instance.name))
339
      kvm_cmd.extend(['-serial', serial_dev])
340
    else:
341
      kvm_cmd.extend(['-serial', 'none'])
342

    
343
    # Save the current instance nics, but defer their expansion as parameters,
344
    # as we'll need to generate executable temp files for them.
345
    kvm_nics = instance.nics
346
    hvparams = hvp
347

    
348
    return (kvm_cmd, kvm_nics, hvparams)
349

    
350
  def _WriteKVMRuntime(self, instance_name, data):
351
    """Write an instance's KVM runtime
352

353
    """
354
    try:
355
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
356
                      data=data)
357
    except EnvironmentError, err:
358
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
359

    
360
  def _ReadKVMRuntime(self, instance_name):
361
    """Read an instance's KVM runtime
362

363
    """
364
    try:
365
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
366
    except EnvironmentError, err:
367
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
368
    return file_content
369

    
370
  def _SaveKVMRuntime(self, instance, kvm_runtime):
371
    """Save an instance's KVM runtime
372

373
    """
374
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
375
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
376
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
377
    self._WriteKVMRuntime(instance.name, serialized_form)
378

    
379
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
380
    """Load an instance's KVM runtime
381

382
    """
383
    if not serialized_runtime:
384
      serialized_runtime = self._ReadKVMRuntime(instance.name)
385
    loaded_runtime = serializer.Load(serialized_runtime)
386
    kvm_cmd, serialized_nics, hvparams = loaded_runtime
387
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
388
    return (kvm_cmd, kvm_nics, hvparams)
389

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

393
    @type incoming: tuple of strings
394
    @param incoming: (target_host_ip, port)
395

396
    """
397
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
398
    if alive:
399
      raise errors.HypervisorError("Failed to start instance %s: %s" %
400
                                   (instance.name, "already running"))
401

    
402
    temp_files = []
403

    
404
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
405

    
406
    if not kvm_nics:
407
      kvm_cmd.extend(['-net', 'none'])
408
    else:
409
      nic_type = hvparams[constants.HV_NIC_TYPE]
410
      if nic_type == constants.HT_NIC_PARAVIRTUAL:
411
        nic_model = "model=virtio"
412
      else:
413
        nic_model = "model=%s" % nic_type
414

    
415
      for nic_seq, nic in enumerate(kvm_nics):
416
        nic_val = "nic,macaddr=%s,%s" % (nic.mac, nic_model)
417
        script = self._WriteNetScript(instance, nic_seq, nic)
418
        kvm_cmd.extend(['-net', nic_val])
419
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
420
        temp_files.append(script)
421

    
422
    if incoming:
423
      target, port = incoming
424
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
425

    
426
    result = utils.RunCmd(kvm_cmd)
427
    if result.failed:
428
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
429
                                   (instance.name, result.fail_reason,
430
                                    result.output))
431

    
432
    if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
433
      raise errors.HypervisorError("Failed to start instance %s: %s" %
434
                                   (instance.name))
435

    
436
    for filename in temp_files:
437
      utils.RemoveFile(filename)
438

    
439
  def StartInstance(self, instance, block_devices):
440
    """Start an instance.
441

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

    
448
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices)
449
    self._SaveKVMRuntime(instance, kvm_runtime)
450
    self._ExecuteKVMRuntime(instance, kvm_runtime)
451

    
452
  def _CallMonitorCommand(self, instance_name, command):
453
    """Invoke a command on the instance monitor.
454

455
    """
456
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
457
             (utils.ShellQuote(command),
458
              constants.SOCAT_PATH,
459
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
460
    result = utils.RunCmd(socat)
461
    if result.failed:
462
      msg = ("Failed to send command '%s' to instance %s."
463
             " output: %s, error: %s, fail_reason: %s" %
464
             (command, instance_name,
465
              result.stdout, result.stderr, result.fail_reason))
466
      raise errors.HypervisorError(msg)
467

    
468
    return result
469

    
470
  def _RetryInstancePowerdown(self, instance, pid, timeout=30):
471
    """Wait for an instance  to power down.
472

473
    """
474
    # Wait up to $timeout seconds
475
    end = time.time() + timeout
476
    wait = 1
477
    while time.time() < end and utils.IsProcessAlive(pid):
478
      self._CallMonitorCommand(instance.name, 'system_powerdown')
479
      time.sleep(wait)
480
      # Make wait time longer for next try
481
      if wait < 5:
482
        wait *= 1.3
483

    
484
  def StopInstance(self, instance, force=False):
485
    """Stop an instance.
486

487
    """
488
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
489
    if pid > 0 and alive:
490
      if force or not instance.hvparams[constants.HV_ACPI]:
491
        utils.KillProcess(pid)
492
      else:
493
        self._RetryInstancePowerdown(instance, pid)
494

    
495
    if not utils.IsProcessAlive(pid):
496
      utils.RemoveFile(pidfile)
497
      utils.RemoveFile(self._InstanceMonitor(instance.name))
498
      utils.RemoveFile(self._InstanceSerial(instance.name))
499
      utils.RemoveFile(self._InstanceKVMRuntime(instance.name))
500
      return True
501
    else:
502
      return False
503

    
504
  def RebootInstance(self, instance):
505
    """Reboot an instance.
506

507
    """
508
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
509
    # socket the instance will stop, but now power up again. So we'll resort
510
    # to shutdown and restart.
511
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
512
    if not alive:
513
      raise errors.HypervisorError("Failed to reboot instance %s: not running" %
514
                                             (instance.name))
515
    # StopInstance will delete the saved KVM runtime so:
516
    # ...first load it...
517
    kvm_runtime = self._LoadKVMRuntime(instance)
518
    # ...now we can safely call StopInstance...
519
    if not self.StopInstance(instance):
520
      self.StopInstance(instance, force=True)
521
    # ...and finally we can save it again, and execute it...
522
    self._SaveKVMRuntime(instance, kvm_runtime)
523
    self._ExecuteKVMRuntime(instance, kvm_runtime)
524

    
525
  def MigrationInfo(self, instance):
526
    """Get instance information to perform a migration.
527

528
    @type instance: L{objects.Instance}
529
    @param instance: instance to be migrated
530
    @rtype: string
531
    @return: content of the KVM runtime file
532

533
    """
534
    return self._ReadKVMRuntime(instance.name)
535

    
536
  def AcceptInstance(self, instance, info, target):
537
    """Prepare to accept an instance.
538

539
    @type instance: L{objects.Instance}
540
    @param instance: instance to be accepted
541
    @type info: string
542
    @param info: content of the KVM runtime file on the source node
543
    @type target: string
544
    @param target: target host (usually ip), on this node
545

546
    """
547
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
548
    incoming_address = (target, constants.KVM_MIGRATION_PORT)
549
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
550

    
551
  def FinalizeMigration(self, instance, info, success):
552
    """Finalize an instance migration.
553

554
    Stop the incoming mode KVM.
555

556
    @type instance: L{objects.Instance}
557
    @param instance: instance whose migration is being aborted
558

559
    """
560
    if success:
561
      self._WriteKVMRuntime(instance.name, info)
562
    else:
563
      self.StopInstance(instance, force=True)
564

    
565
  def MigrateInstance(self, instance_name, target, live):
566
    """Migrate an instance to a target node.
567

568
    The migration will not be attempted if the instance is not
569
    currently running.
570

571
    @type instance_name: string
572
    @param instance_name: name of the instance to be migrated
573
    @type target: string
574
    @param target: ip address of the target node
575
    @type live: boolean
576
    @param live: perform a live migration
577

578
    """
579
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
580
    if not alive:
581
      raise errors.HypervisorError("Instance not running, cannot migrate")
582

    
583
    if not live:
584
      self._CallMonitorCommand(instance_name, 'stop')
585

    
586
    migrate_command = ('migrate -d tcp:%s:%s' %
587
                       (target, constants.KVM_MIGRATION_PORT))
588
    self._CallMonitorCommand(instance_name, migrate_command)
589

    
590
    info_command = 'info migrate'
591
    done = False
592
    while not done:
593
      result = self._CallMonitorCommand(instance_name, info_command)
594
      match = self._MIGRATION_STATUS_RE.search(result.stdout)
595
      if not match:
596
        raise errors.HypervisorError("Unknown 'info migrate' result: %s" %
597
                                     result.stdout)
598
      else:
599
        status = match.group(1)
600
        if status == 'completed':
601
          done = True
602
        elif status == 'active':
603
          time.sleep(2)
604
        elif status == 'failed' or status == 'cancelled':
605
          if not live:
606
            self._CallMonitorCommand(instance_name, 'cont')
607
          raise errors.HypervisorError("Migration %s at the kvm level" %
608
                                       status)
609
        else:
610
          logging.info("KVM: unknown migration status '%s'" % status)
611
          time.sleep(2)
612

    
613
    utils.KillProcess(pid)
614
    utils.RemoveFile(pidfile)
615
    utils.RemoveFile(self._InstanceMonitor(instance_name))
616
    utils.RemoveFile(self._InstanceSerial(instance_name))
617
    utils.RemoveFile(self._InstanceKVMRuntime(instance_name))
618

    
619
  def GetNodeInfo(self):
620
    """Return information about the node.
621

622
    @return: a dict with the following keys (values in MiB):
623
          - memory_total: the total memory size on the node
624
          - memory_free: the available memory on the node for instances
625
          - memory_dom0: the memory used by the node itself, if available
626

627
    """
628
    # global ram usage from the xm info command
629
    # memory                 : 3583
630
    # free_memory            : 747
631
    # note: in xen 3, memory has changed to total_memory
632
    try:
633
      fh = file("/proc/meminfo")
634
      try:
635
        data = fh.readlines()
636
      finally:
637
        fh.close()
638
    except EnvironmentError, err:
639
      raise errors.HypervisorError("Failed to list node info: %s" % err)
640

    
641
    result = {}
642
    sum_free = 0
643
    for line in data:
644
      splitfields = line.split(":", 1)
645

    
646
      if len(splitfields) > 1:
647
        key = splitfields[0].strip()
648
        val = splitfields[1].strip()
649
        if key == 'MemTotal':
650
          result['memory_total'] = int(val.split()[0])/1024
651
        elif key in ('MemFree', 'Buffers', 'Cached'):
652
          sum_free += int(val.split()[0])/1024
653
        elif key == 'Active':
654
          result['memory_dom0'] = int(val.split()[0])/1024
655
    result['memory_free'] = sum_free
656

    
657
    cpu_total = 0
658
    try:
659
      fh = open("/proc/cpuinfo")
660
      try:
661
        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
662
                                   fh.read()))
663
      finally:
664
        fh.close()
665
    except EnvironmentError, err:
666
      raise errors.HypervisorError("Failed to list node info: %s" % err)
667
    result['cpu_total'] = cpu_total
668
    # FIXME: export correct data here
669
    result['cpu_nodes'] = 1
670
    result['cpu_sockets'] = 1
671

    
672
    return result
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 os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
731
        raise errors.HypervisorError("The kernel path must be an absolute path"
732
                                     ", if defined")
733

    
734
      if not hvparams[constants.HV_ROOT_PATH]:
735
        raise errors.HypervisorError("Need a root partition for the instance"
736
                                     ", if a kernel is defined")
737

    
738
    if hvparams[constants.HV_INITRD_PATH]:
739
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
740
        raise errors.HypervisorError("The initrd path must an absolute path"
741
                                     ", if defined")
742

    
743
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
744
    if vnc_bind_address:
745
      if not utils.IsValidIP(vnc_bind_address):
746
        if not os.path.isabs(vnc_bind_address):
747
          raise errors.HypervisorError("The VNC bind address must be either"
748
                                       " a valid IP address or an absolute"
749
                                       " pathname. '%s' given" %
750
                                       vnc_bind_address)
751

    
752
    if hvparams[constants.HV_VNC_X509_VERIFY] and \
753
      not hvparams[constants.HV_VNC_X509]:
754
        raise errors.HypervisorError("%s must be defined, if %s is" %
755
                                     (constants.HV_VNC_X509,
756
                                      constants.HV_VNC_X509_VERIFY))
757

    
758
    if hvparams[constants.HV_VNC_X509]:
759
      if not os.path.isabs(hvparams[constants.HV_VNC_X509]):
760
        raise errors.HypervisorError("The vnc x509 path must an absolute path"
761
                                     ", if defined")
762

    
763
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
764
    if iso_path and not os.path.isabs(iso_path):
765
      raise errors.HypervisorError("The path to the CDROM image must be"
766
                                   " an absolute path, if defined")
767

    
768
    boot_order = hvparams[constants.HV_BOOT_ORDER]
769
    if boot_order not in ('cdrom', 'disk', 'network'):
770
      raise errors.HypervisorError("The boot order must be 'cdrom', 'disk' or"
771
                                   " 'network'")
772

    
773
    if boot_order == 'cdrom' and not iso_path:
774
      raise errors.HypervisorError("Cannot boot from cdrom without an ISO path")
775

    
776
    nic_type = hvparams[constants.HV_NIC_TYPE]
777
    if nic_type not in constants.HT_KVM_VALID_NIC_TYPES:
778
      raise errors.HypervisorError("Invalid NIC type %s specified for the KVM"
779
                                   " hypervisor. Please choose one of: %s" %
780
                                   (nic_type,
781
                                    constants.HT_KVM_VALID_NIC_TYPES))
782
    elif boot_order == 'network' and nic_type == constants.HT_NIC_PARAVIRTUAL:
783
      raise errors.HypervisorError("Cannot boot from a paravirtual NIC. Please"
784
                                   " change the nic type.")
785

    
786
    disk_type = hvparams[constants.HV_DISK_TYPE]
787
    if disk_type not in constants.HT_KVM_VALID_DISK_TYPES:
788
      raise errors.HypervisorError("Invalid disk type %s specified for the KVM"
789
                                   " hypervisor. Please choose one of: %s" %
790
                                   (disk_type,
791
                                    constants.HT_KVM_VALID_DISK_TYPES))
792

    
793
    mouse_type = hvparams[constants.HV_USB_MOUSE]
794
    if mouse_type and mouse_type not in ('mouse', 'tablet'):
795
      raise errors.HypervisorError("Invalid usb mouse type %s specified for"
796
                                   " the KVM hyervisor. Please choose"
797
                                   " 'mouse' or 'tablet'" % mouse_type)
798

    
799
  def ValidateParameters(self, hvparams):
800
    """Check the given parameters for validity.
801

802
    For the KVM hypervisor, this checks the existence of the
803
    kernel.
804

805
    """
806
    super(KVMHypervisor, self).ValidateParameters(hvparams)
807

    
808
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
809
    if kernel_path and not os.path.isfile(kernel_path):
810
      raise errors.HypervisorError("Instance kernel '%s' not found or"
811
                                   " not a file" % kernel_path)
812
    initrd_path = hvparams[constants.HV_INITRD_PATH]
813
    if initrd_path and not os.path.isfile(initrd_path):
814
      raise errors.HypervisorError("Instance initrd '%s' not found or"
815
                                   " not a file" % initrd_path)
816

    
817
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
818
    if vnc_bind_address and not utils.IsValidIP(vnc_bind_address) and \
819
       not os.path.isdir(vnc_bind_address):
820
       raise errors.HypervisorError("Instance vnc bind address must be either"
821
                                    " an ip address or an existing directory")
822

    
823
    vnc_x509 = hvparams[constants.HV_VNC_X509]
824
    if vnc_x509 and not os.path.isdir(vnc_x509):
825
      raise errors.HypervisorError("Instance vnc x509 path '%s' not found"
826
                                   " or not a directory" % vnc_x509)
827

    
828
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
829
    if iso_path and not os.path.isfile(iso_path):
830
      raise errors.HypervisorError("Instance cdrom image '%s' not found or"
831
                                   " not a file" % iso_path)