Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 8447f52b

History | View | Annotate | Download (24.3 kB)

1
#
2
#
3

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

    
21

    
22
"""KVM hypervisor
23

24
"""
25

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

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

    
41

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

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

    
51
  PARAMETERS = [
52
    constants.HV_KERNEL_PATH,
53
    constants.HV_INITRD_PATH,
54
    constants.HV_ROOT_PATH,
55
    constants.HV_ACPI,
56
    constants.HV_SERIAL_CONSOLE,
57
    constants.HV_VNC_BIND_ADDRESS,
58
    ]
59

    
60
  _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)',
61
                                    re.M | re.I)
62

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

    
71
  def _InstancePidAlive(self, instance_name):
72
    """Returns the instance pid and pidfile
73

74
    """
75
    pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
76
    pid = utils.ReadPidFile(pidfile)
77
    alive = utils.IsProcessAlive(pid)
78

    
79
    return (pidfile, pid, alive)
80

    
81
  @classmethod
82
  def _InstanceMonitor(cls, instance_name):
83
    """Returns the instance monitor socket name
84

85
    """
86
    return '%s/%s.monitor' % (cls._CTRL_DIR, instance_name)
87

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

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

    
95
  @classmethod
96
  def _InstanceKVMRuntime(cls, instance_name):
97
    """Returns the instance KVM runtime filename
98

99
    """
100
    return '%s/%s.runtime' % (cls._CONF_DIR, instance_name)
101

    
102
  def _WriteNetScript(self, instance, seq, nic):
103
    """Write a script to connect a net interface to the proper bridge.
104

105
    This can be used by any qemu-type hypervisor.
106

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

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

    
143
  def ListInstances(self):
144
    """Get the list of running instances.
145

146
    We can do this by listing our live instances directory and
147
    checking whether the associated kvm process is still alive.
148

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

    
157
  def GetInstanceInfo(self, instance_name):
158
    """Get instance properties.
159

160
    @param instance_name: the instance name
161

162
    @return: tuple (name, id, memory, vcpus, stat, times)
163

164
    """
165
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
166
    if not alive:
167
      return None
168

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

    
180
    memory = 0
181
    vcpus = 0
182
    stat = "---b-"
183
    times = "0"
184

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

    
193
    return (instance_name, pid, memory, vcpus, stat, times)
194

    
195
  def GetAllInstancesInfo(self):
196
    """Get properties of all instances.
197

198
    @return: list of tuples (name, id, memory, vcpus, stat, times)
199

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

    
212
    return data
213

    
214
  def _GenerateKVMRuntime(self, instance, block_devices, extra_args):
215
    """Generate KVM information to start an instance.
216

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

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

    
242
      # TODO: handle different if= types
243
      if_val = ',if=virtio'
244

    
245
      drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
246
      kvm_cmd.extend(['-drive', drive_val])
247

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

    
260
    #"hvm_boot_order",
261
    #"hvm_cdrom_image_path",
262

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

    
289
    monitor_dev = 'unix:%s,server,nowait' % \
290
      self._InstanceMonitor(instance.name)
291
    kvm_cmd.extend(['-monitor', monitor_dev])
292
    if instance.hvparams[constants.HV_SERIAL_CONSOLE]:
293
      serial_dev = 'unix:%s,server,nowait' % self._InstanceSerial(instance.name)
294
      kvm_cmd.extend(['-serial', serial_dev])
295
    else:
296
      kvm_cmd.extend(['-serial', 'none'])
297

    
298
    # Save the current instance nics, but defer their expansion as parameters,
299
    # as we'll need to generate executable temp files for them.
300
    kvm_nics = instance.nics
301

    
302
    return (kvm_cmd, kvm_nics)
303

    
304
  def _WriteKVMRuntime(self, instance_name, data):
305
    """Write an instance's KVM runtime
306

307
    """
308
    try:
309
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
310
                      data=data)
311
    except EnvironmentError, err:
312
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
313

    
314
  def _ReadKVMRuntime(self, instance_name):
315
    """Read an instance's KVM runtime
316

317
    """
318
    try:
319
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
320
    except EnvironmentError, err:
321
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
322
    return file_content
323

    
324
  def _SaveKVMRuntime(self, instance, kvm_runtime):
325
    """Save an instance's KVM runtime
326

327
    """
328
    kvm_cmd, kvm_nics = kvm_runtime
329
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
330
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics))
331
    self._WriteKVMRuntime(instance.name, serialized_form)
