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