KVM: don't boot from cdrom with no 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         boot_val = ',boot=on'
244         boot_disk = False
245       else:
246         boot_val = ''
247
248       # TODO: handle different if= types
249       if_val = ',if=virtio'
250
251       drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
252       kvm_cmd.extend(['-drive', drive_val])
253
254     iso_image = instance.hvparams[constants.HV_CDROM_IMAGE_PATH]
255     if iso_image:
256       options = ',format=raw,if=virtio,media=cdrom'
257       if boot_cdrom:
258         options = '%s,boot=on' % options
259       drive_val = 'file=%s%s' % (iso_image, options)
260       kvm_cmd.extend(['-drive', drive_val])
261
262     kernel_path = instance.hvparams[constants.HV_KERNEL_PATH]
263     if kernel_path:
264       kvm_cmd.extend(['-kernel', kernel_path])
265       initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
266       if initrd_path:
267         kvm_cmd.extend(['-initrd', initrd_path])
268       root_append = 'root=%s ro' % instance.hvparams[constants.HV_ROOT_PATH]
269       if instance.hvparams[constants.HV_SERIAL_CONSOLE]:
270         kvm_cmd.extend(['-append', 'console=ttyS0,38400 %s' % root_append])
271       else:
272         kvm_cmd.extend(['-append', root_append])
273
274     # FIXME: handle vnc password
275     vnc_bind_address = instance.hvparams[constants.HV_VNC_BIND_ADDRESS]
276     if vnc_bind_address:
277       kvm_cmd.extend(['-usbdevice', 'tablet'])
278       if utils.IsValidIP(vnc_bind_address):
279         if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
280           display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
281           if vnc_bind_address == '0.0.0.0':
282             vnc_arg = ':%d' % (display)
283           else:
284             vnc_arg = '%s:%d' % (constants.HV_VNC_BIND_ADDRESS, display)
285         else:
286           logging.error("Network port is not a valid VNC display (%d < %d)."
287                         " Not starting VNC" %
288                         (instance.network_port,
289                          constants.HT_HVM_VNC_BASE_PORT))
290           vnc_arg = 'none'
291
292         # Only allow tls and other option when not binding to a file, for now.
293         # kvm/qemu gets confused otherwise about the filename to use.
294         vnc_append = ''
295         if instance.hvparams[constants.HV_VNC_TLS]:
296           vnc_append = '%s,tls' % vnc_append
297           if instance.hvparams[constants.HV_VNC_X509_VERIFY]:
298             vnc_append = '%s,x509verify=%s' % (vnc_append,
299               instance.hvparams[constants.HV_VNC_X509])
300           elif instance.hvparams[constants.HV_VNC_X509]:
301             vnc_append = '%s,x509=%s' % (vnc_append,
302               instance.hvparams[constants.HV_VNC_X509])
303         vnc_arg = '%s%s' % (vnc_arg, vnc_append)
304
305       else:
306         vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
307
308       kvm_cmd.extend(['-vnc', vnc_arg])
309     else:
310       kvm_cmd.extend(['-nographic'])
311
312     monitor_dev = 'unix:%s,server,nowait' % \
313       self._InstanceMonitor(instance.name)
314     kvm_cmd.extend(['-monitor', monitor_dev])
315     if instance.hvparams[constants.HV_SERIAL_CONSOLE]:
316       serial_dev = 'unix:%s,server,nowait' % self._InstanceSerial(instance.name)
317       kvm_cmd.extend(['-serial', serial_dev])
318     else:
319       kvm_cmd.extend(['-serial', 'none'])
320
321     # Save the current instance nics, but defer their expansion as parameters,
322     # as we'll need to generate executable temp files for them.
323     kvm_nics = instance.nics
324
325     return (kvm_cmd, kvm_nics)
326
327   def _WriteKVMRuntime(self, instance_name, data):
328     """Write an instance's KVM runtime
329
330     """
331     try:
332       utils.WriteFile(self._InstanceKVMRuntime(instance_name),
333                       data=data)
334     except EnvironmentError, err:
335       raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
336
337   def _ReadKVMRuntime(self, instance_name):
338     """Read an instance's KVM runtime
339
340     """
341     try:
342       file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
343     except EnvironmentError, err:
344       raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
345     return file_content
346
347   def _SaveKVMRuntime(self, instance, kvm_runtime):
348     """Save an instance's KVM runtime
349
350     """
351     kvm_cmd, kvm_nics = kvm_runtime
352     serialized_nics = [nic.ToDict() for nic in kvm_nics]
353     serialized_form = serializer.Dump((kvm_cmd, serialized_nics))
354     self._WriteKVMRuntime(instance.name, serialized_form)
355
356   def _LoadKVMRuntime(self, instance, serialized_runtime=None):
357     """Load an instance's KVM runtime
358
359     """
360     if not serialized_runtime:
361       serialized_runtime = self._ReadKVMRuntime(instance.name)
362     loaded_runtime = serializer.Load(serialized_runtime)
363     kvm_cmd, serialized_nics = loaded_runtime
364     kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
365     return (kvm_cmd, kvm_nics)
366
367   def _ExecuteKVMRuntime(self, instance, kvm_runtime, incoming=None):
368     """Execute a KVM cmd, after completing it with some last minute data
369
370     @type incoming: tuple of strings
371     @param incoming: (target_host_ip, port)
372
373     """
374     pidfile, pid, alive = self._InstancePidAlive(instance.name)
375     if alive:
376       raise errors.HypervisorError("Failed to start instance %s: %s" %
377                                    (instance.name, "already running"))
378
379     temp_files = []
380
381     kvm_cmd, kvm_nics = kvm_runtime
382
383     if not kvm_nics:
384       kvm_cmd.extend(['-net', 'none'])
385     else:
386       for nic_seq, nic in enumerate(kvm_nics):
387         nic_val = "nic,macaddr=%s,model=virtio" % nic.mac
388         script = self._WriteNetScript(instance, nic_seq, nic)
389         kvm_cmd.extend(['-net', nic_val])
390         kvm_cmd.extend(['-net', 'tap,script=%s' % script])
391         temp_files.append(script)
392
393     if incoming:
394       target, port = incoming
395       kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
396
397     result = utils.RunCmd(kvm_cmd)
398     if result.failed:
399       raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
400                                    (instance.name, result.fail_reason,
401                                     result.output))
402
403     if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
404       raise errors.HypervisorError("Failed to start instance %s: %s" %
405                                    (instance.name))
406
407     for filename in temp_files:
408       utils.RemoveFile(filename)
409
410   def StartInstance(self, instance, block_devices, extra_args):
411     """Start an instance.
412
413     """
414     pidfile, pid, alive = self._InstancePidAlive(instance.name)
415     if alive:
416       raise errors.HypervisorError("Failed to start instance %s: %s" %
417                                    (instance.name, "already running"))
418
419     kvm_runtime = self._GenerateKVMRuntime(instance, block_devices, extra_args)
420     self._SaveKVMRuntime(instance, kvm_runtime)
421     self._ExecuteKVMRuntime(instance, kvm_runtime)
422
423   def _CallMonitorCommand(self, instance_name, command):
424     """Invoke a command on the instance monitor.
425
426     """
427     socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
428              (utils.ShellQuote(command),
429               constants.SOCAT_PATH,
430               utils.ShellQuote(self._InstanceMonitor(instance_name))))
431     result = utils.RunCmd(socat)
432     if result.failed:
433       msg = ("Failed to send command '%s' to instance %s."
434              " output: %s, error: %s, fail_reason: %s" %
435              (instance.name, result.stdout, result.stderr, result.fail_reason))
436       raise errors.HypervisorError(msg)
437
438     return result
439
440   def _RetryInstancePowerdown(self, instance, pid, timeout=30):
441     """Wait for an instance  to power down.
442
443     """
444     # Wait up to $timeout seconds
445     end = time.time() + timeout
446     wait = 1
447     while time.time() < end and utils.IsProcessAlive(pid):
448       self._CallMonitorCommand(instance.name, 'system_powerdown')
449       time.sleep(wait)
450       # Make wait time longer for next try
451       if wait < 5:
452         wait *= 1.3
453
454   def StopInstance(self, instance, force=False):
455     """Stop an instance.
456
457     """
458     pidfile, pid, alive = self._InstancePidAlive(instance.name)
459     if pid > 0 and alive:
460       if force or not instance.hvparams[constants.HV_ACPI]:
461         utils.KillProcess(pid)
462       else:
463         self._RetryInstancePowerdown(instance, pid)
464
465     if not utils.IsProcessAlive(pid):
466       utils.RemoveFile(pidfile)
467       utils.RemoveFile(self._InstanceMonitor(instance.name))
468       utils.RemoveFile(self._InstanceSerial(instance.name))
469       utils.RemoveFile(self._InstanceKVMRuntime(instance.name))
470       return True
471     else:
472       return False
473
474   def RebootInstance(self, instance):
475     """Reboot an instance.
476
477     """
478     # For some reason if we do a 'send-key ctrl-alt-delete' to the control
479     # socket the instance will stop, but now power up again. So we'll resort
480     # to shutdown and restart.
481     pidfile, pid, alive = self._InstancePidAlive(instance.name)
482     if not alive:
483       raise errors.HypervisorError("Failed to reboot instance %s: not running" %
484                                              (instance.name))
485     # StopInstance will delete the saved KVM runtime so:
486     # ...first load it...
487     kvm_runtime = self._LoadKVMRuntime(instance)
488     # ...now we can safely call StopInstance...
489     if not self.StopInstance(instance):
490       self.StopInstance(instance, force=True)
491     # ...and finally we can save it again, and execute it...
492     self._SaveKVMRuntime(instance, kvm_runtime)
493     self._ExecuteKVMRuntime(instance, kvm_runtime)
494
495   def MigrationInfo(self, instance):
496     """Get instance information to perform a migration.
497
498     @type instance: L{objects.Instance}
499     @param instance: instance to be migrated
500     @rtype: string
501     @return: content of the KVM runtime file
502
503     """
504     return self._ReadKVMRuntime(instance.name)
505
506   def AcceptInstance(self, instance, info, target):
507     """Prepare to accept an instance.
508
509     @type instance: L{objects.Instance}
510     @param instance: instance to be accepted
511     @type info: string
512     @param info: content of the KVM runtime file on the source node
513     @type target: string
514     @param target: target host (usually ip), on this node
515
516     """
517     kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
518     incoming_address = (target, constants.KVM_MIGRATION_PORT)
519     self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
520
521   def FinalizeMigration(self, instance, info, success):
522     """Finalize an instance migration.
523
524     Stop the incoming mode KVM.
525
526     @type instance: L{objects.Instance}
527     @param instance: instance whose migration is being aborted
528
529     """
530     if success:
531       self._WriteKVMRuntime(instance.name, info)
532     else:
533       self.StopInstance(instance, force=True)
534
535   def MigrateInstance(self, instance_name, target, live):
536     """Migrate an instance to a target node.
537
538     The migration will not be attempted if the instance is not
539     currently running.
540
541     @type instance_name: string
542     @param instance_name: name of the instance to be migrated
543     @type target: string
544     @param target: ip address of the target node
545     @type live: boolean
546     @param live: perform a live migration
547
548     """
549     pidfile, pid, alive = self._InstancePidAlive(instance_name)
550     if not alive:
551       raise errors.HypervisorError("Instance not running, cannot migrate")
552
553     if not live:
554       self._CallMonitorCommand(instance_name, 'stop')
555
556     migrate_command = ('migrate -d tcp:%s:%s' %
557                        (target, constants.KVM_MIGRATION_PORT))
558     self._CallMonitorCommand(instance_name, migrate_command)
559
560     info_command = 'info migrate'
561     done = False
562     while not done:
563       result = self._CallMonitorCommand(instance_name, info_command)
564       match = self._MIGRATION_STATUS_RE.search(result.stdout)
565       if not match:
566         raise errors.HypervisorError("Unknown 'info migrate' result: %s" %
567                                      result.stdout)
568       else:
569         status = match.group(1)
570         if status == 'completed':
571           done = True
572         elif status == 'active':
573           time.sleep(2)
574         elif status == 'failed' or status == 'cancelled':
575           if not live:
576             self._CallMonitorCommand(instance_name, 'cont')
577           raise errors.HypervisorError("Migration %s at the kvm level" %
578                                        status)
579         else:
580           logging.info("KVM: unknown migration status '%s'" % status)
581           time.sleep(2)
582
583     utils.KillProcess(pid)
584     utils.RemoveFile(pidfile)
585     utils.RemoveFile(self._InstanceMonitor(instance_name))
586     utils.RemoveFile(self._InstanceSerial(instance_name))
587     utils.RemoveFile(self._InstanceKVMRuntime(instance_name))
588
589   def GetNodeInfo(self):
590     """Return information about the node.
591
592     @return: a dict with the following keys (values in MiB):
593           - memory_total: the total memory size on the node
594           - memory_free: the available memory on the node for instances
595           - memory_dom0: the memory used by the node itself, if available
596
597     """
598     # global ram usage from the xm info command
599     # memory                 : 3583
600     # free_memory            : 747
601     # note: in xen 3, memory has changed to total_memory
602     try:
603       fh = file("/proc/meminfo")
604       try:
605         data = fh.readlines()
606       finally:
607         fh.close()
608     except EnvironmentError, err:
609       raise errors.HypervisorError("Failed to list node info: %s" % err)
610
611     result = {}
612     sum_free = 0
613     for line in data:
614       splitfields = line.split(":", 1)
615
616       if len(splitfields) > 1:
617         key = splitfields[0].strip()
618         val = splitfields[1].strip()
619         if key == 'MemTotal':
620           result['memory_total'] = int(val.split()[0])/1024
621         elif key in ('MemFree', 'Buffers', 'Cached'):
622           sum_free += int(val.split()[0])/1024
623         elif key == 'Active':
624           result['memory_dom0'] = int(val.split()[0])/1024
625     result['memory_free'] = sum_free
626
627     cpu_total = 0
628     try:
629       fh = open("/proc/cpuinfo")
630       try:
631         cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
632                                    fh.read()))
633       finally:
634         fh.close()
635     except EnvironmentError, err:
636       raise errors.HypervisorError("Failed to list node info: %s" % err)
637     result['cpu_total'] = cpu_total
638
639     return result
640
641   @classmethod
642   def GetShellCommandForConsole(cls, instance, hvparams, beparams):
643     """Return a command for connecting to the console of an instance.
644
645     """
646     if hvparams[constants.HV_SERIAL_CONSOLE]:
647       # FIXME: The socat shell is not perfect. In particular the way we start
648       # it ctrl+c will close it, rather than being passed to the other end.
649       # On the other hand if we pass the option 'raw' (or ignbrk=1) there
650       # will be no way of exiting socat (except killing it from another shell)
651       # and ctrl+c doesn't work anyway, printing ^C rather than being
652       # interpreted by kvm. For now we'll leave it this way, which at least
653       # allows a minimal interaction and changes on the machine.
654       shell_command = ("%s STDIO,echo=0,icanon=0 UNIX-CONNECT:%s" %
655                        (constants.SOCAT_PATH,
656                         utils.ShellQuote(cls._InstanceSerial(instance.name))))
657     else:
658       shell_command = "echo 'No serial shell for instance %s'" % instance.name
659
660     vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
661     if vnc_bind_address:
662       if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
663         display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
664         vnc_command = ("echo 'Instance has VNC listening on %s:%d"
665                        " (display: %d)'" % (vnc_bind_address,
666                                             instance.network_port,
667                                             display))
668         shell_command = "%s; %s" % (vnc_command, shell_command)
669
670     return shell_command
671
672   def Verify(self):
673     """Verify the hypervisor.
674
675     Check that the binary exists.
676
677     """
678     if not os.path.exists(constants.KVM_PATH):
679       return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
680     if not os.path.exists(constants.SOCAT_PATH):
681       return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
682
683
684   @classmethod
685   def CheckParameterSyntax(cls, hvparams):
686     """Check the given parameters for validity.
687
688     For the KVM hypervisor, this only check the existence of the
689     kernel.
690
691     @type hvparams:  dict
692     @param hvparams: dictionary with parameter names/value
693     @raise errors.HypervisorError: when a parameter is not valid
694
695     """
696     super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
697
698     kernel_path = hvparams[constants.HV_KERNEL_PATH]
699     if kernel_path:
700       if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
701         raise errors.HypervisorError("The kernel path must be an absolute path"
702                                      ", if defined")
703
704       if not hvparams[constants.HV_ROOT_PATH]:
705         raise errors.HypervisorError("Need a root partition for the instance"
706                                      ", if a kernel is defined")
707
708     if hvparams[constants.HV_INITRD_PATH]:
709       if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
710         raise errors.HypervisorError("The initrd path must an absolute path"
711                                      ", if defined")
712
713     vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
714     if vnc_bind_address:
715       if not utils.IsValidIP(vnc_bind_address):
716         if not os.path.isabs(vnc_bind_address):
717           raise errors.HypervisorError("The VNC bind address must be either"
718                                        " a valid IP address or an absolute"
719                                        " pathname. '%s' given" %
720                                        vnc_bind_address)
721
722     if hvparams[constants.HV_VNC_X509_VERIFY] and \
723       not hvparams[constants.HV_VNC_X509]:
724         raise errors.HypervisorError("%s must be defined, if %s is" %
725                                      (constants.HV_VNC_X509,
726                                       constants.HV_VNC_X509_VERIFY))
727
728     if hvparams[constants.HV_VNC_X509]:
729       if not os.path.isabs(hvparams[constants.HV_VNC_X509]):
730         raise errors.HypervisorError("The vnc x509 path must an absolute path"
731                                      ", if defined")
732
733     iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
734     if iso_path and not os.path.isabs(iso_path):
735       raise errors.HypervisorError("The path to the CDROM image must be"
736                                    " an absolute path, if defined")
737
738     boot_order = hvparams[constants.HV_BOOT_ORDER]
739     if boot_order not in ('cdrom', 'disk'):
740       raise errors.HypervisorError("The boot order must be 'cdrom' or 'disk'")
741
742     if boot_order == 'cdrom' and not iso_path:
743       raise errors.HypervisorError("Cannot boot from cdrom without an ISO path")
744
745   def ValidateParameters(self, hvparams):
746     """Check the given parameters for validity.
747
748     For the KVM hypervisor, this checks the existence of the
749     kernel.
750
751     """
752     super(KVMHypervisor, self).ValidateParameters(hvparams)
753
754     kernel_path = hvparams[constants.HV_KERNEL_PATH]
755     if kernel_path and not os.path.isfile(kernel_path):
756       raise errors.HypervisorError("Instance kernel '%s' not found or"
757                                    " not a file" % kernel_path)
758     initrd_path = hvparams[constants.HV_INITRD_PATH]
759     if initrd_path and not os.path.isfile(initrd_path):
760       raise errors.HypervisorError("Instance initrd '%s' not found or"
761                                    " not a file" % initrd_path)
762
763     vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
764     if vnc_bind_address and not utils.IsValidIP(vnc_bind_address) and \
765        not os.path.isdir(vnc_bind_address):
766        raise errors.HypervisorError("Instance vnc bind address must be either"
767                                     " an ip address or an existing directory")
768
769     vnc_x509 = hvparams[constants.HV_VNC_X509]
770     if vnc_x509 and not os.path.isdir(vnc_x509):
771       raise errors.HypervisorError("Instance vnc x509 path '%s' not found"
772                                    " or not a directory" % vnc_x509)
773
774     iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
775     if iso_path and not os.path.isfile(iso_path):
776       raise errors.HypervisorError("Instance cdrom image '%s' not found or"
777                                    " not a file" % iso_path)
778
779