Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 9afb67fe

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
    dirs = [(dir, constants.RUN_DIRS_MODE) for dir in self._DIRS]
77
    utils.EnsureDirs(dirs)
78

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

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

    
87
    return (pidfile, pid, alive)
88

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

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

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

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

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

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

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

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

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

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

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

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

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

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

168
    @param instance_name: the instance name
169

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

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

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

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

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

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

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

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

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

    
220
    return data
221

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
347
    return (kvm_cmd, kvm_nics, hvparams)
348

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

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

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

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

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

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

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

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

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

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

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

    
401
    temp_files = []
402

    
403
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
404

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

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

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

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

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

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

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

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

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

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

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

    
467
    return result
468

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

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

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

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

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

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

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

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

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

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

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

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

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

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

553
    Stop the incoming mode KVM.
554

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
671
    return result
672

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

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

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

    
702
    return shell_command
703

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

707
    Check that the binary exists.
708

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

    
715

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

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

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

    
727
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
728
    if kernel_path:
729
      if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
730
        raise errors.HypervisorError("The kernel path must be an absolute path"
731
                                     ", if defined")
732

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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