Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 8745c3d7

History | View | Annotate | Download (29 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
    boot_network = (instance.hvparams[constants.HV_BOOT_ORDER] == "network")
240

    
241
    if boot_network:
242
      kvm_cmd.extend(['-boot', 'n'])
243

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

    
262
      drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
263
      kvm_cmd.extend(['-drive', drive_val])
264

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

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

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

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

    
319
      else:
320
        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
321

    
322
      kvm_cmd.extend(['-vnc', vnc_arg])
323
    else:
324
      kvm_cmd.extend(['-nographic'])
325

    
326
    monitor_dev = 'unix:%s,server,nowait' % \
327
      self._InstanceMonitor(instance.name)
328
    kvm_cmd.extend(['-monitor', monitor_dev])
329
    if instance.hvparams[constants.HV_SERIAL_CONSOLE]:
330
      serial_dev = 'unix:%s,server,nowait' % self._InstanceSerial(instance.name)
331
      kvm_cmd.extend(['-serial', serial_dev])
332
    else:
333
      kvm_cmd.extend(['-serial', 'none'])
334

    
335
    # Save the current instance nics, but defer their expansion as parameters,
336
    # as we'll need to generate executable temp files for them.
337
    kvm_nics = instance.nics
338
    hvparams = instance.hvparams
339

    
340
    return (kvm_cmd, kvm_nics, hvparams)
341

    
342
  def _WriteKVMRuntime(self, instance_name, data):
343
    """Write an instance's KVM runtime
344

345
    """
346
    try:
347
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
348
                      data=data)
349
    except EnvironmentError, err:
350
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
351

    
352
  def _ReadKVMRuntime(self, instance_name):
353
    """Read an instance's KVM runtime
354

355
    """
356
    try:
357
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
358
    except EnvironmentError, err:
359
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
360
    return file_content
361

    
362
  def _SaveKVMRuntime(self, instance, kvm_runtime):
363
    """Save an instance's KVM runtime
364

365
    """
366
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
367
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
368
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
369
    self._WriteKVMRuntime(instance.name, serialized_form)
370

    
371
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
372
    """Load an instance's KVM runtime
373

374
    """
375
    if not serialized_runtime:
376
      serialized_runtime = self._ReadKVMRuntime(instance.name)
377
    loaded_runtime = serializer.Load(serialized_runtime)
378
    kvm_cmd, serialized_nics, hvparams = loaded_runtime
379
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
380
    return (kvm_cmd, kvm_nics, hvparams)
381

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

385
    @type incoming: tuple of strings
386
    @param incoming: (target_host_ip, port)
387

388
    """
389
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
390
    if alive:
391
      raise errors.HypervisorError("Failed to start instance %s: %s" %
392
                                   (instance.name, "already running"))
393

    
394
    temp_files = []
395

    
396
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
397

    
398
    if not kvm_nics:
399
      kvm_cmd.extend(['-net', 'none'])
400
    else:
401
      nic_type = hvparams[constants.HV_NIC_TYPE]
402
      if nic_type == constants.HT_NIC_PARAVIRTUAL:
403
        nic_model = "model=virtio"
404
      else:
405
        nic_model = "model=%s" % nic_type
406

    
407
      for nic_seq, nic in enumerate(kvm_nics):
408
        nic_val = "nic,macaddr=%s,%s" % (nic.mac, nic_model)
409
        script = self._WriteNetScript(instance, nic_seq, nic)
410
        kvm_cmd.extend(['-net', nic_val])
411
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
412
        temp_files.append(script)
413

    
414
    if incoming:
415
      target, port = incoming
416
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
417

    
418
    result = utils.RunCmd(kvm_cmd)
419
    if result.failed:
420
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
421
                                   (instance.name, result.fail_reason,
422
                                    result.output))
423

    
424
    if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
425
      raise errors.HypervisorError("Failed to start instance %s: %s" %
426
                                   (instance.name))
427

    
428
    for filename in temp_files:
429
      utils.RemoveFile(filename)
430

    
431
  def StartInstance(self, instance, block_devices, extra_args):
432
    """Start an instance.
433

434
    """
435
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
436
    if alive:
437
      raise errors.HypervisorError("Failed to start instance %s: %s" %
438
                                   (instance.name, "already running"))
439

    
440
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices, extra_args)
441
    self._SaveKVMRuntime(instance, kvm_runtime)
442
    self._ExecuteKVMRuntime(instance, kvm_runtime)
443

    
444
  def _CallMonitorCommand(self, instance_name, command):
445
    """Invoke a command on the instance monitor.
446

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

    
459
    return result
460

    
461
  def _RetryInstancePowerdown(self, instance, pid, timeout=30):
