Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 66d5dbef

History | View | Annotate | Download (27 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
        boot_val = ',boot=on'
244
        boot_disk = False
245
      else:
246
        boot_val = ''
247

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

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

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

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

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

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

    
305
      else:
306
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
307

    
308
      kvm_cmd.extend(['-vnc', vnc_arg])
309
    else:
310
      kvm_cmd.extend(['-nographic'])
311

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

    
321
    # Save the current instance nics, but defer their expansion as parameters,
322
    # as we'll need to generate executable temp files for them.
323
    kvm_nics = instance.nics
324

    
325
    return (kvm_cmd, kvm_nics)
326

    
327
  def _WriteKVMRuntime(self, instance_name, data):
328
    """Write an instance's KVM runtime
329

330
    """
331
    try:
332
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
333
                      data=data)
334
    except EnvironmentError, err:
335
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
336

    
337
  def _ReadKVMRuntime(self, instance_name):
338
    """Read an instance's KVM runtime
339

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

    
347
  def _SaveKVMRuntime(self, instance, kvm_runtime):
348
    """Save an instance's KVM runtime
349

350
    """
351
    kvm_cmd, kvm_nics = kvm_runtime
352
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
353
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics))
354
    self._WriteKVMRuntime(instance.name, serialized_form)
355

    
356
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
357
    """Load an instance's KVM runtime
358

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

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

370
    @type incoming: tuple of strings
371
    @param incoming: (target_host_ip, port)
372

373
    """
374
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
375
    if alive:
376
      raise errors.HypervisorError("Failed to start instance %s: %s" %
377
                                   (instance.name, "already running"))
378

    
379
    temp_files = []
380

    
381
    kvm_cmd, kvm_nics = kvm_runtime
382

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

    
393
    if incoming:
394
      target, port = incoming
395
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
396

    
397
    result = utils.RunCmd(kvm_cmd)
398
    if result.failed:
399
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
400
                                   (instance.name, result.fail_reason,
401
                                    result.output))
402

    
403
    if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
404
      raise errors.HypervisorError("Failed to start instance %s: %s" %
405
                                   (instance.name))
406

    
407
    for filename in temp_files:
408
      utils.RemoveFile(filename)
409

    
410
  def StartInstance(self, instance, block_devices, extra_args):
411
    """Start an instance.
412

413
    """
414
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
415
    if alive:
416
      raise errors.HypervisorError("Failed to start instance %s: %s" %
417
                                   (instance.name, "already running"))
418

    
419
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices, extra_args)
420
    self._SaveKVMRuntime(instance, kvm_runtime)
421
    self._ExecuteKVMRuntime(instance, kvm_runtime)
422

    
423
  def _CallMonitorCommand(self, instance_name, command):
424
    """Invoke a command on the instance monitor.
425

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

    
438
    return result
439

    
440
  def _RetryInstancePowerdown(self, instance, pid, timeout=30):
441
    """Wait for an instance  to power down.
442

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

    
454
  def StopInstance(self, instance, force=False):
455
    """Stop an instance.
456

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

    
465
    if not utils.IsProcessAlive(pid):
466
      utils.RemoveFile(pidfile)
467
      utils.RemoveFile(self._InstanceMonitor(instance.name))
468
      utils.RemoveFile(self._InstanceSerial(instance.name))
469
      utils.RemoveFile(self._InstanceKVMRuntime(instance.name))
470
      return True
471
    else:
472
      return False
473

    
474
  def RebootInstance(self, instance):
475
    """Reboot an instance.
476

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

    
495
  def MigrationInfo(self, instance):
496
    """Get instance information to perform a migration.
497

498
    @type instance: L{objects.Instance}
499
    @param instance: instance to be migrated
500
    @rtype: string
501
    @return: content of the KVM runtime file
502

503
    """
504
    return self._ReadKVMRuntime(instance.name)
505

    
506
  def AcceptInstance(self, instance, info, target):
507
    """Prepare to accept an instance.
508

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

516
    """
517
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
518
    incoming_address = (target, constants.KVM_MIGRATION_PORT)
519
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
520

    
521
  def FinalizeMigration(self, instance, info, success):
522
    """Finalize an instance migration.
523

524
    Stop the incoming mode KVM.
525

526
    @type instance: L{objects.Instance}
527
    @param instance: instance whose migration is being aborted
528

529
    """
530
    if success:
531
      self._WriteKVMRuntime(instance.name, info)
532
    else:
533
      self.StopInstance(instance, force=True)
534

    
535
  def MigrateInstance(self, instance_name, target, live):
536
    """Migrate an instance to a target node.
537

538
    The migration will not be attempted if the instance is not
539
    currently running.
540

541
    @type instance_name: string
542
    @param instance_name: name of the instance to be migrated
543
    @type target: string
544
    @param target: ip address of the target node
545
    @type live: boolean
546
    @param live: perform a live migration
547

548
    """
549
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
550
    if not alive:
551
      raise errors.HypervisorError("Instance not running, cannot migrate")
552

    
553
    if not live:
554
      self._CallMonitorCommand(instance_name, 'stop')
555

    
556
    migrate_command = ('migrate -d tcp:%s:%s' %
557
                       (target, constants.KVM_MIGRATION_PORT))
558
    self._CallMonitorCommand(instance_name, migrate_command)
559

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

    
583
    utils.KillProcess(pid)
584
    utils.RemoveFile(pidfile)
585
    utils.RemoveFile(self._InstanceMonitor(instance_name))
586
    utils.RemoveFile(self._InstanceSerial(instance_name))
587
    utils.RemoveFile(self._InstanceKVMRuntime(instance_name))
588

    
589
  def GetNodeInfo(self):
590
    """Return information about the node.
