Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 37f88dc6

History | View | Annotate | Download (28.5 kB)

1
#
2
#
3

    
4
# Copyright (C) 2008 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""KVM hypervisor
23

24
"""
25

    
26
import os
27
import os.path
28
import re
29
import tempfile
30
import time
31
import logging
32
from cStringIO import StringIO
33

    
34
from ganeti import utils
35
from ganeti import constants
36
from ganeti import errors
37
from ganeti import serializer
38
from ganeti import objects
39
from ganeti.hypervisor import hv_base
40

    
41

    
42
class KVMHypervisor(hv_base.BaseHypervisor):
43
  """KVM hypervisor interface"""
44

    
45
  _ROOT_DIR = constants.RUN_GANETI_DIR + "/kvm-hypervisor"
46
  _PIDS_DIR = _ROOT_DIR + "/pid" # contains live instances pids
47
  _CTRL_DIR = _ROOT_DIR + "/ctrl" # contains instances control sockets
48
  _CONF_DIR = _ROOT_DIR + "/conf" # contains instances startup data
49
  _DIRS = [_ROOT_DIR, _PIDS_DIR, _CTRL_DIR, _CONF_DIR]
50

    
51
  PARAMETERS = [
52
    constants.HV_KERNEL_PATH,
53
    constants.HV_INITRD_PATH,
54
    constants.HV_ROOT_PATH,
55
    constants.HV_ACPI,
56
    constants.HV_SERIAL_CONSOLE,
57
    constants.HV_VNC_BIND_ADDRESS,
58
    constants.HV_VNC_TLS,
59
    constants.HV_VNC_X509,
60
    constants.HV_VNC_X509_VERIFY,
61
    constants.HV_CDROM_IMAGE_PATH,
62
    constants.HV_BOOT_ORDER,
63
    constants.HV_NIC_TYPE,
64
    constants.HV_DISK_TYPE,
65
    ]
66

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

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

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

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

    
86
    return (pidfile, pid, alive)
87

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

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

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

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

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

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

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

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

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

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

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

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

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

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

167
    @param instance_name: the instance name
168

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

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

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

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

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

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

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

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

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

    
219
    return data
220

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

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

    
237
    boot_disk = (instance.hvparams[constants.HV_BOOT_ORDER] == "disk")
238
    boot_cdrom = (instance.hvparams[constants.HV_BOOT_ORDER] == "cdrom")
239

    
240
    disk_type = instance.hvparams[constants.HV_DISK_TYPE]
241
    if disk_type == constants.HT_DISK_PARAVIRTUAL:
242
      if_val = ',if=virtio'
243
    else:
244
      if_val = ',if=%s' % disk_type
245
    for cfdev, dev_path in block_devices:
246
      if cfdev.mode != constants.DISK_RDWR:
247
        raise errors.HypervisorError("Instance has read-only disks which"
248
                                     " are not supported by KVM")
249
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
250
      if boot_disk:
251
        kvm_cmd.extend(['-boot', 'c'])
252
        boot_val = ',boot=on'
253
        boot_disk = False
254
      else:
255
        boot_val = ''
256

    
257
      drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
258
      kvm_cmd.extend(['-drive', drive_val])
259

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

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

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

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

    
314
      else:
315
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
316

    
317
      kvm_cmd.extend(['-vnc', vnc_arg])
318
    else:
319
      kvm_cmd.extend(['-nographic'])
320

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

    
330
    # Save the current instance nics, but defer their expansion as parameters,
331
    # as we'll need to generate executable temp files for them.
332
    kvm_nics = instance.nics
333
    hvparams = instance.hvparams
334

    
335
    return (kvm_cmd, kvm_nics, hvparams)
336

    
337
  def _WriteKVMRuntime(self, instance_name, data):
338
    """Write an instance's KVM runtime
339

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

    
347
  def _ReadKVMRuntime(self, instance_name):
348
    """Read an instance's KVM runtime
349

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

    
357
  def _SaveKVMRuntime(self, instance, kvm_runtime):
358
    """Save an instance's KVM runtime
359

360
    """
361
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
362
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
363
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
364
    self._WriteKVMRuntime(instance.name, serialized_form)
365

    
366
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
367
    """Load an instance's KVM runtime
368

369
    """
370
    if not serialized_runtime:
371
      serialized_runtime = self._ReadKVMRuntime(instance.name)
372
    loaded_runtime = serializer.Load(serialized_runtime)
373
    kvm_cmd, serialized_nics, hvparams = loaded_runtime
374
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
375
    return (kvm_cmd, kvm_nics, hvparams)
376

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

380
    @type incoming: tuple of strings
381
    @param incoming: (target_host_ip, port)
382

383
    """
384
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
385
    if alive:
386
      raise errors.HypervisorError("Failed to start instance %s: %s" %
387
                                   (instance.name, "already running"))
