Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 8b2d1013

History | View | Annotate | Download (26 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
    ]
62

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

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

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

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

    
82
    return (pidfile, pid, alive)
83

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

88
    """
89
    return '%s/%s.monitor' % (cls._CTRL_DIR, instance_name)
90

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

95
    """
96
    return '%s/%s.serial' % (cls._CTRL_DIR, instance_name)
97

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

102
    """
103
    return '%s/%s.runtime' % (cls._CONF_DIR, instance_name)
104

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

108
    This can be used by any qemu-type hypervisor.
109

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

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

    
146
  def ListInstances(self):
147
    """Get the list of running instances.
148

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

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

    
160
  def GetInstanceInfo(self, instance_name):
161
    """Get instance properties.
162

163
    @param instance_name: the instance name
164

165
    @return: tuple (name, id, memory, vcpus, stat, times)
166

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

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

    
183
    memory = 0
184
    vcpus = 0
185
    stat = "---b-"
186
    times = "0"
187

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

    
196
    return (instance_name, pid, memory, vcpus, stat, times)
197

    
198
  def GetAllInstancesInfo(self):
199
    """Get properties of all instances.
200

201
    @return: list of tuples (name, id, memory, vcpus, stat, times)
202

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

    
215
    return data
216

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

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

    
233
    boot_drive = True
234
    for cfdev, dev_path in block_devices:
235
      if cfdev.mode != constants.DISK_RDWR:
236
        raise errors.HypervisorError("Instance has read-only disks which"
237
                                     " are not supported by KVM")
238
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
239
      if boot_drive:
240
        boot_val = ',boot=on'
241
        boot_drive = False
242
      else:
243
        boot_val = ''
244

    
245
      # TODO: handle different if= types
246
      if_val = ',if=virtio'
247

    
248
      drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
249
      kvm_cmd.extend(['-drive', drive_val])
250

    
251
    kernel_path = instance.hvparams[constants.HV_KERNEL_PATH]
252
    if kernel_path:
253
      kvm_cmd.extend(['-kernel', kernel_path])
254
      initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
255
      if initrd_path:
256
        kvm_cmd.extend(['-initrd', initrd_path])
257
      root_append = 'root=%s ro' % instance.hvparams[constants.HV_ROOT_PATH]
258
      if instance.hvparams[constants.HV_SERIAL_CONSOLE]:
259
        kvm_cmd.extend(['-append', 'console=ttyS0,38400 %s' % root_append])
260
      else:
261
        kvm_cmd.extend(['-append', root_append])
262

    
263
    #"hvm_boot_order",
264
    #"hvm_cdrom_image_path",
265

    
266
    # FIXME: handle vnc password
267
    vnc_bind_address = instance.hvparams[constants.HV_VNC_BIND_ADDRESS]
268
    if vnc_bind_address:
269
      kvm_cmd.extend(['-usbdevice', 'tablet'])
270
      if utils.IsValidIP(vnc_bind_address):
271
        if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
272
          display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
273
          if vnc_bind_address == '0.0.0.0':
274
            vnc_arg = ':%d' % (display)
275
          else:
276
            vnc_arg = '%s:%d' % (constants.HV_VNC_BIND_ADDRESS, display)
277
        else:
278
          logging.error("Network port is not a valid VNC display (%d < %d)."
279
                        " Not starting VNC" %
280
                        (instance.network_port,
281
                         constants.HT_HVM_VNC_BASE_PORT))
282
          vnc_arg = 'none'
283

    
284
        # Only allow tls and other option when not binding to a file, for now.
285
        # kvm/qemu gets confused otherwise about the filename to use.
286
        vnc_append = ''
287
        if instance.hvparams[constants.HV_VNC_TLS]:
288
          vnc_append = '%s,tls' % vnc_append
289
          if instance.hvparams[constants.HV_VNC_X509_VERIFY]:
290
            vnc_append = '%s,x509verify=%s' % (vnc_append,
291
              instance.hvparams[constants.HV_VNC_X509])
292
          elif instance.hvparams[constants.HV_VNC_X509]:
293
            vnc_append = '%s,x509=%s' % (vnc_append,
294
              instance.hvparams[constants.HV_VNC_X509])
295
        vnc_arg = '%s%s' % (vnc_arg, vnc_append)
296

    
297
      else:
298
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
299

    
300
      kvm_cmd.extend(['-vnc', vnc_arg])
301
    else:
302
      kvm_cmd.extend(['-nographic'])
