Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 11344a50

History | View | Annotate | Download (29.4 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_ACPI,
56
    constants.HV_SERIAL_CONSOLE,
57
    constants.HV_VNC_BIND_ADDRESS,
58
    constants.HV_VNC_TLS,
59
    constants.HV_VNC_X509,
60
    constants.HV_VNC_X509_VERIFY,
61
    constants.HV_CDROM_IMAGE_PATH,
62
    constants.HV_BOOT_ORDER,
63
    constants.HV_NIC_TYPE,
64
    constants.HV_DISK_TYPE,
65
    constants.HV_USB_MOUSE,
66
    ]
67

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

    
71
  def __init__(self):
72
    hv_base.BaseHypervisor.__init__(self)
73
    # Let's make sure the directories we need exist, even if the RUN_DIR lives
74
    # in a tmpfs filesystem or has been otherwise wiped out.
75
    for mydir in self._DIRS:
76
      if not os.path.exists(mydir):
77
        os.mkdir(mydir)
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, extra_args):
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
    boot_disk = (instance.hvparams[constants.HV_BOOT_ORDER] == "disk")
239
    boot_cdrom = (instance.hvparams[constants.HV_BOOT_ORDER] == "cdrom")
240
    boot_network = (instance.hvparams[constants.HV_BOOT_ORDER] == "network")
241

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

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

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

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

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

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

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

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

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

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

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

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

    
345
    return (kvm_cmd, kvm_nics, hvparams)
346

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

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

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

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

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

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

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

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

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

390
    @type incoming: tuple of strings
391
    @param incoming: (target_host_ip, port)
392

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

    
399
    temp_files = []
400

    
401
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
402

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

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

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

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

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

    
433
    for filename in temp_files:
434
      utils.RemoveFile(filename)
435

    
436
  def StartInstance(self, instance, block_devices, extra_args):
437
    """Start an instance.
438

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

    
445
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices, extra_args)
446
    self._SaveKVMRuntime(instance, kvm_runtime)
447
    self._ExecuteKVMRuntime(instance, kvm_runtime)
448

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

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

    
464
    return result
465

    
466
  def _RetryInstancePowerdown(self, instance, pid, timeout=30):
467
    """Wait for an instance  to power down.
468

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

    
480
  def StopInstance(self, instance, force=False):
481
    """Stop an instance.
482

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

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

    
500
  def RebootInstance(self, instance):
501
    """Reboot an instance.
502

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

    
521
  def MigrationInfo(self, instance):
522
    """Get instance information to perform a migration.
523

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

529
    """
530
    return self._ReadKVMRuntime(instance.name)
531

    
532
  def AcceptInstance(self, instance, info, target):
533
    """Prepare to accept an instance.
534

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

542
    """
543
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
544
    incoming_address = (target, constants.KVM_MIGRATION_PORT)
545
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
546

    
547
  def FinalizeMigration(self, instance, info, success):
548
    """Finalize an instance migration.
549

550
    Stop the incoming mode KVM.
551

552
    @type instance: L{objects.Instance}
553
    @param instance: instance whose migration is being aborted
554

555
    """
556
    if success:
557
      self._WriteKVMRuntime(instance.name, info)
558
    else:
559
      self.StopInstance(instance, force=True)
560

    
561
  def MigrateInstance(self, instance_name, target, live):
562
    """Migrate an instance to a target node.
563

564
    The migration will not be attempted if the instance is not
565
    currently running.
566

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

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

    
579
    if not live:
580
      self._CallMonitorCommand(instance_name, 'stop')
581

    
582
    migrate_command = ('migrate -d tcp:%s:%s' %
583
                       (target, constants.KVM_MIGRATION_PORT))
584
    self._CallMonitorCommand(instance_name, migrate_command)
585

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

    
609
    utils.KillProcess(pid)
610
    utils.RemoveFile(pidfile)
611
    utils.RemoveFile(self._InstanceMonitor(instance_name))
612
    utils.RemoveFile(self._InstanceSerial(instance_name))
613
    utils.RemoveFile(self._InstanceKVMRuntime(instance_name))
614

    
615
  def GetNodeInfo(self):
616
    """Return information about the node.
617

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

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

    
637
    result = {}
638
    sum_free = 0
639
    for line in data:
640
      splitfields = line.split(":", 1)
641

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

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

    
668
    return result
669

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

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

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

    
699
    return shell_command
700

    
701
  def Verify(self):
702
    """Verify the hypervisor.
703

704
    Check that the binary exists.
705

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

    
712

    
713
  @classmethod
714
  def CheckParameterSyntax(cls, hvparams):
715
    """Check the given parameters for validity.
716

717
    For the KVM hypervisor, this only check the existence of the
718
    kernel.
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)