Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 43440815

History | View | Annotate | Download (28.1 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
    ]
66

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

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

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

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

    
86
    return (pidfile, pid, alive)
87

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

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

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

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

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

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

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

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

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

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

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

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

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

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

167
    @param instance_name: the instance name
168

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

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

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

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

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

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

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

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

207
    """
208
    data = []
209
    for name in os.listdir(self._PIDS_DIR):
210
      filename = "%s/%s" % (self._PIDS_DIR, name)
211
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
212
        try:
213
          info = self.GetInstanceInfo(name)
214
        except errors.HypervisorError, err:
215
          continue
216
        if info:
217
          data.append(info)
218

    
219
    return data
220

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

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

    
237
    boot_disk = (instance.hvparams[constants.HV_BOOT_ORDER] == "disk")
238
    boot_cdrom = (instance.hvparams[constants.HV_BOOT_ORDER] == "cdrom")
239
    for cfdev, dev_path in block_devices:
240
      if cfdev.mode != constants.DISK_RDWR:
241
        raise errors.HypervisorError("Instance has read-only disks which"
242
                                     " are not supported by KVM")
243
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
244
      if boot_disk:
245
        kvm_cmd.extend(['-boot', 'c'])
246
        boot_val = ',boot=on'
247
        boot_disk = False
248
      else:
249
        boot_val = ''
250

    
251
      # TODO: handle different if= types
252
      if_val = ',if=virtio'
253

    
254
      drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
255
      kvm_cmd.extend(['-drive', drive_val])
256

    
257
    iso_image = instance.hvparams[constants.HV_CDROM_IMAGE_PATH]
258
    if iso_image:
259
      options = ',format=raw,media=cdrom'
260
      if boot_cdrom:
261
        kvm_cmd.extend(['-boot', 'd'])
262
        options = '%s,boot=on' % options
263
      else:
264
        options = '%s,if=virtio' % options
265
      drive_val = 'file=%s%s' % (iso_image, options)
266
      kvm_cmd.extend(['-drive', drive_val])
267

    
268
    kernel_path = instance.hvparams[constants.HV_KERNEL_PATH]
269
    if kernel_path:
270
      kvm_cmd.extend(['-kernel', kernel_path])
271
      initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
272
      if initrd_path:
273
        kvm_cmd.extend(['-initrd', initrd_path])
274
      root_append = 'root=%s ro' % instance.hvparams[constants.HV_ROOT_PATH]
275
      if instance.hvparams[constants.HV_SERIAL_CONSOLE]:
276
        kvm_cmd.extend(['-append', 'console=ttyS0,38400 %s' % root_append])
277
      else:
278
        kvm_cmd.extend(['-append', root_append])
279

    
280
    # FIXME: handle vnc password
281
    vnc_bind_address = instance.hvparams[constants.HV_VNC_BIND_ADDRESS]
282
    if vnc_bind_address:
283
      kvm_cmd.extend(['-usbdevice', 'tablet'])
284
      if utils.IsValidIP(vnc_bind_address):
285
        if instance.network_port > constants.VNC_BASE_PORT:
286
          display = instance.network_port - constants.VNC_BASE_PORT
287
          if vnc_bind_address == '0.0.0.0':
288
            vnc_arg = ':%d' % (display)
289
          else:
290
            vnc_arg = '%s:%d' % (constants.HV_VNC_BIND_ADDRESS, display)
291
        else:
292
          logging.error("Network port is not a valid VNC display (%d < %d)."
293
                        " Not starting VNC" %
294
                        (instance.network_port,
295
                         constants.VNC_BASE_PORT))
296
          vnc_arg = 'none'
297

    
298
        # Only allow tls and other option when not binding to a file, for now.
299
        # kvm/qemu gets confused otherwise about the filename to use.
300
        vnc_append = ''
301
        if instance.hvparams[constants.HV_VNC_TLS]:
302
          vnc_append = '%s,tls' % vnc_append
303
          if instance.hvparams[constants.HV_VNC_X509_VERIFY]:
304
            vnc_append = '%s,x509verify=%s' % (vnc_append,
305
              instance.hvparams[constants.HV_VNC_X509])
306
          elif instance.hvparams[constants.HV_VNC_X509]:
307
            vnc_append = '%s,x509=%s' % (vnc_append,
308
              instance.hvparams[constants.HV_VNC_X509])
309
        vnc_arg = '%s%s' % (vnc_arg, vnc_append)
310

    
311
      else:
312
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
313

    
314
      kvm_cmd.extend(['-vnc', vnc_arg])
315
    else:
316
      kvm_cmd.extend(['-nographic'])
317

    
318
    monitor_dev = 'unix:%s,server,nowait' % \
319
      self._InstanceMonitor(instance.name)
320
    kvm_cmd.extend(['-monitor', monitor_dev])
321
    if instance.hvparams[constants.HV_SERIAL_CONSOLE]:
322
      serial_dev = 'unix:%s,server,nowait' % self._InstanceSerial(instance.name)
323
      kvm_cmd.extend(['-serial', serial_dev])
324
    else:
325
      kvm_cmd.extend(['-serial', 'none'])
326

    
327
    # Save the current instance nics, but defer their expansion as parameters,
328
    # as we'll need to generate executable temp files for them.
329
    kvm_nics = instance.nics
330

    
331
    return (kvm_cmd, kvm_nics)
332

    
333
  def _WriteKVMRuntime(self, instance_name, data):
334
    """Write an instance's KVM runtime