462
    """Wait for an instance  to power down.
463

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

    
475
  def StopInstance(self, instance, force=False):
476
    """Stop an instance.
477

478
    """
479
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
480
    if pid > 0 and alive:
481
      if force or not instance.hvparams[constants.HV_ACPI]:
482
        utils.KillProcess(pid)
483
      else:
484
        self._RetryInstancePowerdown(instance, pid)
485

    
486
    if not utils.IsProcessAlive(pid):
487
      utils.RemoveFile(pidfile)
488
      utils.RemoveFile(self._InstanceMonitor(instance.name))
489
      utils.RemoveFile(self._InstanceSerial(instance.name))
490
      utils.RemoveFile(self._InstanceKVMRuntime(instance.name))
491
      return True
492
    else:
493
      return False
494

    
495
  def RebootInstance(self, instance):
496
    """Reboot an instance.
497

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

    
516
  def MigrationInfo(self, instance):
517
    """Get instance information to perform a migration.
518

519
    @type instance: L{objects.Instance}
520
    @param instance: instance to be migrated
521
    @rtype: string
522
    @return: content of the KVM runtime file
523

524
    """
525
    return self._ReadKVMRuntime(instance.name)
526

    
527
  def AcceptInstance(self, instance, info, target):
528
    """Prepare to accept an instance.
529

530
    @type instance: L{objects.Instance}
531
    @param instance: instance to be accepted
532
    @type info: string
533
    @param info: content of the KVM runtime file on the source node
534
    @type target: string
535
    @param target: target host (usually ip), on this node
536

537
    """
538
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
539
    incoming_address = (target, constants.KVM_MIGRATION_PORT)
540
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
541

    
542
  def FinalizeMigration(self, instance, info, success):
543
    """Finalize an instance migration.
544

545
    Stop the incoming mode KVM.
546

547
    @type instance: L{objects.Instance}
548
    @param instance: instance whose migration is being aborted
549

550
    """
551
    if success:
552
      self._WriteKVMRuntime(instance.name, info)
553
    else:
554
      self.StopInstance(instance, force=True)
555

    
556
  def MigrateInstance(self, instance_name, target, live):
557
    """Migrate an instance to a target node.
558

559
    The migration will not be attempted if the instance is not
560
    currently running.
561

562
    @type instance_name: string
563
    @param instance_name: name of the instance to be migrated
564
    @type target: string
565
    @param target: ip address of the target node
566
    @type live: boolean
567
    @param live: perform a live migration
568

569
    """
570
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
571
    if not alive:
572
      raise errors.HypervisorError("Instance not running, cannot migrate")
573

    
574
    if not live:
575
      self._CallMonitorCommand(instance_name, 'stop')
576

    
577
    migrate_command = ('migrate -d tcp:%s:%s' %
578
                       (target, constants.KVM_MIGRATION_PORT))
579
    self._CallMonitorCommand(instance_name, migrate_command)
580

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

    
604
    utils.KillProcess(pid)
605
    utils.RemoveFile(pidfile)
606
    utils.RemoveFile(self._InstanceMonitor(instance_name))
607
    utils.RemoveFile(self._InstanceSerial(instance_name))
608
    utils.RemoveFile(self._InstanceKVMRuntime(instance_name))
609

    
610
  def GetNodeInfo(self):
611
    """Return information about the node.
612

613
    @return: a dict with the following keys (values in MiB):
614
          - memory_total: the total memory size on the node
615
          - memory_free: the available memory on the node for instances
616
          - memory_dom0: the memory used by the node itself, if available
617

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

    
632
    result = {}
633
    sum_free = 0
634
    for line in data:
635
      splitfields = line.split(":", 1)
636

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

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

    
663
    return result
664

    
665
  @classmethod
666
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
667
    """Return a command for connecting to the console of an instance.
668

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

    
684
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
685
    if vnc_bind_address:
686
      if instance.network_port > constants.VNC_BASE_PORT:
687
        display = instance.network_port - constants.VNC_BASE_PORT
688
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
689
                       " (display: %d)'" % (vnc_bind_address,
690
                                            instance.network_port,
691
                                            display))
692
        shell_command = "%s; %s" % (vnc_command, shell_command)
693

    
694
    return shell_command
695

    
696
  def Verify(self):
697
    """Verify the hypervisor.
698

699
    Check that the binary exists.
700

701
    """
702
    if not os.path.exists(constants.KVM_PATH):
703
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
704
    if not os.path.exists(constants.SOCAT_PATH):
705
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
706

    
707

    
708
  @classmethod
709
  def CheckParameterSyntax(cls, hvparams):
