Statistics
| Branch: | Tag: | Revision:

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

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
  @classmethod
111
  def _RemoveInstanceRuntimeFiles(cls, pidfile, instance_name):
112
    """Removes an instance's rutime sockets/files.
113

114
    """
115
    utils.RemoveFile(pidfile)
116
    utils.RemoveFile(cls._InstanceMonitor(instance_name))
117
    utils.RemoveFile(cls._InstanceSerial(instance_name))
118
    utils.RemoveFile(cls._InstanceKVMRuntime(instance_name))
119

    
120
  def _WriteNetScript(self, instance, seq, nic):
121
    """Write a script to connect a net interface to the proper bridge.
122

123
    This can be used by any qemu-type hypervisor.
124

125
    @param instance: instance we're acting on
126
    @type instance: instance object
127
    @param seq: nic sequence number
128
    @type seq: int
129
    @param nic: nic we're acting on
130
    @type nic: nic object
131
    @return: netscript file name
132
    @rtype: string
133

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

    
161
  def ListInstances(self):
162
    """Get the list of running instances.
163

164
    We can do this by listing our live instances directory and
165
    checking whether the associated kvm process is still alive.
166

167
    """
168
    result = []
169
    for name in os.listdir(self._PIDS_DIR):
170
      filename = "%s/%s" % (self._PIDS_DIR, name)
171
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
172
        result.append(name)
173
    return result
174

    
175
  def GetInstanceInfo(self, instance_name):
176
    """Get instance properties.
177

178
    @param instance_name: the instance name
179

180
    @return: tuple (name, id, memory, vcpus, stat, times)
181

182
    """
183
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
184
    if not alive:
185
      return None
186

    
187
    cmdline_file = "/proc/%s/cmdline" % pid
188
    try:
189
      fh = open(cmdline_file, 'r')
190
      try:
191
        cmdline = fh.read()
192
      finally:
193
        fh.close()
194
    except EnvironmentError, err:
195
      raise errors.HypervisorError("Failed to list instance %s: %s" %
196
                                   (instance_name, err))
197

    
198
    memory = 0
199
    vcpus = 0
200
    stat = "---b-"
201
    times = "0"
202

    
203
    arg_list = cmdline.split('\x00')
204
    while arg_list:
205
      arg =  arg_list.pop(0)
206
      if arg == '-m':
207
        memory = arg_list.pop(0)
208
      elif arg == '-smp':
209
        vcpus = arg_list.pop(0)
210

    
211
    return (instance_name, pid, memory, vcpus, stat, times)
212

    
213
  def GetAllInstancesInfo(self):
214
    """Get properties of all instances.
215

216
    @return: list of tuples (name, id, memory, vcpus, stat, times)
217

218
    """
219
    data = []
220
    for name in os.listdir(self._PIDS_DIR):
221
      filename = "%s/%s" % (self._PIDS_DIR, name)
222
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
223
        try:
224
          info = self.GetInstanceInfo(name)
225
        except errors.HypervisorError, err:
226
          continue
227
        if info:
228
          data.append(info)
229

    
230
    return data
231

    
232
  def _GenerateKVMRuntime(self, instance, block_devices):
233
    """Generate KVM information to start an instance.
234

