246c7f9f7b7b7db9336e61912d8fd14ae492d74e
[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] == "disk"
240     boot_cdrom = hvp[constants.HV_BOOT_ORDER] == "cdrom"
241     boot_network = hvp[constants.HV_BOOT_ORDER] == "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     @return: a dict with the following keys (values in MiB):
622           - memory_total: the total memory size on the node
623           - memory_free: the available memory on the node for instances
624           - memory_dom0: the memory used by the node itself, if available
625
626     """
627     # global ram usage from the xm info command
628     # memory                 : 3583
629     # free_memory            : 747
630     # note: in xen 3, memory has changed to total_memory
631     try:
632       fh = file("/proc/meminfo")
633       try:
634         data = fh.readlines()
635       finally:
636         fh.close()
637     except EnvironmentError, err:
638       raise errors.HypervisorError("Failed to list node info: %s" % err)
639
640     result = {}
641     sum_free = 0
642     for line in data:
643       splitfields = line.split(":", 1)
644
645       if len(splitfields) > 1:
646         key = splitfields[0].strip()
647         val = splitfields[1].strip()
648         if key == 'MemTotal':
649           result['memory_total'] = int(val.split()[0])/1024
650         elif key in ('MemFree', 'Buffers', 'Cached'):
651           sum_free += int(val.split()[0])/1024
652         elif key == 'Active':
653           result['memory_dom0'] = int(val.split()[0])/1024
654     result['memory_free'] = sum_free
655
656     cpu_total = 0
657     try:
658       fh = open("/proc/cpuinfo")
659       try:
660         cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
661                                    fh.read()))
662       finally:
663         fh.close()
664     except EnvironmentError, err:
665       raise errors.HypervisorError("Failed to list node info: %s" % err)
666     result['cpu_total'] = cpu_total
667     # FIXME: export correct data here
668     result['cpu_nodes'] = 1
669     result['cpu_sockets'] = 1
670
671     return result
672
673   @classmethod
674   def GetShellCommandForConsole(cls, instance, hvparams, beparams):
675     """Return a command for connecting to the console of an instance.
676
677     """
678     if hvparams[constants.HV_SERIAL_CONSOLE]:
679       # FIXME: The socat shell is not perfect. In particular the way we start
680       # it ctrl+c will close it, rather than being passed to the other end.
681       # On the other hand if we pass the option 'raw' (or ignbrk=1) there
682       # will be no way of exiting socat (except killing it from another shell)
683       # and ctrl+c doesn't work anyway, printing ^C rather than being
684       # interpreted by kvm. For now we'll leave it this way, which at least
685       # allows a minimal interaction and changes on the machine.
686       shell_command = ("%s STDIO,echo=0,icanon=0 UNIX-CONNECT:%s" %
687                        (constants.SOCAT_PATH,
688                         utils.ShellQuote(cls._InstanceSerial(instance.name))))
689     else:
690       shell_command = "echo 'No serial shell for instance %s'" % instance.name
691
692     vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
693     if vnc_bind_address:
694       if instance.network_port > constants.VNC_BASE_PORT:
695         display = instance.network_port - constants.VNC_BASE_PORT
696         vnc_command = ("echo 'Instance has VNC listening on %s:%d"
697                        " (display: %d)'" % (vnc_bind_address,
698                                             instance.network_port,
699                                             display))
700         shell_command = "%s; %s" % (vnc_command, shell_command)
701
702     return shell_command
703
704   def Verify(self):
705     """Verify the hypervisor.
706
707     Check that the binary exists.
708
709     """
710     if not os.path.exists(constants.KVM_PATH):
711       return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
712     if not os.path.exists(constants.SOCAT_PATH):
713       return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
714
715
716   @classmethod
717   def CheckParameterSyntax(cls, hvparams):
718     """Check the given parameters for validity.
719
720     @type hvparams:  dict
721     @param hvparams: dictionary with parameter names/value
722     @raise errors.HypervisorError: when a parameter is not valid
723
724     """
725     super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
726
727     kernel_path = hvparams[constants.HV_KERNEL_PATH]
728     if kernel_path:
729       if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
730         raise errors.HypervisorError("The kernel path must be an absolute path"
731                                      ", if defined")
732
733       if not hvparams[constants.HV_ROOT_PATH]:
734         raise errors.HypervisorError("Need a root partition for the instance"
735                                      ", if a kernel is defined")
736
737     if hvparams[constants.HV_INITRD_PATH]:
738       if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
739         raise errors.HypervisorError("The initrd path must an absolute path"
740                                      ", if defined")
741
742     vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
743     if vnc_bind_address:
744       if not utils.IsValidIP(vnc_bind_address):
745         if not os.path.isabs(vnc_bind_address):
746           raise errors.HypervisorError("The VNC bind address must be either"
747                                        " a valid IP address or an absolute"
748                                        " pathname. '%s' given" %
749                                        vnc_bind_address)
750
751     if hvparams[constants.HV_VNC_X509_VERIFY] and \
752       not hvparams[constants.HV_VNC_X509]:
753         raise errors.HypervisorError("%s must be defined, if %s is" %
754                                      (constants.HV_VNC_X509,
755                                       constants.HV_VNC_X509_VERIFY))
756
757     if hvparams[constants.HV_VNC_X509]:
758       if not os.path.isabs(hvparams[constants.HV_VNC_X509]):
759         raise errors.HypervisorError("The vnc x509 path must an absolute path"
760                                      ", if defined")
761
762     iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
763     if iso_path and not os.path.isabs(iso_path):
764       raise errors.HypervisorError("The path to the CDROM image must be"
765                                    " an absolute path, if defined")
766
767     boot_order = hvparams[constants.HV_BOOT_ORDER]
768     if boot_order not in ('cdrom', 'disk', 'network'):
769       raise errors.HypervisorError("The boot order must be 'cdrom', 'disk' or"
770                                    " 'network'")
771
772     if boot_order == 'cdrom' and not iso_path:
773       raise errors.HypervisorError("Cannot boot from cdrom without an ISO path")
774
775     nic_type = hvparams[constants.HV_NIC_TYPE]
776     if nic_type not in constants.HT_KVM_VALID_NIC_TYPES:
777       raise errors.HypervisorError("Invalid NIC type %s specified for the KVM"
778                                    " hypervisor. Please choose one of: %s" %
779                                    (nic_type,
780                                     constants.HT_KVM_VALID_NIC_TYPES))
781     elif boot_order == 'network' and nic_type == constants.HT_NIC_PARAVIRTUAL:
782       raise errors.HypervisorError("Cannot boot from a paravirtual NIC. Please"
783                                    " change the nic type.")
784
785     disk_type = hvparams[constants.HV_DISK_TYPE]
786     if disk_type not in constants.HT_KVM_VALID_DISK_TYPES:
787       raise errors.HypervisorError("Invalid disk type %s specified for the KVM"
788                                    " hypervisor. Please choose one of: %s" %
789                                    (disk_type,
790                                     constants.HT_KVM_VALID_DISK_TYPES))
791
792     mouse_type = hvparams[constants.HV_USB_MOUSE]
793     if mouse_type and mouse_type not in ('mouse', 'tablet'):
794       raise errors.HypervisorError("Invalid usb mouse type %s specified for"
795                                    " the KVM hyervisor. Please choose"
796                                    " 'mouse' or 'tablet'" % mouse_type)
797
798   def ValidateParameters(self, hvparams):
799     """Check the given parameters for validity.
800
801     For the KVM hypervisor, this checks the existence of the
802     kernel.
803
804     """
805     super(KVMHypervisor, self).ValidateParameters(hvparams)
806
807     kernel_path = hvparams[constants.HV_KERNEL_PATH]
808     if kernel_path and not os.path.isfile(kernel_path):
809       raise errors.HypervisorError("Instance kernel '%s' not found or"
810                                    " not a file" % kernel_path)
811     initrd_path = hvparams[constants.HV_INITRD_PATH]
812     if initrd_path and not os.path.isfile(initrd_path):
813       raise errors.HypervisorError("Instance initrd '%s' not found or"
814                                    " not a file" % initrd_path)
815
816     vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
817     if vnc_bind_address and not utils.IsValidIP(vnc_bind_address) and \
818        not os.path.isdir(vnc_bind_address):
819        raise errors.HypervisorError("Instance vnc bind address must be either"
820                                     " an ip address or an existing directory")
821
822     vnc_x509 = hvparams[constants.HV_VNC_X509]
823     if vnc_x509 and not os.path.isdir(vnc_x509):
824       raise errors.HypervisorError("Instance vnc x509 path '%s' not found"
825                                    " or not a directory" % vnc_x509)
826
827     iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
828     if iso_path and not os.path.isfile(iso_path):
829       raise errors.HypervisorError("Instance cdrom image '%s' not found or"
830                                    " not a file" % iso_path)