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 hv_base.BaseHypervisor.__init__(self)
52 # Let's make sure the directories we need exist, even if the RUN_DIR lives
53 # in a tmpfs filesystem or has been otherwise wiped out.
54 for dir in self._DIRS:
55 if not os.path.exists(dir):
58 def _WriteNetScript(self, instance, seq, nic):
59 """Write a script to connect a net interface to the proper bridge.
61 This can be used by any qemu-type hypervisor.
63 @param instance: instance we're acting on
64 @type instance: instance object
65 @param seq: nic sequence number
67 @param nic: nic we're acting on
69 @return: netscript file name
74 script.write("#!/bin/sh\n")
75 script.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
76 script.write("export INSTANCE=%s\n" % instance.name)
77 script.write("export MAC=%s\n" % nic.mac)
78 script.write("export IP=%s\n" % nic.ip)
79 script.write("export BRIDGE=%s\n" % nic.bridge)
80 script.write("export INTERFACE=$1\n")
81 # TODO: make this configurable at ./configure time
82 script.write("if [ -x /etc/ganeti/kvm-vif-bridge ]; then\n")
83 script.write(" # Execute the user-specific vif file\n")
84 script.write(" /etc/ganeti/kvm-vif-bridge\n")
85 script.write("else\n")
86 script.write(" # Connect the interface to the bridge\n")
87 script.write(" /sbin/ifconfig $INTERFACE 0.0.0.0 up\n")
88 script.write(" /usr/sbin/brctl addif $BRIDGE $INTERFACE\n")
89 script.write("fi\n\n")
90 # As much as we'd like to put this in our _ROOT_DIR, that will happen to be
91 # mounted noexec sometimes, so we'll have to find another place.
92 (tmpfd, tmpfile_name) = tempfile.mkstemp()
93 tmpfile = os.fdopen(tmpfd, 'w')
94 tmpfile.write(script.getvalue())
96 os.chmod(tmpfile_name, 0755)
99 def ListInstances(self):
100 """Get the list of running instances.
102 We can do this by listing our live instances directory and checking whether
103 the associated kvm process is still alive.
107 for name in os.listdir(self._PIDS_DIR):
108 file = "%s/%s" % (self._PIDS_DIR, name)
109 if utils.IsProcessAlive(utils.ReadPidFile(file)):
113 def GetInstanceInfo(self, instance_name):
114 """Get instance properties.
117 instance_name: the instance name
120 (name, id, memory, vcpus, stat, times)
122 pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
123 pid = utils.ReadPidFile(pidfile)
124 if not utils.IsProcessAlive(pid):
127 cmdline_file = "/proc/%s/cmdline" % pid
129 fh = open(cmdline_file, 'r')
135 raise errors.HypervisorError("Failed to list instance %s: %s" %
136 (instance_name, err))
143 arg_list = cmdline.split('\x00')
145 arg = arg_list.pop(0)
147 memory = arg_list.pop(0)
149 vcpus = arg_list.pop(0)
151 return (instance_name, pid, memory, vcpus, stat, times)
153 def GetAllInstancesInfo(self):
154 """Get properties of all instances.
157 [(name, id, memory, vcpus, stat, times),...]
160 for name in os.listdir(self._PIDS_DIR):
161 file = "%s/%s" % (self._PIDS_DIR, name)
162 if utils.IsProcessAlive(utils.ReadPidFile(file)):
163 data.append(self.GetInstanceInfo(name))
167 def StartInstance(self, instance, block_devices, extra_args):
168 """Start an instance.
172 pidfile = self._PIDS_DIR + "/%s" % instance.name
173 if utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
174 raise errors.HypervisorError("Failed to start instance %s: %s" %
175 (instance.name, "already running"))
177 kvm = constants.KVM_PATH
179 kvm_cmd.extend(['-m', instance.memory])
180 kvm_cmd.extend(['-smp', instance.vcpus])
181 kvm_cmd.extend(['-pidfile', pidfile])
182 # used just by the vnc server, if enabled
183 kvm_cmd.extend(['-name', instance.name])
184 kvm_cmd.extend(['-daemonize'])
185 if not instance.hvm_acpi:
186 kvm_cmd.extend(['-no-acpi'])
187 if not instance.nics:
188 kvm_cmd.extend(['-net', 'none'])
191 for nic in instance.nics:
192 script = self._WriteNetScript(instance, nic_seq, nic)
193 # FIXME: handle other models
194 nic_val = "nic,macaddr=%s,model=virtio" % nic.mac
195 kvm_cmd.extend(['-net', nic_val])
196 kvm_cmd.extend(['-net', 'tap,script=%s' % script])
197 temp_files.append(script)
201 for cfdev, rldev in block_devices:
202 # TODO: handle FD_LOOP and FD_BLKTAP (?)
204 boot_val = ',boot=on'
209 # TODO: handle different if= types
210 if_val = ',if=virtio'
212 drive_val = 'file=%s,format=raw%s%s' % (rldev.dev_path, if_val, boot_val)
213 kvm_cmd.extend(['-drive', drive_val])
216 if instance.kernel_path in (None, constants.VALUE_DEFAULT):
217 kpath = constants.XEN_KERNEL # FIXME: other name??
219 if not os.path.exists(instance.kernel_path):
220 raise errors.HypervisorError("The kernel %s for instance %s is"
221 " missing" % (instance.kernel_path,
223 kpath = instance.kernel_path
225 kvm_cmd.extend(['-kernel', kpath])
228 if instance.initrd_path in (None, constants.VALUE_DEFAULT):
229 if os.path.exists(constants.XEN_INITRD):
230 initrd_path = constants.XEN_INITRD
233 elif instance.initrd_path == constants.VALUE_NONE:
236 if not os.path.exists(instance.initrd_path):
237 raise errors.HypervisorError("The initrd %s for instance %s is"
238 " missing" % (instance.initrd_path,
240 initrd_path = instance.initrd_path
243 kvm_cmd.extend(['-initrd', initrd_path])
245 kvm_cmd.extend(['-append', 'console=ttyS0,38400 root=/dev/vda'])
248 #"hvm_cdrom_image_path",
250 kvm_cmd.extend(['-nographic'])
251 # FIXME: handle vnc, if needed
252 # How do we decide whether to have it or not?? :(
255 base_control = '%s/%s' % (self._CTRL_DIR, instance.name)
256 monitor_dev = 'unix:%s.monitor,server,nowait' % base_control
257 kvm_cmd.extend(['-monitor', monitor_dev])
258 serial_dev = 'unix:%s.serial,server,nowait' % base_control
259 kvm_cmd.extend(['-serial', serial_dev])
261 result = utils.RunCmd(kvm_cmd)
263 raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
264 (instance.name, result.fail_reason,
267 if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
268 raise errors.HypervisorError("Failed to start instance %s: %s" %
271 for file in temp_files:
272 utils.RemoveFile(file)
274 def StopInstance(self, instance, force=False):
278 pid_file = self._PIDS_DIR + "/%s" % instance.name
279 pid = utils.ReadPidFile(pid_file)
280 if pid > 0 and utils.IsProcessAlive(pid):
281 if force or not instance.hvm_acpi:
282 utils.KillProcess(pid)
284 # This only works if the instance os has acpi support
285 monitor_socket = '%s/%s.monitor' % (self._CTRL_DIR, instance.name)
286 socat = 'socat -u STDIN UNIX-CONNECT:%s' % monitor_socket
287 command = "echo 'system_powerdown' | %s" % socat
288 result = utils.RunCmd(command)
290 raise errors.HypervisorError("Failed to stop instance %s: %s" %
291 (instance.name, result.fail_reason))
293 if not utils.IsProcessAlive(pid):
294 utils.RemoveFile(pid_file)
296 def RebootInstance(self, instance):
297 """Reboot an instance.
300 # For some reason if we do a 'send-key ctrl-alt-delete' to the control
301 # socket the instance will stop, but now power up again. So we'll resort
302 # to shutdown and restart.
303 self.StopInstance(instance)
304 self.StartInstance(instance)
306 def GetNodeInfo(self):
307 """Return information about the node.
309 The return value is a dict, which has to have the following items:
311 - memory_total: the total memory size on the node
312 - memory_free: the available memory on the node for instances
313 - memory_dom0: the memory used by the node itself, if available
316 # global ram usage from the xm info command
319 # note: in xen 3, memory has changed to total_memory
321 fh = file("/proc/meminfo")
323 data = fh.readlines()
327 raise errors.HypervisorError("Failed to list node info: %s" % err)
332 splitfields = line.split(":", 1)
334 if len(splitfields) > 1:
335 key = splitfields[0].strip()
336 val = splitfields[1].strip()
337 if key == 'MemTotal':
338 result['memory_total'] = int(val.split()[0])/1024
339 elif key in ('MemFree', 'Buffers', 'Cached'):
340 sum_free += int(val.split()[0])/1024
341 elif key == 'Active':
342 result['memory_dom0'] = int(val.split()[0])/1024
343 result['memory_free'] = sum_free
347 fh = open("/proc/cpuinfo")
349 cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
353 except EnvironmentError, err:
354 raise errors.HypervisorError("Failed to list node info: %s" % err)
355 result['cpu_total'] = cpu_total
360 def GetShellCommandForConsole(instance):
361 """Return a command for connecting to the console of an instance.
364 # TODO: we can either try the serial socket or suggest vnc
365 return "echo Console not available for the kvm hypervisor yet"
368 """Verify the hypervisor.
370 Check that the binary exists.
373 if not os.path.exists(constants.KVM_PATH):
374 return "The kvm binary ('%s') does not exist." % constants.KVM_PATH