Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 3be34f57

History | View | Annotate | Download (23.8 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
        data.append(self.GetInstanceInfo(name))
206

    
207
    return data
208

    
209
  def _GenerateKVMRuntime(self, instance, block_devices, extra_args):
210
    """Generate KVM information to start an instance.
211

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

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

    
237
      # TODO: handle different if= types
238
      if_val = ',if=virtio'
239

    
240
      drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
241
      kvm_cmd.extend(['-drive', drive_val])
242

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

    
255
    #"hvm_boot_order",
256
    #"hvm_cdrom_image_path",
257

    
258
    # FIXME: handle vnc password
259
    vnc_bind_address = instance.hvparams[constants.HV_VNC_BIND_ADDRESS]
260
    if vnc_bind_address:
261
      kvm_cmd.extend(['-usbdevice', 'tablet'])
262
      if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
263
        display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
264
        if vnc_bind_address == '0.0.0.0':
265
          vnc_arg = ':%d' % (display)
266
        else:
267
          vnc_arg = '%s:%d' % (constants.HV_VNC_BIND_ADDRESS, display)
268
        kvm_cmd.extend(['-vnc', vnc_arg])
269
      else:
270
        logging.error("Network port is not a valid VNC display (%d < %d)."
271
                      " Not starting VNC" %
272
                      (instance.network_port, constants.HT_HVM_VNC_BASE_PORT))
273
        kvm_cmd.extend(['-vnc', 'none'])
274
    else:
275
      kvm_cmd.extend(['-nographic'])
276

    
277
    monitor_dev = 'unix:%s,server,nowait' % \
278
      self._InstanceMonitor(instance.name)
279
    kvm_cmd.extend(['-monitor', monitor_dev])
280
    if instance.hvparams[constants.HV_SERIAL_CONSOLE]:
281
      serial_dev = 'unix:%s,server,nowait' % self._InstanceSerial(instance.name)
282
      kvm_cmd.extend(['-serial', serial_dev])
283
    else:
284
      kvm_cmd.extend(['-serial', 'none'])
285

    
286
    # Save the current instance nics, but defer their expansion as parameters,
287
    # as we'll need to generate executable temp files for them.
288
    kvm_nics = instance.nics
289

    
290
    return (kvm_cmd, kvm_nics)
291

    
292
  def _WriteKVMRuntime(self, instance_name, data):
293
    """Write an instance's KVM runtime
294

295
    """
296
    try:
297
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
298
                      data=data)
299
    except EnvironmentError, err:
300
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
301

    
302
  def _ReadKVMRuntime(self, instance_name):
303
    """Read an instance's KVM runtime
304

305
    """
306
    try:
307
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
308
    except EnvironmentError, err:
309
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
310
    return file_content
311

    
312
  def _SaveKVMRuntime(self, instance, kvm_runtime):
313
    """Save an instance's KVM runtime
314

315
    """
316
    kvm_cmd, kvm_nics = kvm_runtime
317
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
318
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics))
319
    self._WriteKVMRuntime(instance.name, serialized_form)
320

    
321
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
322
    """Load an instance's KVM runtime
323

324
    """
325
    if not serialized_runtime:
326
      serialized_runtime = self._ReadKVMRuntime(instance.name)
327
    loaded_runtime = serializer.Load(serialized_runtime)
328
    kvm_cmd, serialized_nics = loaded_runtime
329
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
330
    return (kvm_cmd, kvm_nics)
331

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

335
    @type incoming: tuple of strings
336
    @param incoming: (target_host_ip, port)
337

338
    """
339
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
340
    if alive:
341
      raise errors.HypervisorError("Failed to start instance %s: %s" %
342
                                   (instance.name, "already running"))
343

    
344
    temp_files = []
345

    
346
    kvm_cmd, kvm_nics = kvm_runtime
347

    
348
    if not kvm_nics:
349
      kvm_cmd.extend(['-net', 'none'])
350
    else:
351
      for nic_seq, nic in enumerate(kvm_nics):
352
        nic_val = "nic,macaddr=%s,model=virtio" % nic.mac
353
        script = self._WriteNetScript(instance, nic_seq, nic)
354
        kvm_cmd.extend(['-net', nic_val])
355
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
356
        temp_files.append(script)
357

    
358
    if incoming:
359
      target, port = incoming
360
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
361

    
362
    result = utils.RunCmd(kvm_cmd)
363
    if result.failed:
364
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
365
                                   (instance.name, result.fail_reason,
366
                                    result.output))
367

    
368
    if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
369
      raise errors.HypervisorError("Failed to start instance %s: %s" %
370
                                   (instance.name))
371

    
372
    for filename in temp_files:
373
      utils.RemoveFile(filename)
374

    
375
  def StartInstance(self, instance, block_devices, extra_args):
376
    """Start an instance.
377

378
    """
379
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
380
    if alive:
381
      raise errors.HypervisorError("Failed to start instance %s: %s" %
382
                                   (instance.name, "already running"))
383

    
384
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices, extra_args)
385
    self._SaveKVMRuntime(instance, kvm_runtime)
386
    self._ExecuteKVMRuntime(instance, kvm_runtime)