335

336
    """
337
    try:
338
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
339
                      data=data)
340
    except EnvironmentError, err:
341
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
342

    
343
  def _ReadKVMRuntime(self, instance_name):
344
    """Read an instance's KVM runtime
345

346
    """
347
    try:
348
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
349
    except EnvironmentError, err:
350
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
351
    return file_content
352

    
353
  def _SaveKVMRuntime(self, instance, kvm_runtime):
354
    """Save an instance's KVM runtime
355

356
    """
357
    kvm_cmd, kvm_nics = kvm_runtime
358
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
359
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics))
360
    self._WriteKVMRuntime(instance.name, serialized_form)
361

    
362
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
363
    """Load an instance's KVM runtime
364

365
    """
366
    if not serialized_runtime:
367
      serialized_runtime = self._ReadKVMRuntime(instance.name)
368
    loaded_runtime = serializer.Load(serialized_runtime)
369
    kvm_cmd, serialized_nics = loaded_runtime
370
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
371
    return (kvm_cmd, kvm_nics)
372

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

376
    @type incoming: tuple of strings
377
    @param incoming: (target_host_ip, port)
378

379
    """
380
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
381
    if alive:
382
      raise errors.HypervisorError("Failed to start instance %s: %s" %
383
                                   (instance.name, "already running"))
384

    
385
    temp_files = []
386

    
387
    kvm_cmd, kvm_nics = kvm_runtime
388

    
389
    if not kvm_nics:
390
      kvm_cmd.extend(['-net', 'none'])
391
    else:
392
      for nic_seq, nic in enumerate(kvm_nics):
393
        nic_val = "nic,macaddr=%s,model=virtio" % nic.mac
394
        script = self._WriteNetScript(instance, nic_seq, nic)
395
        kvm_cmd.extend(['-net', nic_val])
396
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
397
        temp_files.append(script)
398

    
399
    if incoming:
400
      target, port = incoming
401
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
402

    
403
    result = utils.RunCmd(kvm_cmd)
404
    if result.failed:
405
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
406
                                   (instance.name, result.fail_reason,
407
                                    result.output))
408

    
409
    if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
410
      raise errors.HypervisorError("Failed to start instance %s: %s" %
411
                                   (instance.name))
412

    
413
    for filename in temp_files:
414
      utils.RemoveFile(filename)
415

    
416
  def StartInstance(self, instance, block_devices, extra_args):
417
    """Start an instance.
418

419
    """
420
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
421
    if alive:
422
      raise errors.HypervisorError("Failed to start instance %s: %s" %
423
                                   (instance.name, "already running"))
424

    
425
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices, extra_args)
426
    self._SaveKVMRuntime(instance, kvm_runtime)
427
    self._ExecuteKVMRuntime(instance, kvm_runtime)
428

    
429
  def _CallMonitorCommand(self, instance_name, command):
430
    """Invoke a command on the instance monitor.
431

432
    """