235
    """
236
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
237
    kvm = constants.KVM_PATH
238
    kvm_cmd = [kvm]
239
    kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
240
    kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
241
    kvm_cmd.extend(['-pidfile', pidfile])
242
    # used just by the vnc server, if enabled
243
    kvm_cmd.extend(['-name', instance.name])
244
    kvm_cmd.extend(['-daemonize'])
245
    if not instance.hvparams[constants.HV_ACPI]:
246
      kvm_cmd.extend(['-no-acpi'])
247

    
248
    hvp = instance.hvparams
249
    boot_disk = hvp[constants.HV_BOOT_ORDER] == "disk"
250
    boot_cdrom = hvp[constants.HV_BOOT_ORDER] == "cdrom"
251
    boot_network = hvp[constants.HV_BOOT_ORDER] == "network"
252

    
253
    if boot_network:
254
      kvm_cmd.extend(['-boot', 'n'])
255

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

    
274
      drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
275
      kvm_cmd.extend(['-drive', drive_val])
276

    
277
    iso_image = hvp[constants.HV_CDROM_IMAGE_PATH]
278
    if iso_image:
279
      options = ',format=raw,media=cdrom'
280
      if boot_cdrom:
281
        kvm_cmd.extend(['-boot', 'd'])
282
        options = '%s,boot=on' % options
283
      else:
284
        options = '%s,if=virtio' % options
285
      drive_val = 'file=%s%s' % (iso_image, options)
286
      kvm_cmd.extend(['-drive', drive_val])
287

    
288
    kernel_path = hvp[constants.HV_KERNEL_PATH]
289
    if kernel_path:
290
      kvm_cmd.extend(['-kernel', kernel_path])
291
      initrd_path = hvp[constants.HV_INITRD_PATH]
292
      if initrd_path:
293
        kvm_cmd.extend(['-initrd', initrd_path])
294
      root_append = ['root=%s' % hvp[constants.HV_ROOT_PATH],
295
                     hvp[constants.HV_KERNEL_ARGS]]
296
      if hvp[constants.HV_SERIAL_CONSOLE]:
297
        root_append.append('console=ttyS0,38400')
298
      kvm_cmd.extend(['-append', ' '.join(root_append)])
299

    
300
    mouse_type = hvp[constants.HV_USB_MOUSE]
301
    if mouse_type:
302
      kvm_cmd.extend(['-usb'])
303
      kvm_cmd.extend(['-usbdevice', mouse_type])
304

    
305
    # FIXME: handle vnc password
306
    vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS]
307
    if vnc_bind_address:
308
      if utils.IsValidIP(vnc_bind_address):
309
        if instance.network_port > constants.VNC_BASE_PORT:
310
          display = instance.network_port - constants.VNC_BASE_PORT
311
          if vnc_bind_address == '0.0.0.0':
312
            vnc_arg = ':%d' % (display)
313
          else:
314
            vnc_arg = '%s:%d' % (vnc_bind_address, display)
315
        else:
316
          logging.error("Network port is not a valid VNC display (%d < %d)."
317
                        " Not starting VNC" %
318
                        (instance.network_port,
319
                         constants.VNC_BASE_PORT))
320
          vnc_arg = 'none'
321

    
322
        # Only allow tls and other option when not binding to a file, for now.
323
        # kvm/qemu gets confused otherwise about the filename to use.
324
        vnc_append = ''
325
        if hvp[constants.HV_VNC_TLS]:
326
          vnc_append = '%s,tls' % vnc_append
327
          if hvp[constants.HV_VNC_X509_VERIFY]:
328
            vnc_append = '%s,x509verify=%s' % (vnc_append,
329
                                               hvp[constants.HV_VNC_X509])
330
          elif hvp[constants.HV_VNC_X509]:
331
            vnc_append = '%s,x509=%s' % (vnc_append,
332
                                         hvp[constants.HV_VNC_X509])
333
        vnc_arg = '%s%s' % (vnc_arg, vnc_append)
334

    
335
      else:
336
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
337

    
338
      kvm_cmd.extend(['-vnc', vnc_arg])
339
    else:
340
      kvm_cmd.extend(['-nographic'])
341

    
342
    monitor_dev = 'unix:%s,server,nowait' % \
343
      self._InstanceMonitor(instance.name)
344
    kvm_cmd.extend(['-monitor', monitor_dev])
345
    if hvp[constants.HV_SERIAL_CONSOLE]:
346
      serial_dev = ('unix:%s,server,nowait' %
347
                    self._InstanceSerial(instance.name))
348
      kvm_cmd.extend(['-serial', serial_dev])
349
    else:
350
      kvm_cmd.extend(['-serial', 'none'])
351

    
352
    # Save the current instance nics, but defer their expansion as parameters,
353
    # as we'll need to generate executable temp files for them.
354
    kvm_nics = instance.nics
355
    hvparams = hvp
356

    
357
    return (kvm_cmd, kvm_nics, hvparams)
358

    
359
  def _WriteKVMRuntime(self, instance_name, data):
360
    """Write an instance's KVM runtime