332

    
333
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
334
    """Load an instance's KVM runtime
335

336
    """
337
    if not serialized_runtime:
338
      serialized_runtime = self._ReadKVMRuntime(instance.name)
339
    loaded_runtime = serializer.Load(serialized_runtime)
340
    kvm_cmd, serialized_nics = loaded_runtime
341
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
342
    return (kvm_cmd, kvm_nics)
343

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

347
    @type incoming: tuple of strings
348
    @param incoming: (target_host_ip, port)
349

350
    """
351
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
352
    if alive:
353
      raise errors.HypervisorError("Failed to start instance %s: %s" %
354
                                   (instance.name, "already running"))
355

    
356
    temp_files = []
357

    
358
    kvm_cmd, kvm_nics = kvm_runtime
359

    
360
    if not kvm_nics:
361
      kvm_cmd.extend(['-net', 'none'])
362
    else:
363
      for nic_seq, nic in enumerate(kvm_nics):
364
        nic_val = "nic,macaddr=%s,model=virtio" % nic.mac
365
        script = self._WriteNetScript(instance, nic_seq, nic)
366
        kvm_cmd.extend(['-net', nic_val])
367
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
368
        temp_files.append(script)
369

    
370
    if incoming:
371
      target, port = incoming
372
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
373

    
374
    result = utils.RunCmd(kvm_cmd)
375
    if result.failed:
376
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
377
                                   (instance.name, result.fail_reason,
378
                                    result.output))
379

    
380
    if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
381
      raise errors.HypervisorError("Failed to start instance %s: %s" %
382
                                   (instance.name))
383

    
384
    for filename in temp_files:
385
      utils.RemoveFile(filename)
386

    
387
  def StartInstance(self, instance, block_devices, extra_args):
388
    """Start an instance.
389

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

    
396
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices, extra_args)
397
    self._SaveKVMRuntime(instance, kvm_runtime)
398
    self._ExecuteKVMRuntime(instance, kvm_runtime)
399

    
400
  def _CallMonitorCommand(self, instance_name, command):
401
    """Invoke a command on the instance monitor.
402

403
    """
