Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 377d74c9

History | View | Annotate | Download (27.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_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
    ]
64

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

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

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

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

    
84
    return (pidfile, pid, alive)
85

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

90
    """
91
    return '%s/%s.monitor' % (cls._CTRL_DIR, instance_name)
92

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

97
    """
98
    return '%s/%s.serial' % (cls._CTRL_DIR, instance_name)
99

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

104
    """
105
    return '%s/%s.runtime' % (cls._CONF_DIR, instance_name)
106

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

110
    This can be used by any qemu-type hypervisor.
111

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

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

    
148
  def ListInstances(self):
149
    """Get the list of running instances.
150

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

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

    
162
  def GetInstanceInfo(self, instance_name):
163
    """Get instance properties.
164

165
    @param instance_name: the instance name
166

167
    @return: tuple (name, id, memory, vcpus, stat, times)
168

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

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

    
185
    memory = 0
186
    vcpus = 0
187
    stat = "---b-"
188
    times = "0"
189

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

    
198
    return (instance_name, pid, memory, vcpus, stat, times)
199

    
200
  def GetAllInstancesInfo(self):
201
    """Get properties of all instances.
202

203
    @return: list of tuples (name, id, memory, vcpus, stat, times)
204

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

    
217
    return data
218

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

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

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

    
249
      # TODO: handle different if= types
250
      if_val = ',if=virtio'
251

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

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

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

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

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

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

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

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

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

    
329
    return (kvm_cmd, kvm_nics)
330

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

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

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

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

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

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

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

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

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

374
    @type incoming: tuple of strings
375
    @param incoming: (target_host_ip, port)
376

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

    
383
    temp_files = []
384

    
385
    kvm_cmd, kvm_nics = kvm_runtime
386

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

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

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

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

    
411
    for filename in temp_files:
412
      utils.RemoveFile(filename)
413

    
414
  def StartInstance(self, instance, block_devices, extra_args):
415
    """Start an instance.
416

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

    
423
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices, extra_args)
424
    self._SaveKVMRuntime(instance, kvm_runtime)
425
    self._ExecuteKVMRuntime(instance, kvm_runtime)
426

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

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

    
442
    return result
443

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

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

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

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

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

    
478
  def RebootInstance(self, instance):
479
    """Reboot an instance.
480

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

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

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

507
    """
508
    return self._ReadKVMRuntime(instance.name)
509

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

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

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

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

528
    Stop the incoming mode KVM.
529

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

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

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

542
    The migration will not be attempted if the instance is not
543
    currently running.
544

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

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

    
557
    if not live:
558
      self._CallMonitorCommand(instance_name, 'stop')
559

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

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

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

    
593
  def GetNodeInfo(self):
594
    """Return information about the node.
595

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

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

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

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

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

    
646
    return result
647

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

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

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

    
677
    return shell_command
678

    
679
  def Verify(self):
680
    """Verify the hypervisor.
681

682
    Check that the binary exists.
683

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

    
690

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

695
    For the KVM hypervisor, this only check the existence of the
696
    kernel.
697

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

702
    """
703
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
704

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

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

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

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

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

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

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

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

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

    
752
  def ValidateParameters(self, hvparams):
753
    """Check the given parameters for validity.
754

755
    For the KVM hypervisor, this checks the existence of the
756
    kernel.
757

758
    """
759
    super(KVMHypervisor, self).ValidateParameters(hvparams)
760

    
761
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
762
    if kernel_path and not os.path.isfile(kernel_path):
763
      raise errors.HypervisorError("Instance kernel '%s' not found or"
764
                                   " not a file" % kernel_path)
765
    initrd_path = hvparams[constants.HV_INITRD_PATH]
766
    if initrd_path and not os.path.isfile(initrd_path):
767
      raise errors.HypervisorError("Instance initrd '%s' not found or"
768
                                   " not a file" % initrd_path)
769

    
770
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
771
    if vnc_bind_address and not utils.IsValidIP(vnc_bind_address) and \
772
       not os.path.isdir(vnc_bind_address):
773
       raise errors.HypervisorError("Instance vnc bind address must be either"
774
                                    " an ip address or an existing directory")
775

    
776
    vnc_x509 = hvparams[constants.HV_VNC_X509]
777
    if vnc_x509 and not os.path.isdir(vnc_x509):
778
      raise errors.HypervisorError("Instance vnc x509 path '%s' not found"
779
                                   " or not a directory" % vnc_x509)
780

    
781
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
782
    if iso_path and not os.path.isfile(iso_path):
783
      raise errors.HypervisorError("Instance cdrom image '%s' not found or"
784
                                   " not a file" % iso_path)