387

    
388
  def _CallMonitorCommand(self, instance_name, command):
389
    """Invoke a command on the instance monitor.
390

391
    """
392
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
393
             (utils.ShellQuote(command),
394
              constants.SOCAT_PATH,
395
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
396
    result = utils.RunCmd(socat)
397
    if result.failed:
398
      msg = ("Failed to send command '%s' to instance %s."
399
             " output: %s, error: %s, fail_reason: %s" %
400
             (instance.name, result.stdout, result.stderr, result.fail_reason))
401
      raise errors.HypervisorError(msg)
402

    
403
    return result
404

    
405
  def _RetryInstancePowerdown(self, instance, pid, timeout=30):
406
    """Wait for an instance  to power down.
407

408
    """
409
    # Wait up to $timeout seconds
410
    end = time.time() + timeout
411
    wait = 1
412
    while time.time() < end and utils.IsProcessAlive(pid):
413
      self._CallMonitorCommand(instance.name, 'system_powerdown')
414
      time.sleep(wait)
415
      # Make wait time longer for next try
416
      if wait < 5:
417
        wait *= 1.3
418

    
419
  def StopInstance(self, instance, force=False):
420
    """Stop an instance.
421

422
    """
423
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
424
    if pid > 0 and alive:
425
      if force or not instance.hvparams[constants.HV_ACPI]:
426
        utils.KillProcess(pid)
427
      else:
428
        self._RetryInstancePowerdown(instance, pid)
429

    
430
    if not utils.IsProcessAlive(pid):
431
      utils.RemoveFile(pidfile)
432
      utils.RemoveFile(self._InstanceMonitor(instance.name))
433
      utils.RemoveFile(self._InstanceSerial(instance.name))
434
      utils.RemoveFile(self._InstanceKVMRuntime(instance.name))
435
      return True
436
    else:
437
      return False
438

    
439
  def RebootInstance(self, instance):
440
    """Reboot an instance.
441

442
    """
443
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
444
    # socket the instance will stop, but now power up again. So we'll resort
445
    # to shutdown and restart.
446
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
447
    if not alive:
448
      raise errors.HypervisorError("Failed to reboot instance %s: not running" %
449
                                             (instance.name))
450
    # StopInstance will delete the saved KVM runtime so:
451
    # ...first load it...
452
    kvm_runtime = self._LoadKVMRuntime(instance)
453
    # ...now we can safely call StopInstance...
454
    if not self.StopInstance(instance):
455
      self.StopInstance(instance, force=True)
456
    # ...and finally we can save it again, and execute it...
457
    self._SaveKVMRuntime(instance, kvm_runtime)
458
    self._ExecuteKVMRuntime(instance, kvm_runtime)
459

    
460
  def MigrationInfo(self, instance):
461
    """Get instance information to perform a migration.
462

463
    @type instance: L{objects.Instance}
464
    @param instance: instance to be migrated
465
    @rtype: string
466
    @return: content of the KVM runtime file
467

468
    """
469
    return self._ReadKVMRuntime(instance.name)
470

    
471
  def AcceptInstance(self, instance, info, target):
472
    """Prepare to accept an instance.
473

474
    @type instance: L{objects.Instance}
475
    @param instance: instance to be accepted
476
    @type info: string
477
    @param info: content of the KVM runtime file on the source node
478
    @type target: string
479
    @param target: target host (usually ip), on this node
480

481
    """
482
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
483
    incoming_address = (target, constants.KVM_MIGRATION_PORT)
484
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
485

    
486
  def FinalizeMigration(self, instance, info, success):
487
    """Finalize an instance migration.
488

489
    Stop the incoming mode KVM.
490

491
    @type instance: L{objects.Instance}
492
    @param instance: instance whose migration is being aborted
493

494
    """
495
    if success:
496
      self._WriteKVMRuntime(instance.name, info)
497
    else:
498
      self.StopInstance(instance, force=True)
499

    
500
  def MigrateInstance(self, instance_name, target, live):
501
    """Migrate an instance to a target node.
502

503
    The migration will not be attempted if the instance is not
504
    currently running.
505

506
    @type instance_name: string
507
    @param instance_name: name of the instance to be migrated
508
    @type target: string
509
    @param target: ip address of the target node
510
    @type live: boolean
511
    @param live: perform a live migration
512

513
    """
514
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
515
    if not alive:
516
      raise errors.HypervisorError("Instance not running, cannot migrate")
517

    
518
    if not live:
519
      self._CallMonitorCommand(instance_name, 'stop')
520

    
521
    migrate_command = ('migrate -d tcp:%s:%s' %
522
                       (target, constants.KVM_MIGRATION_PORT))
523
    self._CallMonitorCommand(instance_name, migrate_command)
524

    
525
    info_command = 'info migrate'
526
    done = False
527
    while not done:
528
      result = self._CallMonitorCommand(instance_name, info_command)
529
      match = self._MIGRATION_STATUS_RE.search(result.stdout)
530
      if not match:
531
        raise errors.HypervisorError("Unknown 'info migrate' result: %s" %
532
                                     result.stdout)
533
      else:
534
        status = match.group(1)
535
        if status == 'completed':
536
          done = True
537
        elif status == 'active':
538
          time.sleep(2)
539
        elif status == 'failed' or status == 'cancelled':
540
          if not live:
541
            self._CallMonitorCommand(instance_name, 'cont')
542
          raise errors.HypervisorError("Migration %s at the kvm level" %
543
                                       status)
544
        else:
545
          logging.info("KVM: unknown migration status '%s'" % status)
546
          time.sleep(2)
547

    
548
    utils.KillProcess(pid)
549
    utils.RemoveFile(pidfile)
550
    utils.RemoveFile(self._InstanceMonitor(instance_name))
551
    utils.RemoveFile(self._InstanceSerial(instance_name))
552
    utils.RemoveFile(self._InstanceKVMRuntime(instance_name))
553

    
554
  def GetNodeInfo(self):
555
    """Return information about the node.
556

557
    @return: a dict with the following keys (values in MiB):
558
          - memory_total: the total memory size on the node
559
          - memory_free: the available memory on the node for instances
560
          - memory_dom0: the memory used by the node itself, if available
561

562
    """
563
    # global ram usage from the xm info command
564
    # memory                 : 3583
565
    # free_memory            : 747
566
    # note: in xen 3, memory has changed to total_memory
567
    try:
568
      fh = file("/proc/meminfo")
569
      try:
570
        data = fh.readlines()
571
      finally:
572
        fh.close()
573
    except EnvironmentError, err:
574
      raise errors.HypervisorError("Failed to list node info: %s" % err)
575

    
576
    result = {}
577
    sum_free = 0
578
    for line in data:
579
      splitfields = line.split(":", 1)
580

    
581
      if len(splitfields) > 1:
582
        key = splitfields[0].strip()
583
        val = splitfields[1].strip()
584
        if key == 'MemTotal':
585
          result['memory_total'] = int(val.split()[0])/1024
586
        elif key in ('MemFree', 'Buffers', 'Cached'):
587
          sum_free += int(val.split()[0])/1024
588
        elif key == 'Active':
589
          result['memory_dom0'] = int(val.split()[0])/1024
590
    result['memory_free'] = sum_free
591

    
592
    cpu_total = 0
593
    try:
594
      fh = open("/proc/cpuinfo")
595
      try:
596
        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
597
                                   fh.read()))