404
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
405
             (utils.ShellQuote(command),
406
              constants.SOCAT_PATH,
407
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
408
    result = utils.RunCmd(socat)
409
    if result.failed:
410
      msg = ("Failed to send command '%s' to instance %s."
411
             " output: %s, error: %s, fail_reason: %s" %
412
             (instance.name, result.stdout, result.stderr, result.fail_reason))
413
      raise errors.HypervisorError(msg)
414

    
415
    return result
416

    
417
  def _RetryInstancePowerdown(self, instance, pid, timeout=30):
418
    """Wait for an instance  to power down.
419

420
    """
421
    # Wait up to $timeout seconds
422
    end = time.time() + timeout
423
    wait = 1
424
    while time.time() < end and utils.IsProcessAlive(pid):
425
      self._CallMonitorCommand(instance.name, 'system_powerdown')
426
      time.sleep(wait)
427
      # Make wait time longer for next try
428
      if wait < 5:
429
        wait *= 1.3
430

    
431
  def StopInstance(self, instance, force=False):
432
    """Stop an instance.
433

434
    """
435
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
436
    if pid > 0 and alive:
437
      if force or not instance.hvparams[constants.HV_ACPI]:
438
        utils.KillProcess(pid)
439
      else:
440
        self._RetryInstancePowerdown(instance, pid)
441

    
442
    if not utils.IsProcessAlive(pid):
443
      utils.RemoveFile(pidfile)
444
      utils.RemoveFile(self._InstanceMonitor(instance.name))
445
      utils.RemoveFile(self._InstanceSerial(instance.name))
446
      utils.RemoveFile(self._InstanceKVMRuntime(instance.name))
447
      return True
448
    else:
449
      return False
450

    
451
  def RebootInstance(self, instance):
452
    """Reboot an instance.
453

454
    """
455
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
456
    # socket the instance will stop, but now power up again. So we'll resort
457
    # to shutdown and restart.
458
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
459
    if not alive:
460
      raise errors.HypervisorError("Failed to reboot instance %s: not running" %
461
                                             (instance.name))
462
    # StopInstance will delete the saved KVM runtime so:
463
    # ...first load it...
464
    kvm_runtime = self._LoadKVMRuntime(instance)
465
    # ...now we can safely call StopInstance...
466
    if not self.StopInstance(instance):
467
      self.StopInstance(instance, force=True)
468
    # ...and finally we can save it again, and execute it...
469
    self._SaveKVMRuntime(instance, kvm_runtime)
470
    self._ExecuteKVMRuntime(instance, kvm_runtime)
471

    
472
  def MigrationInfo(self, instance):
473
    """Get instance information to perform a migration.
474

475
    @type instance: L{objects.Instance}
476
    @param instance: instance to be migrated
477
    @rtype: string
478
    @return: content of the KVM runtime file
479

480
    """
481
    return self._ReadKVMRuntime(instance.name)
482

    
483
  def AcceptInstance(self, instance, info, target):
484
    """Prepare to accept an instance.
485

486
    @type instance: L{objects.Instance}
487
    @param instance: instance to be accepted
488
    @type info: string
489
    @param info: content of the KVM runtime file on the source node
490
    @type target: string
491
    @param target: target host (usually ip), on this node
492

493
    """
494
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
495
    incoming_address = (target, constants.KVM_MIGRATION_PORT)
496
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
497

    
498
  def FinalizeMigration(self, instance, info, success):
499
    """Finalize an instance migration.
500

501
    Stop the incoming mode KVM.
502

503
    @type instance: L{objects.Instance}
504
    @param instance: instance whose migration is being aborted
505

506
    """
507
    if success:
508
      self._WriteKVMRuntime(instance.name, info)
509
    else:
510
      self.StopInstance(instance, force=True)
511

    
512
  def MigrateInstance(self, instance_name, target, live):
513
    """Migrate an instance to a target node.
514

515
    The migration will not be attempted if the instance is not
516
    currently running.
517

518
    @type instance_name: string
519
    @param instance_name: name of the instance to be migrated
520
    @type target: string
521
    @param target: ip address of the target node
522
    @type live: boolean
523
    @param live: perform a live migration
524

525
    """
526
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
527
    if not alive:
528
      raise errors.HypervisorError("Instance not running, cannot migrate")
529

    
530
    if not live:
531
      self._CallMonitorCommand(instance_name, 'stop')
532

    
533
    migrate_command = ('migrate -d tcp:%s:%s' %
534
                       (target, constants.KVM_MIGRATION_PORT))
535
    self._CallMonitorCommand(instance_name, migrate_command)
536

    
537
    info_command = 'info migrate'
538
    done = False
539
    while not done:
540
      result = self._CallMonitorCommand(instance_name, info_command)
541
      match = self._MIGRATION_STATUS_RE.search(result.stdout)
542
      if not match:
543
        raise errors.HypervisorError("Unknown 'info migrate' result: %s" %
544
                                     result.stdout)
545
      else:
546
        status = match.group(1)
547
        if status == 'completed':
548
          done = True
549
        elif status == 'active':
550
          time.sleep(2)
551
        elif status == 'failed' or status == 'cancelled':
552
          if not live:
553
            self._CallMonitorCommand(instance_name, 'cont')
554
          raise errors.HypervisorError("Migration %s at the kvm level" %
555
                                       status)
556
        else:
557
          logging.info("KVM: unknown migration status '%s'" % status)
558
          time.sleep(2)
559

    
560
    utils.KillProcess(pid)
561
    utils.RemoveFile(pidfile)
562
    utils.RemoveFile(self._InstanceMonitor(instance_name))
563
    utils.RemoveFile(self._InstanceSerial(instance_name))
564
    utils.RemoveFile(self._InstanceKVMRuntime(instance_name))
565

    
566
  def GetNodeInfo(self):
567
    """Return information about the node.
568

569
    @return: a dict with the following keys (values in MiB):
570
          - memory_total: the total memory size on the node
571
          - memory_free: the available memory on the node for instances
572
          - memory_dom0: the memory used by the node itself, if available
573

574
    """
575
    # global ram usage from the xm info command
576
    # memory                 : 3583
577
    # free_memory            : 747
578
    # note: in xen 3, memory has changed to total_memory
579
    try:
580
      fh = file("/proc/meminfo")
581
      try:
582
        data = fh.readlines()
583
      finally:
584
        fh.close()
585
    except EnvironmentError, err:
586
      raise errors.HypervisorError("Failed to list node info: %s" % err)
587

    
588
    result = {}
589
    sum_free = 0
590
    for line in data:
591
      splitfields = line.split(":", 1)
592

    
593
      if len(splitfields) > 1:
594
        key = splitfields[0].strip()
595
        val = splitfields[1].strip()
596
        if key == 'MemTotal':
597
          result['memory_total'] = int(val.split()[0])/1024
598
        elif key in ('MemFree', 'Buffers', 'Cached'):
599
          sum_free += int(val.split()[0])/1024
600
        elif key == 'Active':
601
          result['memory_dom0'] = int(val.split()[0])/1024
602
    result['memory_free'] = sum_free
603

    
604
    cpu_total = 0
605
    try:
606
      fh = open("/proc/cpuinfo")
607
      try:
608
        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
609
                                   fh.read()))