388

    
389
    temp_files = []
390

    
391
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
392

    
393
    if not kvm_nics:
394
      kvm_cmd.extend(['-net', 'none'])
395
    else:
396
      nic_type = hvparams[constants.HV_NIC_TYPE]
397
      if nic_type == constants.HT_NIC_PARAVIRTUAL:
398
        nic_model = "model=virtio"
399
      else:
400
        nic_model = "model=%s" % nic_type
401

    
402
      for nic_seq, nic in enumerate(kvm_nics):
403
        nic_val = "nic,macaddr=%s,%s" % (nic.mac, nic_model)
404
        script = self._WriteNetScript(instance, nic_seq, nic)
405
        kvm_cmd.extend(['-net', nic_val])
406
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
407
        temp_files.append(script)
408

    
409
    if incoming:
410
      target, port = incoming
411
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
412

    
413
    result = utils.RunCmd(kvm_cmd)
414
    if result.failed:
415
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
416
                                   (instance.name, result.fail_reason,
417
                                    result.output))
418

    
419
    if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
420
      raise errors.HypervisorError("Failed to start instance %s: %s" %
421
                                   (instance.name))
422

    
423
    for filename in temp_files:
424
      utils.RemoveFile(filename)
425

    
426
  def StartInstance(self, instance, block_devices, extra_args):
427
    """Start an instance.
428

429
    """
430
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
431
    if alive:
432
      raise errors.HypervisorError("Failed to start instance %s: %s" %
433
                                   (instance.name, "already running"))
434

    
435
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices, extra_args)
436
    self._SaveKVMRuntime(instance, kvm_runtime)
437
    self._ExecuteKVMRuntime(instance, kvm_runtime)
438

    
439
  def _CallMonitorCommand(self, instance_name, command):
440
    """Invoke a command on the instance monitor.
441

442
    """
