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