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