303

    
304
    monitor_dev = 'unix:%s,server,nowait' % \
305
      self._InstanceMonitor(instance.name)
306
    kvm_cmd.extend(['-monitor', monitor_dev])
307
    if instance.hvparams[constants.HV_SERIAL_CONSOLE]:
308
      serial_dev = 'unix:%s,server,nowait' % self._InstanceSerial(instance.name)
309
      kvm_cmd.extend(['-serial', serial_dev])
310
    else:
311
      kvm_cmd.extend(['-serial', 'none'])
312

    
313
    # Save the current instance nics, but defer their expansion as parameters,
314
    # as we'll need to generate executable temp files for them.
315
    kvm_nics = instance.nics
316

    
317
    return (kvm_cmd, kvm_nics)
318

    
319
  def _WriteKVMRuntime(self, instance_name, data):
320
    """Write an instance's KVM runtime
321

322
    """
323
    try:
324
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
325
                      data=data)
326
    except EnvironmentError, err:
327
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
328

    
329
  def _ReadKVMRuntime(self, instance_name):
330
    """Read an instance's KVM runtime
331

332
    """
333
    try:
334
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
335
    except EnvironmentError, err:
336
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
337
    return file_content
338

    
339
  def _SaveKVMRuntime(self, instance, kvm_runtime):
340
    """Save an instance's KVM runtime
341

342
    """
343
    kvm_cmd, kvm_nics = kvm_runtime
344
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
345
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics))
346
    self._WriteKVMRuntime(instance.name, serialized_form)
347

    
348
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
349
    """Load an instance's KVM runtime
350

351
    """
352
    if not serialized_runtime:
353
      serialized_runtime = self._ReadKVMRuntime(instance.name)
354
    loaded_runtime = serializer.Load(serialized_runtime)
355
    kvm_cmd, serialized_nics = loaded_runtime
356
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
357
    return (kvm_cmd, kvm_nics)
358

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

362
    @type incoming: tuple of strings
363
    @param incoming: (target_host_ip, port)
364

365
    """
366
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
367
    if alive:
368
      raise errors.HypervisorError("Failed to start instance %s: %s" %
369
                                   (instance.name, "already running"))
370

    
371
    temp_files = []
372

    
373
    kvm_cmd, kvm_nics = kvm_runtime
374

    
375
    if not kvm_nics:
376
      kvm_cmd.extend(['-net', 'none'])
377
    else:
378
      for nic_seq, nic in enumerate(kvm_nics):
379
        nic_val = "nic,macaddr=%s,model=virtio" % nic.mac
380
        script = self._WriteNetScript(instance, nic_seq, nic)
381
        kvm_cmd.extend(['-net', nic_val])
382
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
383
        temp_files.append(script)
384

    
385
    if incoming:
386
      target, port = incoming
387
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
388

    
389
    result = utils.RunCmd(kvm_cmd)
390
    if result.failed:
391
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
392
                                   (instance.name, result.fail_reason,
393
                                    result.output))
394

    
395
    if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
396
      raise errors.HypervisorError("Failed to start instance %s: %s" %
397
                                   (instance.name))
398

    
399
    for filename in temp_files:
400
      utils.RemoveFile(filename)
401

    
402
  def StartInstance(self, instance, block_devices, extra_args):
403
    """Start an instance.
404

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

    
411
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices, extra_args)
412
    self._SaveKVMRuntime(instance, kvm_runtime)
413
    self._ExecuteKVMRuntime(instance, kvm_runtime)
414

    
415
  def _CallMonitorCommand(self, instance_name, command):
416
    """Invoke a command on the instance monitor.
417

418
    """