361

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

    
369
  def _ReadKVMRuntime(self, instance_name):
370
    """Read an instance's KVM runtime
371

372
    """
373
    try:
374
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
375
    except EnvironmentError, err:
376
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
377
    return file_content
378

    
379
  def _SaveKVMRuntime(self, instance, kvm_runtime):
380
    """Save an instance's KVM runtime
381

382
    """
383
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
384
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
385
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
386
    self._WriteKVMRuntime(instance.name, serialized_form)
387

    
388
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
389
    """Load an instance's KVM runtime
390

391
    """
392
    if not serialized_runtime:
393
      serialized_runtime = self._ReadKVMRuntime(instance.name)
394
    loaded_runtime = serializer.Load(serialized_runtime)
395
    kvm_cmd, serialized_nics, hvparams = loaded_runtime
396
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
397
    return (kvm_cmd, kvm_nics, hvparams)
398

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

402
    @type incoming: tuple of strings
403
    @param incoming: (target_host_ip, port)
404

405
    """
406
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
407
    if alive:
408
      raise errors.HypervisorError("Failed to start instance %s: %s" %
409
                                   (instance.name, "already running"))
410

    
411
    temp_files = []
412

    
413
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
414

    
415
    if not kvm_nics:
416
      kvm_cmd.extend(['-net', 'none'])
417
    else:
418
      nic_type = hvparams[constants.HV_NIC_TYPE]
419
      if nic_type == constants.HT_NIC_PARAVIRTUAL:
420
        nic_model = "model=virtio"
421
      else:
422
        nic_model = "model=%s" % nic_type
423

    
424
      for nic_seq, nic in enumerate(kvm_nics):
425
        nic_val = "nic,macaddr=%s,%s" % (nic.mac, nic_model)
426
        script = self._WriteNetScript(instance, nic_seq, nic)
427
        kvm_cmd.extend(['-net', nic_val])
428
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
429
        temp_files.append(script)
430

    
431
    if incoming:
432
      target, port = incoming
433
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
434

    
435
    result = utils.RunCmd(kvm_cmd)
436
    if result.failed:
437
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
438
                                   (instance.name, result.fail_reason,
439
                                    result.output))
440

    
441
    if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
442
      raise errors.HypervisorError("Failed to start instance %s: %s" %
443
                                   (instance.name))
444

    
445
    for filename in temp_files:
446
      utils.RemoveFile(filename)
447

    
448
  def StartInstance(self, instance, block_devices):
449
    """Start an instance.
450

451
    """
452
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
453
    if alive:
454
      raise errors.HypervisorError("Failed to start instance %s: %s" %
455
                                   (instance.name, "already running"))
456

    
457
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices)
458
    self._SaveKVMRuntime(instance, kvm_runtime)
459
    self._ExecuteKVMRuntime(instance, kvm_runtime)
460

    
461
  def _CallMonitorCommand(self, instance_name, command):
462
    """Invoke a command on the instance monitor.
463

464
    """
465
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
466
             (utils.ShellQuote(command),
467
              constants.SOCAT_PATH,
468
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
469
    result = utils.RunCmd(socat)
470
    if result.failed:
471
      msg = ("Failed to send command '%s' to instance %s."
472
             " output: %s, error: %s, fail_reason: %s" %
473
             (command, instance_name,
474
              result.stdout, result.stderr, result.fail_reason))
475
      raise errors.HypervisorError(msg)
476

    
477
    return result
478

    
479
  def _RetryInstancePowerdown(self, instance, pid, timeout=30):
480
    """Wait for an instance  to power down.
481

482
    """
483
    # Wait up to $timeout seconds
484
    end = time.time() + timeout
485
    wait = 1
486
    while time.time() < end and utils.IsProcessAlive(pid):
487
      self._CallMonitorCommand(instance.name, 'system_powerdown')
488
      time.sleep(wait)
489
      # Make wait time longer for next try
490
      if wait < 5:
491
        wait *= 1.3
492

    
493
  def StopInstance(self, instance, force=False):
494
    """Stop an instance.
495