433
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
434
             (utils.ShellQuote(command),
435
              constants.SOCAT_PATH,
436
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
437
    result = utils.RunCmd(socat)
438
    if result.failed:
439
      msg = ("Failed to send command '%s' to instance %s."
440
             " output: %s, error: %s, fail_reason: %s" %
441
             (instance.name, result.stdout, result.stderr, result.fail_reason))
442
      raise errors.HypervisorError(msg)
443

    
444
    return result
445

    
446
  def _RetryInstancePowerdown(self, instance, pid, timeout=30):
447
    """Wait for an instance  to power down.
448

449
    """
450
    # Wait up to $timeout seconds
451
    end = time.time() + timeout
452
    wait = 1
453
    while time.time() < end and utils.IsProcessAlive(pid):
454
      self._CallMonitorCommand(instance.name, 'system_powerdown')
455
      time.sleep(wait)
456
      # Make wait time longer for next try
457
      if wait < 5:
458
        wait *= 1.3
459

    
460
  def StopInstance(self, instance, force=False):
461
    """Stop an instance.
462

463
    """
464
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
465
    if pid > 0 and alive:
466
      if force or not instance.hvparams[constants.HV_ACPI]:
467
        utils.KillProcess(pid)
468
      else:
469
        self._RetryInstancePowerdown(instance, pid)
470

    
471
    if not utils.IsProcessAlive(pid):
472
      utils.RemoveFile(pidfile)
473
      utils.RemoveFile(self._InstanceMonitor(instance.name))
474
      utils.RemoveFile(self._InstanceSerial(instance.name))
475
      utils.RemoveFile(self._InstanceKVMRuntime(instance.name))
476
      return True
477
    else:
478
      return False
479

    
480
  def RebootInstance(self, instance):
481
    """Reboot an instance.
482

483
    """
484
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
485
    # socket the instance will stop, but now power up again. So we'll resort
486
    # to shutdown and restart.
487
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
488
    if not alive:
489
      raise errors.HypervisorError("Failed to reboot instance %s: not running" %
490
                                             (instance.name))
491
    # StopInstance will delete the saved KVM runtime so:
492
    # ...first load it...
493
    kvm_runtime = self._LoadKVMRuntime(instance)
494
    # ...now we can safely call StopInstance...
495
    if not self.StopInstance(instance):
496
      self.StopInstance(instance, force=True)
497
    # ...and finally we can save it again, and execute it...
498
    self._SaveKVMRuntime(instance, kvm_runtime)
499
    self._ExecuteKVMRuntime(instance, kvm_runtime)
500

    
501
  def MigrationInfo(self, instance):
502
    """Get instance information to perform a migration.
503

504
    @type instance: L{objects.Instance}
505
    @param instance: instance to be migrated
506
    @rtype: string
507
    @return: content of the KVM runtime file
508

509
    """
510
    return self._ReadKVMRuntime(instance.name)
511

    
512
  def AcceptInstance(self, instance, info, target):
513
    """Prepare to accept an instance.
514

515
    @type instance: L{objects.Instance}
516
    @param instance: instance to be accepted
517
    @type info: string
518
    @param info: content of the KVM runtime file on the source node
519
    @type target: string
520
    @param target: target host (usually ip), on this node
521

522
    """
523
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
524
    incoming_address = (target, constants.KVM_MIGRATION_PORT)
525
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
526

    
527
  def FinalizeMigration(self, instance, info, success):
528
    """Finalize an instance migration.
529

530
    Stop the incoming mode KVM.
531

532
    @type instance: L{objects.Instance}
533
    @param instance: instance whose migration is being aborted
534

535
    """
536
    if success:
537
      self._WriteKVMRuntime(instance.name, info)
538
    else:
539
      self.StopInstance(instance, force=True)
540

    
541
  def MigrateInstance(self, instance_name, target, live):
542
    """Migrate an instance to a target node.
543

544
    The migration will not be attempted if the instance is not
545
    currently running.
546

547
    @type instance_name: string
548
    @param instance_name: name of the instance to be migrated
549
    @type target: string
550
    @param target: ip address of the target node
551
    @type live: boolean
552
    @param live: perform a live migration
553

554
    """
555
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
556
    if not alive:
557
      raise errors.HypervisorError("Instance not running, cannot migrate")
558

    
559
    if not live:
560
      self._CallMonitorCommand(instance_name, 'stop')
561

    
562
    migrate_command = ('migrate -d tcp:%s:%s' %
563
                       (target, constants.KVM_MIGRATION_PORT))
564
    self._CallMonitorCommand(instance_name, migrate_command)
565

    
566
    info_command = 'info migrate'
567
    done = False
568
    while not done:
569
      result = self._CallMonitorCommand(instance_name, info_command)
570
      match = self._MIGRATION_STATUS_RE.search(result.stdout)
571
      if not match:
572
        raise errors.HypervisorError("Unknown 'info migrate' result: %s" %
573
                                     result.stdout)
574
      else:
575
        status = match.group(1)
576
        if status == 'completed':
577
          done = True
578
        elif status == 'active':
579
          time.sleep(2)
580
        elif status == 'failed' or status == 'cancelled':
581
          if not live:
582
            self._CallMonitorCommand(instance_name, 'cont')
583
          raise errors.HypervisorError("Migration %s at the kvm level" %
584
                                       status)
585
        else:
586
          logging.info("KVM: unknown migration status '%s'" % status)
587
          time.sleep(2)
588

    
589
    utils.KillProcess(pid)
590
    utils.RemoveFile(pidfile)
591
    utils.RemoveFile(self._InstanceMonitor(instance_name))
592
    utils.RemoveFile(self._InstanceSerial(instance_name))
593
    utils.RemoveFile(self._InstanceKVMRuntime(instance_name))
594

    
595
  def GetNodeInfo(self):
596
    """Return information about the node.
597

598
    @return: a dict with the following keys (values in MiB):
599
          - memory_total: the total memory size on the node
600
          - memory_free: the available memory on the node for instances
601
          - memory_dom0: the memory used by the node itself, if available
602

603
    """
604
    # global ram usage from the xm info command
605
    # memory                 : 3583
606
    # free_memory            : 747
607
    # note: in xen 3, memory has changed to total_memory
608
    try:
609
      fh = file("/proc/meminfo")
610
      try:
611
        data = fh.readlines()
612
      finally:
613
        fh.close()
614
    except EnvironmentError, err:
615
      raise errors.HypervisorError("Failed to list node info: %s" % err)
616

    
617
    result = {}
618
    sum_free = 0
619
    for line in data:
620
      splitfields = line.split(":", 1)
621

    
622
      if len(splitfields) > 1:
623
        key = splitfields[0].strip()
624
        val = splitfields[1].strip()
625
        if key == 'MemTotal':
626
          result['memory_total'] = int(val.split()[0])/1024
627
        elif key in ('MemFree', 'Buffers', 'Cached'):
628
          sum_free += int(val.split()[0])/1024
629
        elif key == 'Active':
630
          result['memory_dom0'] = int(val.split()[0])/1024
631
    result['memory_free'] = sum_free
632

    
633
    cpu_total = 0
634
    try:
635
      fh = open("/proc/cpuinfo")
636
      try:
637
        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
638
                                   fh.read()))
