4 # Copyright (C) 2008 Google Inc.
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.
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.
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
30 from cStringIO import StringIO
32 from ganeti import utils
33 from ganeti import constants
34 from ganeti import errors
35 from ganeti.hypervisor import hv_base
38 class KVMHypervisor(hv_base.BaseHypervisor):
39 """Fake hypervisor interface.
41 This can be used for testing the ganeti code without having to have
42 a real virtualisation software installed.
45 _ROOT_DIR = constants.RUN_GANETI_DIR + "/kvm-hypervisor"
46 _PIDS_DIR = _ROOT_DIR + "/pid"
47 _CTRL_DIR = _ROOT_DIR + "/ctrl"
48 _DIRS = [_ROOT_DIR, _PIDS_DIR, _CTRL_DIR]
51 constants.HV_KERNEL_PATH,
52 constants.HV_INITRD_PATH,
57 hv_base.BaseHypervisor.__init__(self)
58 # Let's make sure the directories we need exist, even if the RUN_DIR lives
59 # in a tmpfs filesystem or has been otherwise wiped out.
60 for mydir in self._DIRS:
61 if not os.path.exists(mydir):
64 def _WriteNetScript(self, instance, seq, nic):
65 """Write a script to connect a net interface to the proper bridge.
67 This can be used by any qemu-type hypervisor.
69 @param instance: instance we're acting on
70 @type instance: instance object
71 @param seq: nic sequence number
73 @param nic: nic we're acting on
75 @return: netscript file name
80 script.write("#!/bin/sh\n")
81 script.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
82 script.write("export INSTANCE=%s\n" % instance.name)
83 script.write("export MAC=%s\n" % nic.mac)
84 script.write("export IP=%s\n" % nic.ip)
85 script.write("export BRIDGE=%s\n" % nic.bridge)
86 script.write("export INTERFACE=$1\n")
87 # TODO: make this configurable at ./configure time
88 script.write("if [ -x /etc/ganeti/kvm-vif-bridge ]; then\n")
89 script.write(" # Execute the user-specific vif file\n")
90 script.write(" /etc/ganeti/kvm-vif-bridge\n")
91 script.write("else\n")
92 script.write(" # Connect the interface to the bridge\n")
93 script.write(" /sbin/ifconfig $INTERFACE 0.0.0.0 up\n")
94 script.write(" /usr/sbin/brctl addif $BRIDGE $INTERFACE\n")
95 script.write("fi\n\n")
96 # As much as we'd like to put this in our _ROOT_DIR, that will happen to be
97 # mounted noexec sometimes, so we'll have to find another place.
98 (tmpfd, tmpfile_name) = tempfile.mkstemp()
99 tmpfile = os.fdopen(tmpfd, 'w')
100 tmpfile.write(script.getvalue())
102 os.chmod(tmpfile_name, 0755)
105 def ListInstances(self):
106 """Get the list of running instances.
108 We can do this by listing our live instances directory and
109 checking whether the associated kvm process is still alive.
113 for name in os.listdir(self._PIDS_DIR):
114 filename = "%s/%s" % (self._PIDS_DIR, name)
115 if utils.IsProcessAlive(utils.ReadPidFile(filename)):
119 def GetInstanceInfo(self, instance_name):
120 """Get instance properties.
122 @param instance_name: the instance name
124 @return: tuple (name, id, memory, vcpus, stat, times)
127 pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
128 pid = utils.ReadPidFile(pidfile)
129 if not utils.IsProcessAlive(pid):
132 cmdline_file = "/proc/%s/cmdline" % pid
134 fh = open(cmdline_file, 'r')
140 raise errors.HypervisorError("Failed to list instance %s: %s" %
141 (instance_name, err))
148 arg_list = cmdline.split('\x00')
150 arg = arg_list.pop(0)
152 memory = arg_list.pop(0)
154 vcpus = arg_list.pop(0)
156 return (instance_name, pid, memory, vcpus, stat, times)
158 def GetAllInstancesInfo(self):
159 """Get properties of all instances.
161 @return: list of tuples (name, id, memory, vcpus, stat, times)
165 for name in os.listdir(self._PIDS_DIR):
166 filename = "%s/%s" % (self._PIDS_DIR, name)
167 if utils.IsProcessAlive(utils.ReadPidFile(filename)):
168 data.append(self.GetInstanceInfo(name))
172 def StartInstance(self, instance, block_devices, extra_args):
173 """Start an instance.
177 pidfile = self._PIDS_DIR + "/%s" % instance.name
178 if utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
179 raise errors.HypervisorError("Failed to start instance %s: %s" %
180 (instance.name, "already running"))
182 kvm = constants.KVM_PATH
184 kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
185 kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
186 kvm_cmd.extend(['-pidfile', pidfile])
187 # used just by the vnc server, if enabled
188 kvm_cmd.extend(['-name', instance.name])
189 kvm_cmd.extend(['-daemonize'])
190 if not instance.hvparams[constants.HV_ACPI]:
191 kvm_cmd.extend(['-no-acpi'])
192 if not instance.nics:
193 kvm_cmd.extend(['-net', 'none'])
196 for nic in instance.nics:
197 script = self._WriteNetScript(instance, nic_seq, nic)
198 # FIXME: handle other models
199 nic_val = "nic,macaddr=%s,model=virtio" % nic.mac
200 kvm_cmd.extend(['-net', nic_val])
201 kvm_cmd.extend(['-net', 'tap,script=%s' % script])
202 temp_files.append(script)
206 for cfdev, dev_path in block_devices:
207 # TODO: handle FD_LOOP and FD_BLKTAP (?)
209 boot_val = ',boot=on'
214 # TODO: handle different if= types
215 if_val = ',if=virtio'
217 drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
218 kvm_cmd.extend(['-drive', drive_val])
220 kvm_cmd.extend(['-kernel', instance.hvparams[constants.HV_KERNEL_PATH]])
222 initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
224 kvm_cmd.extend(['-initrd', initrd_path])
226 kvm_cmd.extend(['-append', 'console=ttyS0,38400 root=/dev/vda'])
229 #"hvm_cdrom_image_path",
231 kvm_cmd.extend(['-nographic'])
232 # FIXME: handle vnc, if needed
233 # How do we decide whether to have it or not?? :(
236 base_control = '%s/%s' % (self._CTRL_DIR, instance.name)
237 monitor_dev = 'unix:%s.monitor,server,nowait' % base_control
238 kvm_cmd.extend(['-monitor', monitor_dev])
239 serial_dev = 'unix:%s.serial,server,nowait' % base_control
240 kvm_cmd.extend(['-serial', serial_dev])
242 result = utils.RunCmd(kvm_cmd)
244 raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
245 (instance.name, result.fail_reason,
248 if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
249 raise errors.HypervisorError("Failed to start instance %s: %s" %
252 for filename in temp_files:
253 utils.RemoveFile(filename)
255 def StopInstance(self, instance, force=False):
259 socat_bin = constants.SOCAT_PATH
260 pid_file = self._PIDS_DIR + "/%s" % instance.name
261 pid = utils.ReadPidFile(pid_file)
262 if pid > 0 and utils.IsProcessAlive(pid):
263 if force or not instance.hvparams[constants.HV_ACPI]:
264 utils.KillProcess(pid)
266 # This only works if the instance os has acpi support
267 monitor_socket = '%s/%s.monitor' % (self._CTRL_DIR, instance.name)
268 socat = '%s -u STDIN UNIX-CONNECT:%s' % (socat_bin, monitor_socket)
269 command = "echo 'system_powerdown' | %s" % socat
270 result = utils.RunCmd(command)
272 raise errors.HypervisorError("Failed to stop instance %s: %s" %
273 (instance.name, result.fail_reason))
275 if not utils.IsProcessAlive(pid):
276 utils.RemoveFile(pid_file)
278 def RebootInstance(self, instance):
279 """Reboot an instance.
282 # For some reason if we do a 'send-key ctrl-alt-delete' to the control
283 # socket the instance will stop, but now power up again. So we'll resort
284 # to shutdown and restart.
285 self.StopInstance(instance)
286 self.StartInstance(instance)
288 def GetNodeInfo(self):
289 """Return information about the node.
291 @return: a dict with the following keys (values in MiB):
292 - memory_total: the total memory size on the node
293 - memory_free: the available memory on the node for instances
294 - memory_dom0: the memory used by the node itself, if available
297 # global ram usage from the xm info command
300 # note: in xen 3, memory has changed to total_memory
302 fh = file("/proc/meminfo")
304 data = fh.readlines()
308 raise errors.HypervisorError("Failed to list node info: %s" % err)
313 splitfields = line.split(":", 1)
315 if len(splitfields) > 1:
316 key = splitfields[0].strip()
317 val = splitfields[1].strip()
318 if key == 'MemTotal':
319 result['memory_total'] = int(val.split()[0])/1024
320 elif key in ('MemFree', 'Buffers', 'Cached'):
321 sum_free += int(val.split()[0])/1024
322 elif key == 'Active':
323 result['memory_dom0'] = int(val.split()[0])/1024
324 result['memory_free'] = sum_free
328 fh = open("/proc/cpuinfo")
330 cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
334 except EnvironmentError, err:
335 raise errors.HypervisorError("Failed to list node info: %s" % err)
336 result['cpu_total'] = cpu_total
341 def GetShellCommandForConsole(instance):
342 """Return a command for connecting to the console of an instance.
345 # TODO: we can either try the serial socket or suggest vnc
346 return "echo Console not available for the kvm hypervisor yet"
349 """Verify the hypervisor.
351 Check that the binary exists.
354 if not os.path.exists(constants.KVM_PATH):
355 return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
356 if not os.path.exists(constants.SOCAT_PATH):
357 return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
361 def CheckParameterSyntax(cls, hvparams):
362 """Check the given parameters for validity.
364 For the KVM hypervisor, this only check the existence of the
368 @param hvparams: dictionary with parameter names/value
369 @raise errors.HypervisorError: when a parameter is not valid
372 super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
374 if not hvparams[constants.HV_KERNEL_PATH]:
375 raise errors.HypervisorError("Need a kernel for the instance")
377 if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
378 raise errors.HypervisorError("The kernel path must an absolute path")
380 if hvparams[constants.HV_INITRD_PATH]:
381 if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
382 raise errors.HypervisorError("The initrd path must an absolute path"
385 def ValidateParameters(self, hvparams):
386 """Check the given parameters for validity.
388 For the KVM hypervisor, this checks the existence of the
392 super(KVMHypervisor, self).ValidateParameters(hvparams)
394 kernel_path = hvparams[constants.HV_KERNEL_PATH]
395 if not os.path.isfile(kernel_path):
396 raise errors.HypervisorError("Instance kernel '%s' not found or"
397 " not a file" % kernel_path)
398 initrd_path = hvparams[constants.HV_INITRD_PATH]
399 if initrd_path and not os.path.isfile(initrd_path):
400 raise errors.HypervisorError("Instance initrd '%s' not found or"
401 " not a file" % initrd_path)