443
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
444
             (utils.ShellQuote(command),
445
              constants.SOCAT_PATH,
446
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
447
    result = utils.RunCmd(socat)
448
    if result.failed:
449
      msg = ("Failed to send command '%s' to instance %s."
450
             " output: %s, error: %s, fail_reason: %s" %
451
             (instance.name, result.stdout, result.stderr, result.fail_reason))
452
      raise errors.HypervisorError(msg)
453

    
454
    return result
455

    
456
  def _RetryInstancePowerdown(self, instance, pid, timeout=30):
457
    """Wait for an instance  to power down.
458

459
    """
460
    # Wait up to $timeout seconds
461
    end = time.time() + timeout
462
    wait = 1
463
    while time.time() < end and utils.IsProcessAlive(pid):
464
      self._CallMonitorCommand(instance.name, 'system_powerdown')
465
      time.sleep(wait)
466
      # Make wait time longer for next try
467
      if wait < 5:
468
        wait *= 1.3
469

    
470
  def StopInstance(self, instance, force=False):
471
    """Stop an instance.
472

473
    """
474
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
475
    if pid > 0 and alive:
476
      if force or not instance.hvparams[constants.HV_ACPI]:
477
        utils.KillProcess(pid)
478
      else:
479
        self._RetryInstancePowerdown(instance, pid)
480

    
481
    if not utils.IsProcessAlive(pid):
482
      utils.RemoveFile(pidfile)
483
      utils.RemoveFile(self._InstanceMonitor(instance.name))
484
      utils.RemoveFile(self._InstanceSerial(instance.name))
485
      utils.RemoveFile(self._InstanceKVMRuntime(instance.name))
486
      return True
487
    else:
488
      return False
489

    
490
  def RebootInstance(self, instance):
491
    """Reboot an instance.
492

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

    
511
  def MigrationInfo(self, instance):
512
    """Get instance information to perform a migration.
513

514
    @type instance: L{objects.Instance}
515
    @param instance: instance to be migrated
516
    @rtype: string
517
    @return: content of the KVM runtime file
518

519
    """
520
    return self._ReadKVMRuntime(instance.name)
521

    
522
  def AcceptInstance(self, instance, info, target):
523
    """Prepare to accept an instance.
524

525
    @type instance: L{objects.Instance}
526
    @param instance: instance to be accepted
527
    @type info: string
528
    @param info: content of the KVM runtime file on the source node
529
    @type target: string
530
    @param target: target host (usually ip), on this node
531

532
    """
533
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
534
    incoming_address = (target, constants.KVM_MIGRATION_PORT)
535
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
536

    
537
  def FinalizeMigration(self, instance, info, success):
538
    """Finalize an instance migration.
539

540
    Stop the incoming mode KVM.
541

542
    @type instance: L{objects.Instance}
543
    @param instance: instance whose migration is being aborted
544

545
    """
546
    if success:
547
      self._WriteKVMRuntime(instance.name, info)
548
    else:
549
      self.StopInstance(instance, force=True)
550

    
551
  def MigrateInstance(self, instance_name, target, live):
552
    """Migrate an instance to a target node.
553

554
    The migration will not be attempted if the instance is not
555
    currently running.
556

557
    @type instance_name: string
558
    @param instance_name: name of the instance to be migrated
559
    @type target: string
560
    @param target: ip address of the target node
561
    @type live: boolean
562
    @param live: perform a live migration
563

564
    """
565
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
566
    if not alive:
567
      raise errors.HypervisorError("Instance not running, cannot migrate")
568

    
569
    if not live:
570
      self._CallMonitorCommand(instance_name, 'stop')
571

    
572
    migrate_command = ('migrate -d tcp:%s:%s' %
573
                       (target, constants.KVM_MIGRATION_PORT))
574
    self._CallMonitorCommand(instance_name, migrate_command)
575

    
576
    info_command = 'info migrate'
577
    done = False
578
    while not done:
579
      result = self._CallMonitorCommand(instance_name, info_command)
580
      match = self._MIGRATION_STATUS_RE.search(result.stdout)
581
      if not match:
582
        raise errors.HypervisorError("Unknown 'info migrate' result: %s" %
583
                                     result.stdout)
584
      else:
585
        status = match.group(1)
586
        if status == 'completed':
587
          done = True
588
        elif status == 'active':
589
          time.sleep(2)
590
        elif status == 'failed' or status == 'cancelled':
591
          if not live:
592
            self._CallMonitorCommand(instance_name, 'cont')
593
          raise errors.HypervisorError("Migration %s at the kvm level" %
594
                                       status)
595
        else:
596
          logging.info("KVM: unknown migration status '%s'" % status)
597
          time.sleep(2)
598

    
599
    utils.KillProcess(pid)
600
    utils.RemoveFile(pidfile)
601
    utils.RemoveFile(self._InstanceMonitor(instance_name))
602
    utils.RemoveFile(self._InstanceSerial(instance_name))
603
    utils.RemoveFile(self._InstanceKVMRuntime(instance_name))
604

    
605
  def GetNodeInfo(self):
606
    """Return information about the node.
607

608
    @return: a dict with the following keys (values in MiB):
609
          - memory_total: the total memory size on the node
610
          - memory_free: the available memory on the node for instances
611
          - memory_dom0: the memory used by the node itself, if available
612

613
    """
614
    # global ram usage from the xm info command
615
    # memory                 : 3583
616
    # free_memory            : 747
617
    # note: in xen 3, memory has changed to total_memory
618
    try:
619
      fh = file("/proc/meminfo")
620
      try:
621
        data = fh.readlines()
622
      finally:
623
        fh.close()
624
    except EnvironmentError, err:
625
      raise errors.HypervisorError("Failed to list node info: %s" % err)
626

    
627
    result = {}
628
    sum_free = 0
629
    for line in data:
630
      splitfields = line.split(":", 1)
631

    
632
      if len(splitfields) > 1:
633
        key = splitfields[0].strip()
634
        val = splitfields[1].strip()
635
        if key == 'MemTotal':
636
          result['memory_total'] = int(val.split()[0])/1024
637
        elif key in ('MemFree', 'Buffers', 'Cached'):
638
          sum_free += int(val.split()[0])/1024
639
        elif key == 'Active':
640
          result['memory_dom0'] = int(val.split()[0])/1024
641
    result['memory_free'] = sum_free
642

    
643
    cpu_total = 0
644
    try:
645
      fh = open("/proc/cpuinfo")
646
      try:
647
        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
648
                                   fh.read()))
649
      finally:
650
        fh.close()
651
    except EnvironmentError, err:
652
      raise errors.HypervisorError("Failed to list node info: %s" % err)
653
    result['cpu_total'] = cpu_total
654
    # FIXME: export correct data here
655
    result['cpu_nodes'] = 1
656
    result['cpu_sockets'] = 1
657

    
658
    return result
659

    
660
  @classmethod
661
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
662
    """Return a command for connecting to the console of an instance.
663

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

    
679
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
680
    if vnc_bind_address:
681
      if instance.network_port > constants.VNC_BASE_PORT:
682
        display = instance.network_port - constants.VNC_BASE_PORT
683
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
684
                       " (display: %d)'" % (vnc_bind_address,
685
                                            instance.network_port,
686
                                            display))
687
        shell_command = "%s; %s" % (vnc_command, shell_command)
688

    
689
    return shell_command
690

    
691
  def Verify(self):
692
    """Verify the hypervisor.
693

694
    Check that the binary exists.
695

696
    """
697
    if not os.path.exists(constants.KVM_PATH):
698
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
699
    if not os.path.exists(constants.SOCAT_PATH):
700
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
701

    
702

    
703
  @classmethod