710
    """Check the given parameters for validity.
711

712
    For the KVM hypervisor, this only check the existence of the
713
    kernel.
714

715
    @type hvparams:  dict
716
    @param hvparams: dictionary with parameter names/value
717
    @raise errors.HypervisorError: when a parameter is not valid
718

719
    """
720
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
721

    
722
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
723
    if kernel_path:
724
      if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
725
        raise errors.HypervisorError("The kernel path must be an absolute path"
726
                                     ", if defined")
727

    
728
      if not hvparams[constants.HV_ROOT_PATH]:
729
        raise errors.HypervisorError("Need a root partition for the instance"
730
                                     ", if a kernel is defined")
731

    
732
    if hvparams[constants.HV_INITRD_PATH]:
733
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
734
        raise errors.HypervisorError("The initrd path must an absolute path"
735
                                     ", if defined")
736

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

    
746
    if hvparams[constants.HV_VNC_X509_VERIFY] and \
747
      not hvparams[constants.HV_VNC_X509]:
748
        raise errors.HypervisorError("%s must be defined, if %s is" %
749
                                     (constants.HV_VNC_X509,
750
                                      constants.HV_VNC_X509_VERIFY))
751

    
752
    if hvparams[constants.HV_VNC_X509]:
753
      if not os.path.isabs(hvparams[constants.HV_VNC_X509]):
754
        raise errors.HypervisorError("The vnc x509 path must an absolute path"
755
                                     ", if defined")
756

    
757
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
758
    if iso_path and not os.path.isabs(iso_path):
759
      raise errors.HypervisorError("The path to the CDROM image must be"
760
                                   " an absolute path, if defined")
761

    
762
    boot_order = hvparams[constants.HV_BOOT_ORDER]
763
    if boot_order not in ('cdrom', 'disk', 'network'):
764
      raise errors.HypervisorError("The boot order must be 'cdrom', 'disk' or"
765
                                   " 'network'")
766

    
767
    if boot_order == 'cdrom' and not iso_path:
768
      raise errors.HypervisorError("Cannot boot from cdrom without an ISO path")
769

    
770
    nic_type = hvparams[constants.HV_NIC_TYPE]
771
    if nic_type not in constants.HT_KVM_VALID_NIC_TYPES:
772
      raise errors.HypervisorError("Invalid NIC type %s specified for the KVM"
773
                                   " hypervisor. Please choose one of: %s" %
774
                                   (nic_type,
775
                                    constants.HT_KVM_VALID_NIC_TYPES))
776
    elif boot_order == 'network' and nic_type == constants.HT_NIC_PARAVIRTUAL:
777
      raise errors.HypervisorError("Cannot boot from a paravirtual NIC. Please"
778
                                   " change the nic type.")
779

    
780
    disk_type = hvparams[constants.HV_DISK_TYPE]
781
    if disk_type not in constants.HT_KVM_VALID_DISK_TYPES:
782
      raise errors.HypervisorError("Invalid disk type %s specified for the KVM"
783
                                   " hypervisor. Please choose one of: %s" %
784
                                   (disk_type,
785
                                    constants.HT_KVM_VALID_DISK_TYPES))
786

    
787
  def ValidateParameters(self, hvparams):
788
    """Check the given parameters for validity.
789

790
    For the KVM hypervisor, this checks the existence of the
791
    kernel.
792

793
    """
794
    super(KVMHypervisor, self).ValidateParameters(hvparams)
795

    
796
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
797
    if kernel_path and not os.path.isfile(kernel_path):
798
      raise errors.HypervisorError("Instance kernel '%s' not found or"
799
                                   " not a file" % kernel_path)
800
    initrd_path = hvparams[constants.HV_INITRD_PATH]
801
    if initrd_path and not os.path.isfile(initrd_path):
802
      raise errors.HypervisorError("Instance initrd '%s' not found or"
803
                                   " not a file" % initrd_path)
804

    
805
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
806
    if vnc_bind_address and not utils.IsValidIP(vnc_bind_address) and \
807
       not os.path.isdir(vnc_bind_address):
808
       raise errors.HypervisorError("Instance vnc bind address must be either"
809
                                    " an ip address or an existing directory")
810

    
811
    vnc_x509 = hvparams[constants.HV_VNC_X509]
812
    if vnc_x509 and not os.path.isdir(vnc_x509):
813
      raise errors.HypervisorError("Instance vnc x509 path '%s' not found"
814
                                   " or not a directory" % vnc_x509)
815

    
816
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
817
    if iso_path and not os.path.isfile(iso_path):
818
      raise errors.HypervisorError("Instance cdrom image '%s' not found or"
819
                                   " not a file" % iso_path)