419
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
420
             (utils.ShellQuote(command),
421
              constants.SOCAT_PATH,
422
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
423
    result = utils.RunCmd(socat)
424
    if result.failed:
425
      msg = ("Failed to send command '%s' to instance %s."
426
             " output: %s, error: %s, fail_reason: %s" %
427
             (instance.name, result.stdout, result.stderr, result.fail_reason))
428
      raise errors.HypervisorError(msg)
429

    
430
    return result
431

    
432
  def _RetryInstancePowerdown(self, instance, pid, timeout=30):
433
    """Wait for an instance  to power down.
434

435
    """
436
    # Wait up to $timeout seconds
437
    end = time.time() + timeout
438
    wait = 1
439
    while time.time() < end and utils.IsProcessAlive(pid):
440
      self._CallMonitorCommand(instance.name, 'system_powerdown')
441
      time.sleep(wait)
442
      # Make wait time longer for next try
443
      if wait < 5:
444
        wait *= 1.3
445

    
446
  def StopInstance(self, instance, force=False):
447
    """Stop an instance.
448

449
    """
450
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
451
    if pid > 0 and alive:
452
      if force or not instance.hvparams[constants.HV_ACPI]:
453
        utils.KillProcess(pid)
454
      else:
455
        self._RetryInstancePowerdown(instance, pid)
456

    
457
    if not utils.IsProcessAlive(pid):
458
      utils.RemoveFile(pidfile)
459
      utils.RemoveFile(self._InstanceMonitor(instance.name))
460
      utils.RemoveFile(self._InstanceSerial(instance.name))
461
      utils.RemoveFile(self._InstanceKVMRuntime(instance.name))
462
      return True
463
    else:
464
      return False
465

    
466
  def RebootInstance(self, instance):
467
    """Reboot an instance.
468

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

    
487
  def MigrationInfo(self, instance):
488
    """Get instance information to perform a migration.
489

490
    @type instance: L{objects.Instance}
491
    @param instance: instance to be migrated
492
    @rtype: string
493
    @return: content of the KVM runtime file
494

495
    """
496
    return self._ReadKVMRuntime(instance.name)
497

    
498
  def AcceptInstance(self, instance, info, target):
499
    """Prepare to accept an instance.
500

501
    @type instance: L{objects.Instance}
502
    @param instance: instance to be accepted
503
    @type info: string
504
    @param info: content of the KVM runtime file on the source node
505
    @type target: string
506
    @param target: target host (usually ip), on this node
507

508
    """
509
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
510
    incoming_address = (target, constants.KVM_MIGRATION_PORT)
511
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
512

    
513
  def FinalizeMigration(self, instance, info, success):
514
    """Finalize an instance migration.
515

516
    Stop the incoming mode KVM.
517

518
    @type instance: L{objects.Instance}
519
    @param instance: instance whose migration is being aborted
520

521
    """
522
    if success:
523
      self._WriteKVMRuntime(instance.name, info)
524
    else:
525
      self.StopInstance(instance, force=True)
526

    
527
  def MigrateInstance(self, instance_name, target, live):
528
    """Migrate an instance to a target node.
529

530
    The migration will not be attempted if the instance is not
531
    currently running.
532

533
    @type instance_name: string
534
    @param instance_name: name of the instance to be migrated
535
    @type target: string
536
    @param target: ip address of the target node
537
    @type live: boolean
538
    @param live: perform a live migration
539

540
    """
541
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
542
    if not alive:
543
      raise errors.HypervisorError("Instance not running, cannot migrate")
544

    
545
    if not live:
546
      self._CallMonitorCommand(instance_name, 'stop')
547

    
548
    migrate_command = ('migrate -d tcp:%s:%s' %
549
                       (target, constants.KVM_MIGRATION_PORT))
550
    self._CallMonitorCommand(instance_name, migrate_command)
551

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

    
575
    utils.KillProcess(pid)
576
    utils.RemoveFile(pidfile)
577
    utils.RemoveFile(self._InstanceMonitor(instance_name))
578
    utils.RemoveFile(self._InstanceSerial(instance_name))
579
    utils.RemoveFile(self._InstanceKVMRuntime(instance_name))
580

    
581
  def GetNodeInfo(self):
582
    """Return information about the node.
583

584
    @return: a dict with the following keys (values in MiB):
585
          - memory_total: the total memory size on the node
586
          - memory_free: the available memory on the node for instances
587
          - memory_dom0: the memory used by the node itself, if available
588

589
    """
590
    # global ram usage from the xm info command
591
    # memory                 : 3583
592
    # free_memory            : 747
593
    # note: in xen 3, memory has changed to total_memory
594
    try:
595
      fh = file("/proc/meminfo")
596
      try:
597
        data = fh.readlines()
598
      finally:
599
        fh.close()
600
    except EnvironmentError, err:
601
      raise errors.HypervisorError("Failed to list node info: %s" % err)
602

    
603
    result = {}
604
    sum_free = 0
605
    for line in data:
606
      splitfields = line.split(":", 1)
607

    
608
      if len(splitfields) > 1:
609
        key = splitfields[0].strip()
610
        val = splitfields[1].strip()
611
        if key == 'MemTotal':
612
          result['memory_total'] = int(val.split()[0])/1024
613
        elif key in ('MemFree', 'Buffers', 'Cached'):
614
          sum_free += int(val.split()[0])/1024
615
        elif key == 'Active':
616
          result['memory_dom0'] = int(val.split()[0])/1024
617
    result['memory_free'] = sum_free
618

    
619
    cpu_total = 0
620
    try:
621
      fh = open("/proc/cpuinfo")
622
      try:
623
        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
624
                                   fh.read()))
625
      finally:
626
        fh.close()
627
    except EnvironmentError, err:
628
      raise errors.HypervisorError("Failed to list node info: %s" % err)
629
    result['cpu_total'] = cpu_total
630

    
631
    return result
632

    
633
  @classmethod
634
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
635
    """Return a command for connecting to the console of an instance.
636

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

    
652
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
653
    if vnc_bind_address:
654
      if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
655
        display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
656
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
657
                       " (display: %d)'" % (vnc_bind_address,
658
                                            instance.network_port,
659
                                            display))