704
  def CheckParameterSyntax(cls, hvparams):
705
    """Check the given parameters for validity.
706

707
    For the KVM hypervisor, this only check the existence of the
708
    kernel.
709

710
    @type hvparams:  dict
711
    @param hvparams: dictionary with parameter names/value
712
    @raise errors.HypervisorError: when a parameter is not valid
713

714
    """
715
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
716

    
717
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
718
    if kernel_path:
719
      if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
720
        raise errors.HypervisorError("The kernel path must be an absolute path"
721
                                     ", if defined")
722

    
723
      if not hvparams[constants.HV_ROOT_PATH]:
724
        raise errors.HypervisorError("Need a root partition for the instance"
725
                                     ", if a kernel is defined")
726

    
727
    if hvparams[constants.HV_INITRD_PATH]:
728
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
729
        raise errors.HypervisorError("The initrd path must an absolute path"
730
                                     ", if defined")
731

    
732
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
733
    if vnc_bind_address:
734
      if not utils.IsValidIP(vnc_bind_address):
735
        if not os.path.isabs(vnc_bind_address):
736
          raise errors.HypervisorError("The VNC bind address must be either"
737
                                       " a valid IP address or an absolute"
738
                                       " pathname. '%s' given" %
739
                                       vnc_bind_address)
740

    
741
    if hvparams[constants.HV_VNC_X509_VERIFY] and \
742
      not hvparams[constants.HV_VNC_X509]:
743
        raise errors.HypervisorError("%s must be defined, if %s is" %
744
                                     (constants.HV_VNC_X509,
745
                                      constants.HV_VNC_X509_VERIFY))
746

    
747
    if hvparams[constants.HV_VNC_X509]:
748
      if not os.path.isabs(hvparams[constants.HV_VNC_X509]):
749
        raise errors.HypervisorError("The vnc x509 path must an absolute path"
750
                                     ", if defined")
751

    
752
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
753
    if iso_path and not os.path.isabs(iso_path):
754
      raise errors.HypervisorError("The path to the CDROM image must be"
755
                                   " an absolute path, if defined")
756

    
757
    boot_order = hvparams[constants.HV_BOOT_ORDER]
758
    if boot_order not in ('cdrom', 'disk'):
759
      raise errors.HypervisorError("The boot order must be 'cdrom' or 'disk'")
760

    
761
    if boot_order == 'cdrom' and not iso_path:
762
      raise errors.HypervisorError("Cannot boot from cdrom without an ISO path")
763

    
764
    nic_type = hvparams[constants.HV_NIC_TYPE]
765
    if nic_type not in constants.HT_KVM_VALID_NIC_TYPES:
766
      raise errors.HypervisorError("Invalid NIC type %s specified for the KVM"
767
                                   " hypervisor. Please choose one of: %s" %
768
                                   (nic_type,
769
                                    constants.HT_KVM_VALID_NIC_TYPES))
770

    
771
    disk_type = hvparams[constants.HV_DISK_TYPE]
772
    if disk_type not in constants.HT_KVM_VALID_DISK_TYPES:
773
      raise errors.HypervisorError("Invalid disk type %s specified for the KVM"
774
                                   " hypervisor. Please choose one of: %s" %
775
                                   (disk_type,
776
                                    constants.HT_KVM_VALID_DISK_TYPES))
777

    
778
  def ValidateParameters(self, hvparams):
779
    """Check the given parameters for validity.
780

781
    For the KVM hypervisor, this checks the existence of the
782
    kernel.
783

784
    """
785
    super(KVMHypervisor, self).ValidateParameters(hvparams)
786

    
787
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
788
    if kernel_path and not os.path.isfile(kernel_path):
789
      raise errors.HypervisorError("Instance kernel '%s' not found or"
790
                                   " not a file" % kernel_path)
791
    initrd_path = hvparams[constants.HV_INITRD_PATH]
792
    if initrd_path and not os.path.isfile(initrd_path):
793
      raise errors.HypervisorError("Instance initrd '%s' not found or"
794
                                   " not a file" % initrd_path)
795

    
796
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
797
    if vnc_bind_address and not utils.IsValidIP(vnc_bind_address) and \
798
       not os.path.isdir(vnc_bind_address):
799
       raise errors.HypervisorError("Instance vnc bind address must be either"
800
                                    " an ip address or an existing directory")
801

    
802
    vnc_x509 = hvparams[constants.HV_VNC_X509]
803
    if vnc_x509 and not os.path.isdir(vnc_x509):
804
      raise errors.HypervisorError("Instance vnc x509 path '%s' not found"
805
                                   " or not a directory" % vnc_x509)
806

    
807
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
808
    if iso_path and not os.path.isfile(iso_path):
809
      raise errors.HypervisorError("Instance cdrom image '%s' not found or"
810
                                   " not a file" % iso_path)