591

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

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

    
611
    result = {}
612
    sum_free = 0
613
    for line in data:
614
      splitfields = line.split(":", 1)
615

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

    
627
    cpu_total = 0
628
    try:
629
      fh = open("/proc/cpuinfo")
630
      try:
631
        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
632
                                   fh.read()))
633
      finally:
634
        fh.close()
635
    except EnvironmentError, err:
636
      raise errors.HypervisorError("Failed to list node info: %s" % err)
637
    result['cpu_total'] = cpu_total
638

    
639
    return result
640

    
641
  @classmethod
642
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
643
    """Return a command for connecting to the console of an instance.
644

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

    
660
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
661
    if vnc_bind_address:
662
      if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
663
        display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
664
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
665
                       " (display: %d)'" % (vnc_bind_address,
666
                                            instance.network_port,
667
                                            display))
668
        shell_command = "%s; %s" % (vnc_command, shell_command)
669

    
670
    return shell_command
671

    
672
  def Verify(self):
673
    """Verify the hypervisor.
674

675
    Check that the binary exists.
676

677
    """
678
    if not os.path.exists(constants.KVM_PATH):
679
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
680
    if not os.path.exists(constants.SOCAT_PATH):
681
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
682

    
683

    
684
  @classmethod
685
  def CheckParameterSyntax(cls, hvparams):
686
    """Check the given parameters for validity.
687

688
    For the KVM hypervisor, this only check the existence of the
689
    kernel.
690

691
    @type hvparams:  dict
692
    @param hvparams: dictionary with parameter names/value
693
    @raise errors.HypervisorError: when a parameter is not valid
694

695
    """
696
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
697

    
698
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
699
    if kernel_path:
700
      if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
701
        raise errors.HypervisorError("The kernel path must be an absolute path"
702
                                     ", if defined")
703

    
704
      if not hvparams[constants.HV_ROOT_PATH]:
705
        raise errors.HypervisorError("Need a root partition for the instance"
706
                                     ", if a kernel is defined")
707

    
708
    if hvparams[constants.HV_INITRD_PATH]:
709
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
710
        raise errors.HypervisorError("The initrd path must an absolute path"
711
                                     ", if defined")
712

    
713
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
714
    if vnc_bind_address:
715
      if not utils.IsValidIP(vnc_bind_address):
716
        if not os.path.isabs(vnc_bind_address):
717
          raise errors.HypervisorError("The VNC bind address must be either"
718
                                       " a valid IP address or an absolute"
719
                                       " pathname. '%s' given" %
720
                                       vnc_bind_address)
721

    
722
    if hvparams[constants.HV_VNC_X509_VERIFY] and \
723
      not hvparams[constants.HV_VNC_X509]:
724
        raise errors.HypervisorError("%s must be defined, if %s is" %
725
                                     (constants.HV_VNC_X509,
726
                                      constants.HV_VNC_X509_VERIFY))
727

    
728
    if hvparams[constants.HV_VNC_X509]:
729
      if not os.path.isabs(hvparams[constants.HV_VNC_X509]):
730
        raise errors.HypervisorError("The vnc x509 path must an absolute path"
731
                                     ", if defined")
732

    
733
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
734
    if iso_path and not os.path.isabs(iso_path):
735
      raise errors.HypervisorError("The path to the CDROM image must be"
736
                                   " an absolute path, if defined")
737

    
738
    boot_order = hvparams[constants.HV_BOOT_ORDER]
739
    if boot_order not in ('cdrom', 'disk'):
740
      raise errors.HypervisorError("The boot order must be 'cdrom' or 'disk'")
741

    
742
  def ValidateParameters(self, hvparams):
743
    """Check the given parameters for validity.
744

745
    For the KVM hypervisor, this checks the existence of the
746
    kernel.
747

748
    """
749
    super(KVMHypervisor, self).ValidateParameters(hvparams)
750

    
751
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
752
    if kernel_path and not os.path.isfile(kernel_path):
753
      raise errors.HypervisorError("Instance kernel '%s' not found or"
754
                                   " not a file" % kernel_path)
755
    initrd_path = hvparams[constants.HV_INITRD_PATH]
756
    if initrd_path and not os.path.isfile(initrd_path):
757
      raise errors.HypervisorError("Instance initrd '%s' not found or"
758
                                   " not a file" % initrd_path)
759

    
760
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
761
    if vnc_bind_address and not utils.IsValidIP(vnc_bind_address) and \
762
       not os.path.isdir(vnc_bind_address):
763
       raise errors.HypervisorError("Instance vnc bind address must be either"
764
                                    " an ip address or an existing directory")
765

    
766
    vnc_x509 = hvparams[constants.HV_VNC_X509]
767
    if vnc_x509 and not os.path.isdir(vnc_x509):
768
      raise errors.HypervisorError("Instance vnc x509 path '%s' not found"
769
                                   " or not a directory" % vnc_x509)
770

    
771
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
772
    if iso_path and not os.path.isfile(iso_path):
773
      raise errors.HypervisorError("Instance cdrom image '%s' not found or"
774
                                   " not a file" % iso_path)
775

    
776