639
      finally:
640
        fh.close()
641
    except EnvironmentError, err:
642
      raise errors.HypervisorError("Failed to list node info: %s" % err)
643
    result['cpu_total'] = cpu_total
644
    # FIXME: export correct data here
645
    result['cpu_nodes'] = 1
646
    result['cpu_sockets'] = 1
647

    
648
    return result
649

    
650
  @classmethod
651
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
652
    """Return a command for connecting to the console of an instance.
653

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

    
669
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
670
    if vnc_bind_address:
671
      if instance.network_port > constants.VNC_BASE_PORT:
672
        display = instance.network_port - constants.VNC_BASE_PORT
673
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
674
                       " (display: %d)'" % (vnc_bind_address,
675
                                            instance.network_port,
676
                                            display))
677
        shell_command = "%s; %s" % (vnc_command, shell_command)
678

    
679
    return shell_command
680

    
681
  def Verify(self):
682
    """Verify the hypervisor.
683

684
    Check that the binary exists.
685

686
    """
687
    if not os.path.exists(constants.KVM_PATH):
688
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
689
    if not os.path.exists(constants.SOCAT_PATH):
690
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
691

    
692

    
693
  @classmethod
694
  def CheckParameterSyntax(cls, hvparams):
695
    """Check the given parameters for validity.
696