598
      finally:
599
        fh.close()
600
    except EnvironmentError, err:
601
      raise errors.HypervisorError("Failed to list node info: %s" % err)
602
    result['cpu_total'] = cpu_total
603

    
604
    return result
605

    
606
  @classmethod
607
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
608
    """Return a command for connecting to the console of an instance.
609

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

    
625
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
626
    if vnc_bind_address:
627
      if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
628
        display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
629
        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
630
                       " (display: %d)'" % (vnc_bind_address,
631
                                            instance.network_port,
632
                                            display))
633
        shell_command = "%s; %s" % (vnc_command, shell_command)
634

    
635
    return shell_command
636

    
637
  def Verify(self):
638
    """Verify the hypervisor.
639

640
    Check that the binary exists.
641

642
    """
643
    if not os.path.exists(constants.KVM_PATH):
644
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
645
    if not os.path.exists(constants.SOCAT_PATH):
646
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
647

    
648

    
649
  @classmethod
650
  def CheckParameterSyntax(cls, hvparams):
651
    """Check the given parameters for validity.
652

653
    For the KVM hypervisor, this only check the existence of the
654
    kernel.
655

656
    @type hvparams:  dict
657
    @param hvparams: dictionary with parameter names/value
658
    @raise errors.HypervisorError: when a parameter is not valid
659

660
    """
661
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
662

    
663
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
664
    if kernel_path:
665
      if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
666
        raise errors.HypervisorError("The kernel path must be an absolute path"
667
                                     ", if defined")
668

    
669
      if not hvparams[constants.HV_ROOT_PATH]:
670
        raise errors.HypervisorError("Need a root partition for the instance"
671
                                     ", if a kernel is defined")
672

    
673
    if hvparams[constants.HV_INITRD_PATH]:
674
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
675
        raise errors.HypervisorError("The initrd path must an absolute path"
676
                                     ", if defined")
677

    
678
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
679
    if vnc_bind_address:
680
      if not utils.IsValidIP(vnc_bind_address):
681
        raise errors.OpPrereqError("given VNC bind address '%s' doesn't look"
682
                                   " like a valid IP address" %
683
                                   vnc_bind_address)
684

    
685
  def ValidateParameters(self, hvparams):
686
    """Check the given parameters for validity.
687

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

691
    """
692
    super(KVMHypervisor, self).ValidateParameters(hvparams)
693

    
694
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
695
    if kernel_path and not os.path.isfile(kernel_path):
696
      raise errors.HypervisorError("Instance kernel '%s' not found or"
697
                                   " not a file" % kernel_path)
698
    initrd_path = hvparams[constants.HV_INITRD_PATH]
699
    if initrd_path and not os.path.isfile(initrd_path):
700
      raise errors.HypervisorError("Instance initrd '%s' not found or"
701
                                   " not a file" % initrd_path)