496
    """
497
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
498
    if pid > 0 and alive:
499
      if force or not instance.hvparams[constants.HV_ACPI]:
500
        utils.KillProcess(pid)
501
      else:
502
        self._RetryInstancePowerdown(instance, pid)
503

    
504
    if not utils.IsProcessAlive(pid):
505
      self._RemoveInstanceRuntimeFiles(pidfile, instance.name)
506
      return True
507
    else:
508
      return False
509

    
510
  def RebootInstance(self, instance):
511
    """Reboot an instance.
512

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

    
531
  def MigrationInfo(self, instance):
532
    """Get instance information to perform a migration.
533

534
    @type instance: L{objects.Instance}
535
    @param instance: instance to be migrated
536
    @rtype: string
537
    @return: content of the KVM runtime file
538

539
    """
540
    return self._ReadKVMRuntime(instance.name)
541

    
542
  def AcceptInstance(self, instance, info, target):
543
    """Prepare to accept an instance.
544

545
    @type instance: L{objects.Instance}
546
    @param instance: instance to be accepted
547
    @type info: string
548
    @param info: content of the KVM runtime file on the source node
549
    @type target: string
550
    @param target: target host (usually ip), on this node
551

552
    """
553
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
554
    incoming_address = (target, constants.KVM_MIGRATION_PORT)
555
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
556

    
557
  def FinalizeMigration(self, instance, info, success):
558
    """Finalize an instance migration.
559

560
    Stop the incoming mode KVM.
561

562
    @type instance: L{objects.Instance}
563
    @param instance: instance whose migration is being aborted
564

565
    """
566
    if success:
567
      self._WriteKVMRuntime(instance.name, info)
568
    else:
569
      self.StopInstance(instance, force=True)
570

    
571
  def MigrateInstance(self, instance_name, target, live):
572
    """Migrate an instance to a target node.
573

574
    The migration will not be attempted if the instance is not
575
    currently running.
576

577
    @type instance_name: string
578
    @param instance_name: name of the instance to be migrated
579
    @type target: string
580
    @param target: ip address of the target node
581
    @type live: boolean
582
    @param live: perform a live migration
583

584
    """
585
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
586
    if not alive:
587
      raise errors.HypervisorError("Instance not running, cannot migrate")
588

    
589
    if not live:
590
      self._CallMonitorCommand(instance_name, 'stop')
591

    
592
    migrate_command = ('migrate -d tcp:%s:%s' %
593
                       (target, constants.KVM_MIGRATION_PORT))
594
    self._CallMonitorCommand(instance_name, migrate_command)
595

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

    
619
    utils.KillProcess(pid)
620
    self._RemoveInstanceRuntimeFiles(pidfile, instance.name)
621

    
622
  def GetNodeInfo(self):
623
    """Return information about the node.
624

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

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

    
644
    result = {}
645
    sum_free = 0
646
    for line in data:
647
      splitfields = line.split(":", 1)
648

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

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

    
675
    return result
676

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

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

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

    
706
    return shell_command
707

    
708
  def Verify(self):
709
    """Verify the hypervisor.
710

711
    Check that the binary exists.
712

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

    
719

    
720
  @classmethod
721
  def CheckParameterSyntax(cls, hvparams):
722
    """Check the given parameters for validity.
723

724
    @type hvparams:  dict
725
    @param hvparams: dictionary with parameter names/value
726
    @raise errors.HypervisorError: when a parameter is not valid
727

728
    """
729
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
730

    
731
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
732
    if kernel_path:
733
      if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
734
        raise errors.HypervisorError("The kernel path must be an absolute path"
735
                                     ", if defined")
736

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

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

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

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

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

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

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

    
776
    if boot_order == 'cdrom' and not iso_path:
777
      raise errors.HypervisorError("Cannot boot from cdrom without an ISO path")
778

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

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

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

    
802
  def ValidateParameters(self, hvparams):
803
    """Check the given parameters for validity.
804

805
    For the KVM hypervisor, this checks the existence of the
806
    kernel.
807

808
    """
809
    super(KVMHypervisor, self).ValidateParameters(hvparams)
810

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

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

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

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