610
      finally:
611
        fh.close()
612
    except EnvironmentError, err:
613
      raise errors.HypervisorError("Failed to list node info: %s" % err)
614
    result['cpu_total'] = cpu_total
615

    
616
    return result
617

    
618
  @classmethod
619
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
620
    """Return a command for connecting to the console of an instance.
621

622
    """
623
    if hvparams[constants.HV_SERIAL_CONSOLE]:
624
      # FIXME: The socat shell is not perfect. In particular the way we start
625
      # it ctrl+c will close it, rather than being passed to the other end.
626
      # On the other hand if we pass the option 'raw' (or ignbrk=1) there
627
      # will be no way of exiting socat (except killing it from another shell)
628
      # and ctrl+c doesn't work anyway, printing ^C rather than being
629
      # interpreted by kvm. For now we'll leave it this way, which at least
630
      # allows a minimal interaction and changes on the machine.
631
      shell_command = ("%s STDIO,echo=0,icanon=0 UNIX-CONNECT:%s" %
632
                       (constants.SOCAT_PATH,
633
                        utils.ShellQuote(cls._InstanceSerial(instance.name))))
634
    else:
635
      shell_command = "echo 'No serial shell for instance %s'" % instance.name
636

    
637
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
638
    if vnc_bind_address:
639
      if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
640
        display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
641
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
642
                       " (display: %d)'" % (vnc_bind_address,
643
                                            instance.network_port,
644
                                            display))
645
        shell_command = "%s; %s" % (vnc_command, shell_command)
646

    
647
    return shell_command
648

    
649
  def Verify(self):
650
    """Verify the hypervisor.
651

652
    Check that the binary exists.
653

654
    """
655
    if not os.path.exists(constants.KVM_PATH):
656
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
657
    if not os.path.exists(constants.SOCAT_PATH):
658
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
659

    
660

    
661
  @classmethod
662
  def CheckParameterSyntax(cls, hvparams):
663
    """Check the given parameters for validity.
664

665
    For the KVM hypervisor, this only check the existence of the
666
    kernel.
667

668
    @type hvparams:  dict
669
    @param hvparams: dictionary with parameter names/value
670
    @raise errors.HypervisorError: when a parameter is not valid
671

672
    """
673
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
674

    
675
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
676
    if kernel_path:
677
      if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
678
        raise errors.HypervisorError("The kernel path must be an absolute path"
679
                                     ", if defined")
680

    
681
      if not hvparams[constants.HV_ROOT_PATH]:
682
        raise errors.HypervisorError("Need a root partition for the instance"
683
                                     ", if a kernel is defined")
684

    
685
    if hvparams[constants.HV_INITRD_PATH]:
686
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
687
        raise errors.HypervisorError("The initrd path must an absolute path"
688
                                     ", if defined")
689

    
690
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
691
    if vnc_bind_address:
692
      if not utils.IsValidIP(vnc_bind_address):
693
        if not os.path.isabs(vnc_bind_address):
694
          raise errors.HypervisorError("The VNC bind address must be either"
695
                                       " a valid IP address or an absolute"
696
                                       " pathname. '%s' given" %
697
                                       vnc_bind_address)
698

    
699
  def ValidateParameters(self, hvparams):
700
    """Check the given parameters for validity.
701

702
    For the KVM hypervisor, this checks the existence of the
703
    kernel.
704

705
    """
706
    super(KVMHypervisor, self).ValidateParameters(hvparams)
707

    
708
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
709
    if kernel_path and not os.path.isfile(kernel_path):
710
      raise errors.HypervisorError("Instance kernel '%s' not found or"
711
                                   " not a file" % kernel_path)
712
    initrd_path = hvparams[constants.HV_INITRD_PATH]
713
    if initrd_path and not os.path.isfile(initrd_path):
714
      raise errors.HypervisorError("Instance initrd '%s' not found or"
715
                                   " not a file" % initrd_path)