4 # Copyright (C) 2006, 2007 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
22 """Module that abstracts the virtualisation interface
28 from cStringIO import StringIO
30 from ganeti import utils
31 from ganeti import logger
32 from ganeti import ssconf
33 from ganeti.errors import HypervisorError
38 VALID_HTYPES = (_HT_XEN30, _HT_FAKE)
41 """Return a Hypervisor instance.
43 This function parses the cluster hypervisor configuration file and
44 instantiates a class based on the value of this file.
47 ht_kind = ssconf.SimpleStore().GetHypervisorType()
48 if ht_kind == _HT_XEN30:
50 elif ht_kind == _HT_FAKE:
53 raise HypervisorError("Unknown hypervisor type '%s'" % ht_kind)
57 class BaseHypervisor(object):
58 """Abstract virtualisation technology interface
60 The goal is that all aspects of the virtualisation technology must
61 be abstracted away from the rest of code.
67 def StartInstance(self, instance, block_devices, extra_args):
68 """Start an instance."""
69 raise NotImplementedError
71 def StopInstance(self, instance, force=False):
72 """Stop an instance."""
73 raise NotImplementedError
75 def ListInstances(self):
76 """Get the list of running instances."""
77 raise NotImplementedError
79 def GetInstanceInfo(self, instance_name):
80 """Get instance properties.
83 instance_name: the instance name
86 (name, id, memory, vcpus, state, times)
89 raise NotImplementedError
91 def GetAllInstancesInfo(self):
92 """Get properties of all instances.
95 [(name, id, memory, vcpus, stat, times),...]
97 raise NotImplementedError
99 def GetNodeInfo(self):
100 """Return information about the node.
102 The return value is a dict, which has to have the following items:
104 - memory_total: the total memory size on the node
105 - memory_free: the available memory on the node for instances
106 - memory_dom0: the memory used by the node itself, if available
109 raise NotImplementedError
112 def GetShellCommandForConsole(instance_name):
113 """Return a command for connecting to the console of an instance.
116 raise NotImplementedError
119 """Verify the hypervisor.
122 raise NotImplementedError
125 class XenHypervisor(BaseHypervisor):
126 """Xen hypervisor interface"""
129 def _WriteConfigFile(instance, block_devices, extra_args):
130 """Create a Xen 3.0 config file.
134 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
135 config.write("kernel = '/boot/vmlinuz-2.6-xenU'\n")
136 if os.path.exists("/boot/initrd-2.6-xenU"):
137 config.write("ramdisk = '/boot/initrd-2.6-xenU'\n")
138 config.write("memory = %d\n" % instance.memory)
139 config.write("vcpus = %d\n" % instance.vcpus)
140 config.write("name = '%s'\n" % instance.name)
143 for nic in instance.nics:
144 nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
145 ip = getattr(nic, "ip", None)
147 nic_str += ", ip=%s" % ip
148 vif_data.append("'%s'" % nic_str)
150 config.write("vif = [%s]\n" % ",".join(vif_data))
152 disk_data = ["'phy:%s,%s,w'" % (rldev.dev_path, cfdev.iv_name)
153 for cfdev, rldev in block_devices]
154 config.write("disk = [%s]\n" % ",".join(disk_data))
156 config.write("root = '/dev/sda ro'\n")
157 config.write("on_poweroff = 'destroy'\n")
158 config.write("on_reboot = 'restart'\n")
159 config.write("on_crash = 'restart'\n")
161 config.write("extra = '%s'\n" % extra_args)
162 # just in case it exists
163 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
164 f = open("/etc/xen/%s" % instance.name, "w")
165 f.write(config.getvalue())
170 def _RemoveConfigFile(instance):
171 """Remove the xen configuration file.
174 utils.RemoveFile("/etc/xen/%s" % instance.name)
177 def _GetXMList(include_node):
178 """Return the list of running instances.
180 If the `include_node` argument is True, then we return information
181 for dom0 also, otherwise we filter that from the return value.
183 The return value is a list of (name, id, memory, vcpus, state, time spent)
186 for dummy in range(5):
187 result = utils.RunCmd(["xm", "list"])
188 if not result.failed:
190 logger.Error("xm list failed (%s): %s" % (result.fail_reason,
195 raise HypervisorError("xm list failed, retries exceeded (%s): %s" %
196 (result.fail_reason, result.stderr))
198 # skip over the heading and the domain 0 line (optional)
203 lines = result.stdout.splitlines()[to_skip:]
206 # The format of lines is:
207 # Name ID Mem(MiB) VCPUs State Time(s)
208 # Domain-0 0 3418 4 r----- 266.2
211 raise HypervisorError("Can't parse output of xm list, line: %s" % line)
213 data[1] = int(data[1])
214 data[2] = int(data[2])
215 data[3] = int(data[3])
216 data[5] = float(data[5])
217 except ValueError, err:
218 raise HypervisorError("Can't parse output of xm list,"
219 " line: %s, error: %s" % (line, err))
223 def ListInstances(self):
224 """Get the list of running instances.
227 xm_list = self._GetXMList(False)
228 names = [info[0] for info in xm_list]
231 def GetInstanceInfo(self, instance_name):
232 """Get instance properties.
235 instance_name: the instance name
238 (name, id, memory, vcpus, stat, times)
240 xm_list = self._GetXMList(instance_name=="Domain-0")
243 if data[0] == instance_name:
248 def GetAllInstancesInfo(self):
249 """Get properties of all instances.
252 [(name, id, memory, vcpus, stat, times),...]
254 xm_list = self._GetXMList(False)
257 def StartInstance(self, instance, block_devices, extra_args):
258 """Start an instance."""
259 self._WriteConfigFile(instance, block_devices, extra_args)
260 result = utils.RunCmd(["xm", "create", instance.name])
263 raise HypervisorError("Failed to start instance %s: %s" %
264 (instance.name, result.fail_reason))
266 def StopInstance(self, instance, force=False):
267 """Stop an instance."""
268 self._RemoveConfigFile(instance)
270 command = ["xm", "destroy", instance.name]
272 command = ["xm", "shutdown", instance.name]
273 result = utils.RunCmd(command)
276 raise HypervisorError("Failed to stop instance %s: %s" %
277 (instance.name, result.fail_reason))
279 def GetNodeInfo(self):
280 """Return information about the node.
282 The return value is a dict, which has to have the following items:
284 - memory_total: the total memory size on the node
285 - memory_free: the available memory on the node for instances
286 - memory_dom0: the memory used by the node itself, if available
289 # note: in xen 3, memory has changed to total_memory
290 result = utils.RunCmd(["xm", "info"])
292 logger.Error("Can't run 'xm info': %s" % result.fail_reason)
295 xmoutput = result.stdout.splitlines()
297 for line in xmoutput:
298 splitfields = line.split(":", 1)
300 if len(splitfields) > 1:
301 key = splitfields[0].strip()
302 val = splitfields[1].strip()
303 if key == 'memory' or key == 'total_memory':
304 result['memory_total'] = int(val)
305 elif key == 'free_memory':
306 result['memory_free'] = int(val)
307 dom0_info = self.GetInstanceInfo("Domain-0")
308 if dom0_info is not None:
309 result['memory_dom0'] = dom0_info[2]
314 def GetShellCommandForConsole(instance_name):
315 """Return a command for connecting to the console of an instance.
318 return "xm console %s" % instance_name
322 """Verify the hypervisor.
324 For Xen, this verifies that the xend process is running.
327 if not utils.CheckDaemonAlive('/var/run/xend.pid', 'xend'):
328 return "xend daemon is not running"
331 class FakeHypervisor(BaseHypervisor):
332 """Fake hypervisor interface.
334 This can be used for testing the ganeti code without having to have
335 a real virtualisation software installed.
338 _ROOT_DIR = "/var/run/ganeti-fake-hypervisor"
341 BaseHypervisor.__init__(self)
342 if not os.path.exists(self._ROOT_DIR):
343 os.mkdir(self._ROOT_DIR)
345 def ListInstances(self):
346 """Get the list of running instances.
349 return os.listdir(self._ROOT_DIR)
351 def GetInstanceInfo(self, instance_name):
352 """Get instance properties.
355 instance_name: the instance name
358 (name, id, memory, vcpus, stat, times)
360 file_name = "%s/%s" % (self._ROOT_DIR, instance_name)
361 if not os.path.exists(file_name):
364 fh = file(file_name, "r")
366 inst_id = fh.readline().strip()
367 memory = fh.readline().strip()
368 vcpus = fh.readline().strip()
371 return (instance_name, inst_id, memory, vcpus, stat, times)
375 raise HypervisorError("Failed to list instance %s: %s" %
376 (instance_name, err))
378 def GetAllInstancesInfo(self):
379 """Get properties of all instances.
382 [(name, id, memory, vcpus, stat, times),...]
385 for file_name in os.listdir(self._ROOT_DIR):
387 fh = file(self._ROOT_DIR+"/"+file_name, "r")
393 inst_id = fh.readline().strip()
394 memory = fh.readline().strip()
395 vcpus = fh.readline().strip()
400 data.append((file_name, inst_id, memory, vcpus, stat, times))
402 raise HypervisorError("Failed to list instances: %s" % err)
405 def StartInstance(self, instance, force, extra_args):
406 """Start an instance.
408 For the fake hypervisor, it just creates a file in the base dir,
409 creating an exception if it already exists. We don't actually
410 handle race conditions properly, since these are *FAKE* instances.
413 file_name = self._ROOT_DIR + "/%s" % instance.name
414 if os.path.exists(file_name):
415 raise HypervisorError("Failed to start instance %s: %s" %
416 (instance.name, "already running"))
418 fh = file(file_name, "w")
420 fh.write("0\n%d\n%d\n" % (instance.memory, instance.vcpus))
424 raise HypervisorError("Failed to start instance %s: %s" %
425 (instance.name, err))
427 def StopInstance(self, instance, force=False):
430 For the fake hypervisor, this just removes the file in the base
431 dir, if it exist, otherwise we raise an exception.
434 file_name = self._ROOT_DIR + "/%s" % instance.name
435 if not os.path.exists(file_name):
436 raise HypervisorError("Failed to stop instance %s: %s" %
437 (instance.name, "not running"))
438 utils.RemoveFile(file_name)
440 def GetNodeInfo(self):
441 """Return information about the node.
443 The return value is a dict, which has to have the following items:
445 - memory_total: the total memory size on the node
446 - memory_free: the available memory on the node for instances
447 - memory_dom0: the memory used by the node itself, if available
450 # global ram usage from the xm info command
453 # note: in xen 3, memory has changed to total_memory
455 fh = file("/proc/meminfo")
457 data = fh.readlines()
461 raise HypervisorError("Failed to list node info: %s" % err)
466 splitfields = line.split(":", 1)
468 if len(splitfields) > 1:
469 key = splitfields[0].strip()
470 val = splitfields[1].strip()
471 if key == 'MemTotal':
472 result['memory_total'] = int(val.split()[0])/1024
473 elif key in ('MemFree', 'Buffers', 'Cached'):
474 sum_free += int(val.split()[0])/1024
475 elif key == 'Active':
476 result['memory_dom0'] = int(val.split()[0])/1024
478 result['memory_free'] = sum_free
482 def GetShellCommandForConsole(instance_name):
483 """Return a command for connecting to the console of an instance.
486 return "echo Console not available for fake hypervisor"
489 """Verify the hypervisor.
491 For the fake hypervisor, it just checks the existence of the base
495 if not os.path.exists(self._ROOT_DIR):
496 return "The required directory '%s' does not exist." % self._ROOT_DIR