660
        shell_command = "%s; %s" % (vnc_command, shell_command)
661

    
662
    return shell_command
663

    
664
  def Verify(self):
665
    """Verify the hypervisor.
666

667
    Check that the binary exists.
668

669
    """
670
    if not os.path.exists(constants.KVM_PATH):
671
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
672
    if not os.path.exists(constants.SOCAT_PATH):
673
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
674

    
675

    
676
  @classmethod
677
  def CheckParameterSyntax(cls, hvparams):
678
    """Check the given parameters for validity.
679

680
    For the KVM hypervisor, this only check the existence of the
681
    kernel.
682

683
    @type hvparams:  dict
684
    @param hvparams: dictionary with parameter names/value
685
    @raise errors.HypervisorError: when a parameter is not valid
686

687
    """
688
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
689

    
690
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
691
    if kernel_path:
692
      if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
693
        raise errors.HypervisorError("The kernel path must be an absolute path"
694
                                     ", if defined")
695

    
696
      if not hvparams[constants.HV_ROOT_PATH]:
697
        raise errors.HypervisorError("Need a root partition for the instance"
698
                                     ", if a kernel is defined")
699

    
700
    if hvparams[constants.HV_INITRD_PATH]:
701
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
702
        raise errors.HypervisorError("The initrd path must an absolute path"
703
                                     ", if defined")
704

    
705
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
706
    if vnc_bind_address:
707
      if not utils.IsValidIP(vnc_bind_address):
708
        if not os.path.isabs(vnc_bind_address):
709
          raise errors.HypervisorError("The VNC bind address must be either"
710
                                       " a valid IP address or an absolute"
711
                                       " pathname. '%s' given" %
712
                                       vnc_bind_address)
713

    
714
    if hvparams[constants.HV_VNC_X509_VERIFY] and \
715
      not hvparams[constants.HV_VNC_X509]:
716
        raise errors.HypervisorError("%s must be defined, if %s is" %
717
                                     (constants.HV_VNC_X509,
718
                                      constants.HV_VNC_X509_VERIFY))
719

    
720
    if hvparams[constants.HV_VNC_X509]:
721
      if not os.path.isabs(hvparams[constants.HV_VNC_X509]):
722
        raise errors.HypervisorError("The vnc x509 path must an absolute path"
723
                                     ", if defined")
724

    
725
  def ValidateParameters(self, hvparams):
726
    """Check the given parameters for validity.
727

728
    For the KVM hypervisor, this checks the existence of the
729
    kernel.
730

731
    """
732
    super(KVMHypervisor, self).ValidateParameters(hvparams)
733

    
734
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
735
    if kernel_path and not os.path.isfile(kernel_path):
736
      raise errors.HypervisorError("Instance kernel '%s' not found or"
737
                                   " not a file" % kernel_path)
738
    initrd_path = hvparams[constants.HV_INITRD_PATH]
739
    if initrd_path and not os.path.isfile(initrd_path):
740
      raise errors.HypervisorError("Instance initrd '%s' not found or"
741
                                   " not a file" % initrd_path)
742

    
743
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
744
    if vnc_bind_address and not utils.IsValidIP(vnc_bind_address) and \
745
       not os.path.isdir(vnc_bind_address):
746
       raise errors.HypervisorError("Instance vnc bind address must be either"
747
                                    " an ip address or an existing directory")
748

    
749
    vnc_x509 = hvparams[constants.HV_VNC_X509]
750
    if vnc_x509 and not os.path.isdir(vnc_x509):
751
      raise errors.HypervisorError("Instance vnc x509 path '%s' not found"
752
                                   " or not a directory" % vnc_x509)
753