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