697
    For the KVM hypervisor, this only check the existence of the
698
    kernel.
699

700
    @type hvparams:  dict
701
    @param hvparams: dictionary with parameter names/value
702
    @raise errors.HypervisorError: when a parameter is not valid
703

704
    """
705
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
706

    
707
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
708
    if kernel_path:
709
      if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
710
        raise errors.HypervisorError("The kernel path must be an absolute path"
711
                                     ", if defined")
712

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

    
717
    if hvparams[constants.HV_INITRD_PATH]:
718
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
719
        raise errors.HypervisorError("The initrd path must an absolute path"
720
                                     ", if defined")
721

    
722
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
723
    if vnc_bind_address:
724
      if not utils.IsValidIP(vnc_bind_address):
725
        if not os.path.isabs(vnc_bind_address):
726
          raise errors.HypervisorError("The VNC bind address must be either"
727
                                       " a valid IP address or an absolute"
728
                                       " pathname. '%s' given" %
729
                                       vnc_bind_address)
730

    
731
    if hvparams[constants.HV_VNC_X509_VERIFY] and \
732
      not hvparams[constants.HV_VNC_X509]:
733
        raise errors.HypervisorError("%s must be defined, if %s is" %
734
                                     (constants.HV_VNC_X509,
735
                                      constants.HV_VNC_X509_VERIFY))
736

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

    
742
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
743
    if iso_path and not os.path.isabs(iso_path):
744
      raise errors.HypervisorError("The path to the CDROM image must be"
745
                                   " an absolute path, if defined")
746

    
747
    boot_order = hvparams[constants.HV_BOOT_ORDER]
748
    if boot_order not in ('cdrom', 'disk'):
749
      raise errors.HypervisorError("The boot order must be 'cdrom' or 'disk'")
750

    
751
    if boot_order == 'cdrom' and not iso_path:
752
      raise errors.HypervisorError("Cannot boot from cdrom without an ISO path")
753

    
754
    nic_type = hvparams[constants.HV_NIC_TYPE]
755
    if nic_type not in constants.HT_KVM_VALID_NIC_TYPES:
756
      raise errors.HypervisorError("Invalid NIC type %s specified for the KVM"
757
                                   " hypervisor. Please choose one of: %s" %
758
                                   (nic_type,
759
                                    constants.HT_KVM_VALID_NIC_TYPES))
760

    
761
    disk_type = hvparams[constants.HV_DISK_TYPE]
762
    if disk_type not in constants.HT_KVM_VALID_DISK_TYPES:
763
      raise errors.HypervisorError("Invalid disk type %s specified for the KVM"
764
                                   " hypervisor. Please choose one of: %s" %
765
                                   (disk_type,
766
                                    constants.HT_KVM_VALID_DISK_TYPES))
767

    
768
  def ValidateParameters(self, hvparams):
769
    """Check the given parameters for validity.
770

771
    For the KVM hypervisor, this checks the existence of the
772
    kernel.
773

774
    """
775
    super(KVMHypervisor, self).ValidateParameters(hvparams)
776

    
777
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
778
    if kernel_path and not os.path.isfile(kernel_path):
779
      raise errors.HypervisorError("Instance kernel '%s' not found or"
780
                                   " not a file" % kernel_path)
781
    initrd_path = hvparams[constants.HV_INITRD_PATH]
782
    if initrd_path and not os.path.isfile(initrd_path):
783
      raise errors.HypervisorError("Instance initrd '%s' not found or"
784
                                   " not a file" % initrd_path)
785

    
786
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
787
    if vnc_bind_address and not utils.IsValidIP(vnc_bind_address) and \
788
       not os.path.isdir(vnc_bind_address):
789
       raise errors.HypervisorError("Instance vnc bind address must be either"
790
                                    " an ip address or an existing directory")
791

    
792
    vnc_x509 = hvparams[constants.HV_VNC_X509]
793
    if vnc_x509 and not os.path.isdir(vnc_x509):
794
      raise errors.HypervisorError("Instance vnc x509 path '%s' not found"
795
                                   " or not a directory" % vnc_x509)
796

    
797
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
798
    if iso_path and not os.path.isfile(iso_path):
799
      raise errors.HypervisorError("Instance cdrom image '%s' not found or"
800
                                   " not a file" % iso_path)