KVM: don't boot from a virtio cdrom
[ganeti-local] / lib / hypervisor / hv_kvm.py
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     ]
64
65   _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)',
66                                     re.M | re.I)
67
68   def __init__(self):
69     hv_base.BaseHypervisor.__init__(self)
70     # Let's make sure the directories we need exist, even if the RUN_DIR lives
71     # in a tmpfs filesystem or has been otherwise wiped out.
72     for mydir in self._DIRS:
73       if not os.path.exists(mydir):
74         os.mkdir(mydir)
75
76   def _InstancePidAlive(self, instance_name):
77     """Returns the instance pid and pidfile
78
79     """
80     pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
81     pid = utils.ReadPidFile(pidfile)
82     alive = utils.IsProcessAlive(pid)
83
84     return (pidfile, pid, alive)
85
86   @classmethod
87   def _InstanceMonitor(cls, instance_name):
88     """Returns the instance monitor socket name
89
90     """
91     return '%s/%s.monitor' % (cls._CTRL_DIR, instance_name)
92
93   @classmethod
94   def _InstanceSerial(cls, instance_name):
95     """Returns the instance serial socket name
96
97     """
98     return '%s/%s.serial' % (cls._CTRL_DIR, instance_name)
99
100   @classmethod
101   def _InstanceKVMRuntime(cls, instance_name):
102     """Returns the instance KVM runtime filename
103
104     """
105     return '%s/%s.runtime' % (cls._CONF_DIR, instance_name)
106
107   def _WriteNetScript(self, instance, seq, nic):
108     """Write a script to connect a net interface to the proper bridge.
109
110     This can be used by any qemu-type hypervisor.
111
112     @param instance: instance we're acting on
113     @type instance: instance object
114     @param seq: nic sequence number
115     @type seq: int
116     @param nic: nic we're acting on
117     @type nic: nic object
118     @return: netscript file name
119     @rtype: string
120
121     """
122     script = StringIO()
123     script.write("#!/bin/sh\n")
124     script.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
125     script.write("export INSTANCE=%s\n" % instance.name)
126     script.write("export MAC=%s\n" % nic.mac)
127     script.write("export IP=%s\n" % nic.ip)
128     script.write("export BRIDGE=%s\n" % nic.bridge)
129     script.write("export INTERFACE=$1\n")
130     # TODO: make this configurable at ./configure time
131     script.write("if [ -x /etc/ganeti/kvm-vif-bridge ]; then\n")
132     script.write("  # Execute the user-specific vif file\n")
133     script.write("  /etc/ganeti/kvm-vif-bridge\n")
134     script.write("else\n")
135     script.write("  # Connect the interface to the bridge\n")
136     script.write("  /sbin/ifconfig $INTERFACE 0.0.0.0 up\n")
137     script.write("  /usr/sbin/brctl addif $BRIDGE $INTERFACE\n")
138     script.write("fi\n\n")
139     # As much as we'd like to put this in our _ROOT_DIR, that will happen to be
140     # mounted noexec sometimes, so we'll have to find another place.
141     (tmpfd, tmpfile_name) = tempfile.mkstemp()
142     tmpfile = os.fdopen(tmpfd, 'w')
143     tmpfile.write(script.getvalue())
144     tmpfile.close()
145     os.chmod(tmpfile_name, 0755)
146     return tmpfile_name
147
148   def ListInstances(self):
149     """Get the list of running instances.
150
151     We can do this by listing our live instances directory and
152     checking whether the associated kvm process is still alive.
153
154     """
155     result = []
156     for name in os.listdir(self._PIDS_DIR):
157       filename = "%s/%s" % (self._PIDS_DIR, name)
158       if utils.IsProcessAlive(utils.ReadPidFile(filename)):
159         result.append(name)
160     return result
161
162   def GetInstanceInfo(self, instance_name):
163     """Get instance properties.
164
165     @param instance_name: the instance name
166
167     @return: tuple (name, id, memory, vcpus, stat, times)
168
169     """
170     pidfile, pid, alive = self._InstancePidAlive(instance_name)
171     if not alive:
172       return None
173
174     cmdline_file = "/proc/%s/cmdline" % pid
175     try:
176       fh = open(cmdline_file, 'r')
177       try:
178         cmdline = fh.read()
179       finally:
180         fh.close()
181     except EnvironmentError, err:
182       raise errors.HypervisorError("Failed to list instance %s: %s" %
183                                    (instance_name, err))
184
185     memory = 0
186     vcpus = 0
187     stat = "---b-"
188     times = "0"
189
190     arg_list = cmdline.split('\x00')
191     while arg_list:
192       arg =  arg_list.pop(0)
193       if arg == '-m':
194         memory = arg_list.pop(0)
195       elif arg == '-smp':
196         vcpus = arg_list.pop(0)
197
198     return (instance_name, pid, memory, vcpus, stat, times)
199
200   def GetAllInstancesInfo(self):
201     """Get properties of all instances.
202
203     @return: list of tuples (name, id, memory, vcpus, stat, times)
204
205     """
206     data = []
207     for name in os.listdir(self._PIDS_DIR):
208       filename = "%s/%s" % (self._PIDS_DIR, name)
209       if utils.IsProcessAlive(utils.ReadPidFile(filename)):
210         try:
211           info = self.GetInstanceInfo(name)
212         except errors.HypervisorError, err:
213           continue
214         if info:
215           data.append(info)
216
217     return data
218
219   def _GenerateKVMRuntime(self, instance, block_devices, extra_args):
220     """Generate KVM information to start an instance.
221
222     """
223     pidfile, pid, alive = self._InstancePidAlive(instance.name)
224     kvm = constants.KVM_PATH
225     kvm_cmd = [kvm]
226     kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
227     kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
228     kvm_cmd.extend(['-pidfile', pidfile])
229     # used just by the vnc server, if enabled
230     kvm_cmd.extend(['-name', instance.name])
231     kvm_cmd.extend(['-daemonize'])
232     if not instance.hvparams[constants.HV_ACPI]:
233       kvm_cmd.extend(['-no-acpi'])
234
235     boot_disk = (instance.hvparams[constants.HV_BOOT_ORDER] == "disk")
236     boot_cdrom = (instance.hvparams[constants.HV_BOOT_ORDER] == "cdrom")
237     for cfdev, dev_path in block_devices:
238       if cfdev.mode != constants.DISK_RDWR:
239         raise errors.HypervisorError("Instance has read-only disks which"
240                                      " are not supported by KVM")
241       # TODO: handle FD_LOOP and FD_BLKTAP (?)
242       if boot_disk:
243         kvm_cmd.extend(['-boot', 'c'])
244         boot_val = ',boot=on'
245         boot_disk = False
246       else:
247         boot_val = ''
248
249       # TODO: handle different if= types
250       if_val = ',if=virtio'
251
252       drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
253       kvm_cmd.extend(['-drive', drive_val])
254
255     iso_image = instance.hvparams[constants.HV_CDROM_IMAGE_PATH]
256     if iso_image:
257       options = ',format=raw,media=cdrom'
258       if boot_cdrom:
259         kvm_cmd.extend(['-boot', 'd'])
260         options = '%s,boot=on' % options
261       else:
262         options = '%s,if=virtio' % options
263       drive_val = 'file=%s%s' % (iso_image, options)
264       kvm_cmd.extend(['-drive', drive_val])
265
266     kernel_path = instance.hvparams[constants.HV_KERNEL_PATH]
267     if kernel_path:
268       kvm_cmd.extend(['-kernel', kernel_path])
269       initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
270       if initrd_path:
271         kvm_cmd.extend(['-initrd', initrd_path])
272       root_append = 'root=%s ro' % instance.hvparams[constants.HV_ROOT_PATH]
273       if instance.hvparams[constants.HV_SERIAL_CONSOLE]:
274         kvm_cmd.extend(['-append', 'console=ttyS0,38400 %s' % root_append])
275       else:
276         kvm_cmd.extend(['-append', root_append])
277
278     # FIXME: handle vnc password
279     vnc_bind_address = instance.hvparams[constants.HV_VNC_BIND_ADDRESS]
280     if vnc_bind_address:
281       kvm_cmd.extend(['-usbdevice', 'tablet'])
282       if utils.IsValidIP(vnc_bind_address):
283         if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
284           display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
285           if vnc_bind_address == '0.0.0.0':
286             vnc_arg = ':%d' % (display)
287           else:
288             vnc_arg = '%s:%d' % (constants.HV_VNC_BIND_ADDRESS, display)
289         else:
290           logging.error("Network port is not a valid VNC display (%d < %d)."
291                         " Not starting VNC" %
292                         (instance.network_port,
293                          constants.HT_HVM_VNC_BASE_PORT))
294           vnc_arg = 'none'
295
296         # Only allow tls and other option when not binding to a file, for now.
297         # kvm/qemu gets confused otherwise about the filename to use.
298         vnc_append = ''
299         if instance.hvparams[constants.HV_VNC_TLS]:
300           vnc_append = '%s,tls' % vnc_append
301           if instance.hvparams[constants.HV_VNC_X509_VERIFY]:
302             vnc_append = '%s,x509verify=%s' % (vnc_append,
303               instance.hvparams[constants.HV_VNC_X509])
304           elif instance.hvparams[constants.HV_VNC_X509]:
305             vnc_append = '%s,x509=%s' % (vnc_append,
306               instance.hvparams[constants.HV_VNC_X509])
307         vnc_arg = '%s%s' % (vnc_arg, vnc_append)
308
309       else:
310         vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
311
312       kvm_cmd.extend(['-vnc', vnc_arg])
313     else:
314       kvm_cmd.extend(['-nographic'])
315
316     monitor_dev = 'unix:%s,server,nowait' % \
317       self._InstanceMonitor(instance.name)
318     kvm_cmd.extend(['-monitor', monitor_dev])
319     if instance.hvparams[constants.HV_SERIAL_CONSOLE]:
320       serial_dev = 'unix:%s,server,nowait' % self._InstanceSerial(instance.name)
321       kvm_cmd.extend(['-serial', serial_dev])
322     else:
323       kvm_cmd.extend(['-serial', 'none'])
324
325     # Save the current instance nics, but defer their expansion as parameters,
326     # as we'll need to generate executable temp files for them.
327     kvm_nics = instance.nics
328
329     return (kvm_cmd, kvm_nics)
330
331   def _WriteKVMRuntime(self, instance_name, data):
332     """Write an instance's KVM runtime
333
334     """
335     try:
336       utils.WriteFile(self._InstanceKVMRuntime(instance_name),
337                       data=data)
338     except EnvironmentError, err:
339       raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
340
341   def _ReadKVMRuntime(self, instance_name):
342     """Read an instance's KVM runtime
343
344     """
345     try:
346       file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
347     except EnvironmentError, err:
348       raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
349     return file_content
350
351   def _SaveKVMRuntime(self, instance, kvm_runtime):
352     """Save an instance's KVM runtime
353
354     """
355     kvm_cmd, kvm_nics = kvm_runtime
356     serialized_nics = [nic.ToDict() for nic in kvm_nics]
357     serialized_form = serializer.Dump((kvm_cmd, serialized_nics))
358     self._WriteKVMRuntime(instance.name, serialized_form)
359
360   def _LoadKVMRuntime(self, instance, serialized_runtime=None):
361     """Load an instance's KVM runtime
362
363     """
364     if not serialized_runtime:
365       serialized_runtime = self._ReadKVMRuntime(instance.name)
366     loaded_runtime = serializer.Load(serialized_runtime)
367     kvm_cmd, serialized_nics = loaded_runtime
368     kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
369     return (kvm_cmd, kvm_nics)
370
371   def _ExecuteKVMRuntime(self, instance, kvm_runtime, incoming=None):
372     """Execute a KVM cmd, after completing it with some last minute data
373
374     @type incoming: tuple of strings
375     @param incoming: (target_host_ip, port)
376
377     """
378     pidfile, pid, alive = self._InstancePidAlive(instance.name)
379     if alive:
380       raise errors.HypervisorError("Failed to start instance %s: %s" %
381                                    (instance.name, "already running"))
382
383     temp_files = []
384
385     kvm_cmd, kvm_nics = kvm_runtime
386
387     if not kvm_nics:
388       kvm_cmd.extend(['-net', 'none'])
389     else:
390       for nic_seq, nic in enumerate(kvm_nics):
391         nic_val = "nic,macaddr=%s,model=virtio" % nic.mac
392         script = self._WriteNetScript(instance, nic_seq, nic)
393         kvm_cmd.extend(['-net', nic_val])
394         kvm_cmd.extend(['-net', 'tap,script=%s' % script])
395         temp_files.append(script)
396
397     if incoming:
398       target, port = incoming
399       kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
400
401     result = utils.RunCmd(kvm_cmd)
402     if result.failed:
403       raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
404                                    (instance.name, result.fail_reason,
405                                     result.output))
406
407     if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
408       raise errors.HypervisorError("Failed to start instance %s: %s" %
409                                    (instance.name))
410
411     for filename in temp_files:
412       utils.RemoveFile(filename)
413
414   def StartInstance(self, instance, block_devices, extra_args):
415     """Start an instance.
416
417     """
418     pidfile, pid, alive = self._InstancePidAlive(instance.name)
419     if alive:
420       raise errors.HypervisorError("Failed to start instance %s: %s" %
421                                    (instance.name, "already running"))
422
423     kvm_runtime = self._GenerateKVMRuntime(instance, block_devices, extra_args)
424     self._SaveKVMRuntime(instance, kvm_runtime)
425     self._ExecuteKVMRuntime(instance, kvm_runtime)
426
427   def _CallMonitorCommand(self, instance_name, command):
428     """Invoke a command on the instance monitor.
429
430     """
431     socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
432              (utils.ShellQuote(command),
433               constants.SOCAT_PATH,
434               utils.ShellQuote(self._InstanceMonitor(instance_name))))
435     result = utils.RunCmd(socat)
436     if result.failed:
437       msg = ("Failed to send command '%s' to instance %s."
438              " output: %s, error: %s, fail_reason: %s" %
439              (instance.name, result.stdout, result.stderr, result.fail_reason))
440       raise errors.HypervisorError(msg)
441
442     return result
443
444   def _RetryInstancePowerdown(self, instance, pid, timeout=30):
445     """Wait for an instance  to power down.
446
447     """
448     # Wait up to $timeout seconds
449     end = time.time() + timeout
450     wait = 1
451     while time.time() < end and utils.IsProcessAlive(pid):
452       self._CallMonitorCommand(instance.name, 'system_powerdown')
453       time.sleep(wait)
454       # Make wait time longer for next try
455       if wait < 5:
456         wait *= 1.3
457
458   def StopInstance(self, instance, force=False):
459     """Stop an instance.
460
461     """
462     pidfile, pid, alive = self._InstancePidAlive(instance.name)
463     if pid > 0 and alive:
464       if force or not instance.hvparams[constants.HV_ACPI]:
465         utils.KillProcess(pid)
466       else:
467         self._RetryInstancePowerdown(instance, pid)
468
469     if not utils.IsProcessAlive(pid):
470       utils.RemoveFile(pidfile)
471       utils.RemoveFile(self._InstanceMonitor(instance.name))
472       utils.RemoveFile(self._InstanceSerial(instance.name))
473       utils.RemoveFile(self._InstanceKVMRuntime(instance.name))
474       return True
475     else:
476       return False
477
478   def RebootInstance(self, instance):
479     """Reboot an instance.
480
481     """
482     # For some reason if we do a 'send-key ctrl-alt-delete' to the control
483     # socket the instance will stop, but now power up again. So we'll resort
484     # to shutdown and restart.
485     pidfile, pid, alive = self._InstancePidAlive(instance.name)
486     if not alive:
487       raise errors.HypervisorError("Failed to reboot instance %s: not running" %
488                                              (instance.name))
489     # StopInstance will delete the saved KVM runtime so:
490     # ...first load it...
491     kvm_runtime = self._LoadKVMRuntime(instance)
492     # ...now we can safely call StopInstance...
493     if not self.StopInstance(instance):
494       self.StopInstance(instance, force=True)
495     # ...and finally we can save it again, and execute it...
496     self._SaveKVMRuntime(instance, kvm_runtime)
497     self._ExecuteKVMRuntime(instance, kvm_runtime)
498
499   def MigrationInfo(self, instance):
500     """Get instance information to perform a migration.
501
502     @type instance: L{objects.Instance}
503     @param instance: instance to be migrated
504     @rtype: string
505     @return: content of the KVM runtime file
506
507     """
508     return self._ReadKVMRuntime(instance.name)
509
510   def AcceptInstance(self, instance, info, target):
511     """Prepare to accept an instance.
512
513     @type instance: L{objects.Instance}
514     @param instance: instance to be accepted
515     @type info: string
516     @param info: content of the KVM runtime file on the source node
517     @type target: string
518     @param target: target host (usually ip), on this node
519
520     """
521     kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
522     incoming_address = (target, constants.KVM_MIGRATION_PORT)
523     self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
524
525   def FinalizeMigration(self, instance, info, success):
526     """Finalize an instance migration.
527
528     Stop the incoming mode KVM.
529
530     @type instance: L{objects.Instance}
531     @param instance: instance whose migration is being aborted
532
533     """
534     if success:
535       self._WriteKVMRuntime(instance.name, info)
536     else:
537       self.StopInstance(instance, force=True)
538
539   def MigrateInstance(self, instance_name, target, live):
540     """Migrate an instance to a target node.
541
542     The migration will not be attempted if the instance is not
543     currently running.
544
545     @type instance_name: string
546     @param instance_name: name of the instance to be migrated
547     @type target: string
548     @param target: ip address of the target node
549     @type live: boolean
550     @param live: perform a live migration
551
552     """
553     pidfile, pid, alive = self._InstancePidAlive(instance_name)
554     if not alive:
555       raise errors.HypervisorError("Instance not running, cannot migrate")
556
557     if not live:
558       self._CallMonitorCommand(instance_name, 'stop')
559
560     migrate_command = ('migrate -d tcp:%s:%s' %
561                        (target, constants.KVM_MIGRATION_PORT))
562     self._CallMonitorCommand(instance_name, migrate_command)
563
564     info_command = 'info migrate'
565     done = False
566     while not done:
567       result = self._CallMonitorCommand(instance_name, info_command)
568       match = self._MIGRATION_STATUS_RE.search(result.stdout)
569       if not match:
570         raise errors.HypervisorError("Unknown 'info migrate' result: %s" %
571                                      result.stdout)
572       else:
573         status = match.group(1)
574         if status == 'completed':
575           done = True
576         elif status == 'active':
577           time.sleep(2)
578         elif status == 'failed' or status == 'cancelled':
579           if not live:
580             self._CallMonitorCommand(instance_name, 'cont')
581           raise errors.HypervisorError("Migration %s at the kvm level" %
582                                        status)
583         else:
584           logging.info("KVM: unknown migration status '%s'" % status)
585           time.sleep(2)
586
587     utils.KillProcess(pid)
588     utils.RemoveFile(pidfile)
589     utils.RemoveFile(self._InstanceMonitor(instance_name))
590     utils.RemoveFile(self._InstanceSerial(instance_name))
591     utils.RemoveFile(self._InstanceKVMRuntime(instance_name))
592
593   def GetNodeInfo(self):
594     """Return information about the node.
595
596     @return: a dict with the following keys (values in MiB):
597           - memory_total: the total memory size on the node
598           - memory_free: the available memory on the node for instances
599           - memory_dom0: the memory used by the node itself, if available
600
601     """
602     # global ram usage from the xm info command
603     # memory                 : 3583
604     # free_memory            : 747
605     # note: in xen 3, memory has changed to total_memory
606     try:
607       fh = file("/proc/meminfo")
608       try:
609         data = fh.readlines()
610       finally:
611         fh.close()
612     except EnvironmentError, err:
613       raise errors.HypervisorError("Failed to list node info: %s" % err)
614
615     result = {}
616     sum_free = 0
617     for line in data:
618       splitfields = line.split(":", 1)
619
620       if len(splitfields) > 1:
621         key = splitfields[0].strip()
622         val = splitfields[1].strip()
623         if key == 'MemTotal':
624           result['memory_total'] = int(val.split()[0])/1024
625         elif key in ('MemFree', 'Buffers', 'Cached'):
626           sum_free += int(val.split()[0])/1024
627         elif key == 'Active':
628           result['memory_dom0'] = int(val.split()[0])/1024
629     result['memory_free'] = sum_free
630
631     cpu_total = 0
632     try:
633       fh = open("/proc/cpuinfo")
634       try:
635         cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
636                                    fh.read()))
637       finally:
638         fh.close()
639     except EnvironmentError, err:
640       raise errors.HypervisorError("Failed to list node info: %s" % err)
641     result['cpu_total'] = cpu_total
642
643     return result
644
645   @classmethod
646   def GetShellCommandForConsole(cls, instance, hvparams, beparams):
647     """Return a command for connecting to the console of an instance.
648
649     """
650     if hvparams[constants.HV_SERIAL_CONSOLE]:
651       # FIXME: The socat shell is not perfect. In particular the way we start
652       # it ctrl+c will close it, rather than being passed to the other end.
653       # On the other hand if we pass the option 'raw' (or ignbrk=1) there
654       # will be no way of exiting socat (except killing it from another shell)
655       # and ctrl+c doesn't work anyway, printing ^C rather than being
656       # interpreted by kvm. For now we'll leave it this way, which at least
657       # allows a minimal interaction and changes on the machine.
658       shell_command = ("%s STDIO,echo=0,icanon=0 UNIX-CONNECT:%s" %
659                        (constants.SOCAT_PATH,
660                         utils.ShellQuote(cls._InstanceSerial(instance.name))))
661     else:
662       shell_command = "echo 'No serial shell for instance %s'" % instance.name
663
664     vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
665     if vnc_bind_address:
666       if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
667         display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
668         vnc_command = ("echo 'Instance has VNC listening on %s:%d"
669                        " (display: %d)'" % (vnc_bind_address,
670                                             instance.network_port,
671                                             display))
672         shell_command = "%s; %s" % (vnc_command, shell_command)
673
674     return shell_command
675
676   def Verify(self):
677     """Verify the hypervisor.
678
679     Check that the binary exists.
680
681     """
682     if not os.path.exists(constants.KVM_PATH):
683       return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
684     if not os.path.exists(constants.SOCAT_PATH):
685       return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
686
687
688   @classmethod
689   def CheckParameterSyntax(cls, hvparams):
690     """Check the given parameters for validity.
691
692     For the KVM hypervisor, this only check the existence of the
693     kernel.
694
695     @type hvparams:  dict
696     @param hvparams: dictionary with parameter names/value
697     @raise errors.HypervisorError: when a parameter is not valid
698
699     """
700     super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
701
702     kernel_path = hvparams[constants.HV_KERNEL_PATH]
703     if kernel_path:
704       if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
705         raise errors.HypervisorError("The kernel path must be an absolute path"
706                                      ", if defined")
707
708       if not hvparams[constants.HV_ROOT_PATH]:
709         raise errors.HypervisorError("Need a root partition for the instance"
710                                      ", if a kernel is defined")
711
712     if hvparams[constants.HV_INITRD_PATH]:
713       if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
714         raise errors.HypervisorError("The initrd path must an absolute path"
715                                      ", if defined")
716
717     vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
718     if vnc_bind_address:
719       if not utils.IsValidIP(vnc_bind_address):
720         if not os.path.isabs(vnc_bind_address):
721           raise errors.HypervisorError("The VNC bind address must be either"
722                                        " a valid IP address or an absolute"
723                                        " pathname. '%s' given" %
724                                        vnc_bind_address)
725
726     if hvparams[constants.HV_VNC_X509_VERIFY] and \
727       not hvparams[constants.HV_VNC_X509]:
728         raise errors.HypervisorError("%s must be defined, if %s is" %
729                                      (constants.HV_VNC_X509,
730                                       constants.HV_VNC_X509_VERIFY))
731
732     if hvparams[constants.HV_VNC_X509]:
733       if not os.path.isabs(hvparams[constants.HV_VNC_X509]):
734         raise errors.HypervisorError("The vnc x509 path must an absolute path"
735                                      ", if defined")
736
737     iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
738     if iso_path and not os.path.isabs(iso_path):
739       raise errors.HypervisorError("The path to the CDROM image must be"
740                                    " an absolute path, if defined")
741
742     boot_order = hvparams[constants.HV_BOOT_ORDER]
743     if boot_order not in ('cdrom', 'disk'):
744       raise errors.HypervisorError("The boot order must be 'cdrom' or 'disk'")
745
746     if boot_order == 'cdrom' and not iso_path:
747       raise errors.HypervisorError("Cannot boot from cdrom without an ISO path")
748
749   def ValidateParameters(self, hvparams):
750     """Check the given parameters for validity.
751
752     For the KVM hypervisor, this checks the existence of the
753     kernel.
754
755     """
756     super(KVMHypervisor, self).ValidateParameters(hvparams)
757
758     kernel_path = hvparams[constants.HV_KERNEL_PATH]
759     if kernel_path and not os.path.isfile(kernel_path):
760       raise errors.HypervisorError("Instance kernel '%s' not found or"
761                                    " not a file" % kernel_path)
762     initrd_path = hvparams[constants.HV_INITRD_PATH]
763     if initrd_path and not os.path.isfile(initrd_path):
764       raise errors.HypervisorError("Instance initrd '%s' not found or"
765                                    " not a file" % initrd_path)
766
767     vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
768     if vnc_bind_address and not utils.IsValidIP(vnc_bind_address) and \
769        not os.path.isdir(vnc_bind_address):
770        raise errors.HypervisorError("Instance vnc bind address must be either"
771                                     " an ip address or an existing directory")
772
773     vnc_x509 = hvparams[constants.HV_VNC_X509]
774     if vnc_x509 and not os.path.isdir(vnc_x509):
775       raise errors.HypervisorError("Instance vnc x509 path '%s' not found"
776                                    " or not a directory" % vnc_x509)
777
778     iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
779     if iso_path and not os.path.isfile(iso_path):
780       raise errors.HypervisorError("Instance cdrom image '%s' not found or"
781                                    " not a file" % iso_path)
782
783