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 import constants
34 from ganeti.errors import HypervisorError
39 VALID_HTYPES = (_HT_XEN30, _HT_FAKE)
42 """Return a Hypervisor instance.
44 This function parses the cluster hypervisor configuration file and
45 instantiates a class based on the value of this file.
48 ht_kind = ssconf.SimpleStore().GetHypervisorType()
49 if ht_kind == _HT_XEN30:
51 elif ht_kind == _HT_FAKE:
54 raise HypervisorError("Unknown hypervisor type '%s'" % ht_kind)
58 class BaseHypervisor(object):
59 """Abstract virtualisation technology interface
61 The goal is that all aspects of the virtualisation technology must
62 be abstracted away from the rest of code.
68 def StartInstance(self, instance, block_devices, extra_args):
69 """Start an instance."""
70 raise NotImplementedError
72 def StopInstance(self, instance, force=False):
73 """Stop an instance."""
74 raise NotImplementedError
76 def RebootInstance(self, instance):
77 """Reboot an instance."""
78 raise NotImplementedError
80 def ListInstances(self):
81 """Get the list of running instances."""
82 raise NotImplementedError
84 def GetInstanceInfo(self, instance_name):
85 """Get instance properties.
88 instance_name: the instance name
91 (name, id, memory, vcpus, state, times)
94 raise NotImplementedError
96 def GetAllInstancesInfo(self):
97 """Get properties of all instances.
100 [(name, id, memory, vcpus, stat, times),...]
102 raise NotImplementedError
104 def GetNodeInfo(self):
105 """Return information about the node.
107 The return value is a dict, which has to have the following items:
109 - memory_total: the total memory size on the node
110 - memory_free: the available memory on the node for instances
111 - memory_dom0: the memory used by the node itself, if available
114 raise NotImplementedError
117 def GetShellCommandForConsole(instance_name):
118 """Return a command for connecting to the console of an instance.
121 raise NotImplementedError
124 """Verify the hypervisor.
127 raise NotImplementedError
130 class XenHypervisor(BaseHypervisor):
131 """Xen hypervisor interface"""
134 def _WriteConfigFile(instance, block_devices, extra_args):
135 """Create a Xen 3.0 config file.
139 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
140 config.write("kernel = '%s'\n" % constants.XEN_KERNEL)
141 if os.path.exists(constants.XEN_INITRD):
142 config.write("ramdisk = '%s'\n" % constants.XEN_INITRD)
143 config.write("memory = %d\n" % instance.memory)
144 config.write("vcpus = %d\n" % instance.vcpus)
145 config.write("name = '%s'\n" % instance.name)
148 for nic in instance.nics:
149 nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
150 ip = getattr(nic, "ip", None)
152 nic_str += ", ip=%s" % ip
153 vif_data.append("'%s'" % nic_str)
155 config.write("vif = [%s]\n" % ",".join(vif_data))
157 disk_data = ["'phy:%s,%s,w'" % (rldev.dev_path, cfdev.iv_name)
158 for cfdev, rldev in block_devices]
159 config.write("disk = [%s]\n" % ",".join(disk_data))
161 config.write("root = '/dev/sda ro'\n")
162 config.write("on_poweroff = 'destroy'\n")
163 config.write("on_reboot = 'restart'\n")
164 config.write("on_crash = 'restart'\n")
166 config.write("extra = '%s'\n" % extra_args)
167 # just in case it exists
168 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
169 f = open("/etc/xen/%s" % instance.name, "w")
170 f.write(config.getvalue())
175 def _RemoveConfigFile(instance):
176 """Remove the xen configuration file.
179 utils.RemoveFile("/etc/xen/%s" % instance.name)
182 def _GetXMList(include_node):
183 """Return the list of running instances.
185 If the `include_node` argument is True, then we return information
186 for dom0 also, otherwise we filter that from the return value.
188 The return value is a list of (name, id, memory, vcpus, state, time spent)
191 for dummy in range(5):
192 result = utils.RunCmd(["xm", "list"])
193 if not result.failed:
195 logger.Error("xm list failed (%s): %s" % (result.fail_reason,
200 raise HypervisorError("xm list failed, retries exceeded (%s): %s" %
201 (result.fail_reason, result.stderr))
203 # skip over the heading and the domain 0 line (optional)
208 lines = result.stdout.splitlines()[to_skip:]
211 # The format of lines is:
212 # Name ID Mem(MiB) VCPUs State Time(s)
213 # Domain-0 0 3418 4 r----- 266.2
216 raise HypervisorError("Can't parse output of xm list, line: %s" % line)
218 data[1] = int(data[1])
219 data[2] = int(data[2])
220 data[3] = int(data[3])
221 data[5] = float(data[5])
222 except ValueError, err:
223 raise HypervisorError("Can't parse output of xm list,"
224 " line: %s, error: %s" % (line, err))
228 def ListInstances(self):
229 """Get the list of running instances.
232 xm_list = self._GetXMList(False)
233 names = [info[0] for info in xm_list]
236 def GetInstanceInfo(self, instance_name):
237 """Get instance properties.
240 instance_name: the instance name
243 (name, id, memory, vcpus, stat, times)
245 xm_list = self._GetXMList(instance_name=="Domain-0")
248 if data[0] == instance_name:
253 def GetAllInstancesInfo(self):
254 """Get properties of all instances.
257 [(name, id, memory, vcpus, stat, times),...]
259 xm_list = self._GetXMList(False)
262 def StartInstance(self, instance, block_devices, extra_args):
263 """Start an instance."""
264 self._WriteConfigFile(instance, block_devices, extra_args)
265 result = utils.RunCmd(["xm", "create", instance.name])
268 raise HypervisorError("Failed to start instance %s: %s" %
269 (instance.name, result.fail_reason))
271 def StopInstance(self, instance, force=False):
272 """Stop an instance."""
273 self._RemoveConfigFile(instance)
275 command = ["xm", "destroy", instance.name]
277 command = ["xm", "shutdown", instance.name]
278 result = utils.RunCmd(command)
281 raise HypervisorError("Failed to stop instance %s: %s" %
282 (instance.name, result.fail_reason))
284 def RebootInstance(self, instance):
285 """Reboot an instance."""
286 result = utils.RunCmd(["xm", "reboot", instance.name])
289 raise HypervisorError("Failed to reboot instance %s: %s" %
290 (instance.name, result.fail_reason))
292 def GetNodeInfo(self):
293 """Return information about the node.
295 The return value is a dict, which has to have the following items:
297 - memory_total: the total memory size on the node
298 - memory_free: the available memory on the node for instances
299 - memory_dom0: the memory used by the node itself, if available
302 # note: in xen 3, memory has changed to total_memory
303 result = utils.RunCmd(["xm", "info"])
305 logger.Error("Can't run 'xm info': %s" % result.fail_reason)
308 xmoutput = result.stdout.splitlines()
310 for line in xmoutput:
311 splitfields = line.split(":", 1)
313 if len(splitfields) > 1:
314 key = splitfields[0].strip()
315 val = splitfields[1].strip()
316 if key == 'memory' or key == 'total_memory':
317 result['memory_total'] = int(val)
318 elif key == 'free_memory':
319 result['memory_free'] = int(val)
320 dom0_info = self.GetInstanceInfo("Domain-0")
321 if dom0_info is not None:
322 result['memory_dom0'] = dom0_info[2]
327 def GetShellCommandForConsole(instance_name):
328 """Return a command for connecting to the console of an instance.
331 return "xm console %s" % instance_name
335 """Verify the hypervisor.
337 For Xen, this verifies that the xend process is running.
340 if not utils.CheckDaemonAlive('/var/run/xend.pid', 'xend'):
341 return "xend daemon is not running"
344 class FakeHypervisor(BaseHypervisor):
345 """Fake hypervisor interface.
347 This can be used for testing the ganeti code without having to have
348 a real virtualisation software installed.
351 _ROOT_DIR = "/var/run/ganeti-fake-hypervisor"
354 BaseHypervisor.__init__(self)
355 if not os.path.exists(self._ROOT_DIR):
356 os.mkdir(self._ROOT_DIR)
358 def ListInstances(self):
359 """Get the list of running instances.
362 return os.listdir(self._ROOT_DIR)
364 def GetInstanceInfo(self, instance_name):
365 """Get instance properties.
368 instance_name: the instance name
371 (name, id, memory, vcpus, stat, times)
373 file_name = "%s/%s" % (self._ROOT_DIR, instance_name)
374 if not os.path.exists(file_name):
377 fh = file(file_name, "r")
379 inst_id = fh.readline().strip()
380 memory = fh.readline().strip()
381 vcpus = fh.readline().strip()
384 return (instance_name, inst_id, memory, vcpus, stat, times)
388 raise HypervisorError("Failed to list instance %s: %s" %
389 (instance_name, err))
391 def GetAllInstancesInfo(self):
392 """Get properties of all instances.
395 [(name, id, memory, vcpus, stat, times),...]
398 for file_name in os.listdir(self._ROOT_DIR):
400 fh = file(self._ROOT_DIR+"/"+file_name, "r")
406 inst_id = fh.readline().strip()
407 memory = fh.readline().strip()
408 vcpus = fh.readline().strip()
413 data.append((file_name, inst_id, memory, vcpus, stat, times))
415 raise HypervisorError("Failed to list instances: %s" % err)
418 def StartInstance(self, instance, force, extra_args):
419 """Start an instance.
421 For the fake hypervisor, it just creates a file in the base dir,
422 creating an exception if it already exists. We don't actually
423 handle race conditions properly, since these are *FAKE* instances.
426 file_name = self._ROOT_DIR + "/%s" % instance.name
427 if os.path.exists(file_name):
428 raise HypervisorError("Failed to start instance %s: %s" %
429 (instance.name, "already running"))
431 fh = file(file_name, "w")
433 fh.write("0\n%d\n%d\n" % (instance.memory, instance.vcpus))
437 raise HypervisorError("Failed to start instance %s: %s" %
438 (instance.name, err))
440 def StopInstance(self, instance, force=False):
443 For the fake hypervisor, this just removes the file in the base
444 dir, if it exist, otherwise we raise an exception.
447 file_name = self._ROOT_DIR + "/%s" % instance.name
448 if not os.path.exists(file_name):
449 raise HypervisorError("Failed to stop instance %s: %s" %
450 (instance.name, "not running"))
451 utils.RemoveFile(file_name)
453 def RebootInstance(self, instance):
454 """Reboot an instance.
456 For the fake hypervisor, this does nothing.
461 def GetNodeInfo(self):
462 """Return information about the node.
464 The return value is a dict, which has to have the following items:
466 - memory_total: the total memory size on the node
467 - memory_free: the available memory on the node for instances
468 - memory_dom0: the memory used by the node itself, if available
471 # global ram usage from the xm info command
474 # note: in xen 3, memory has changed to total_memory
476 fh = file("/proc/meminfo")
478 data = fh.readlines()
482 raise HypervisorError("Failed to list node info: %s" % err)
487 splitfields = line.split(":", 1)
489 if len(splitfields) > 1:
490 key = splitfields[0].strip()
491 val = splitfields[1].strip()
492 if key == 'MemTotal':
493 result['memory_total'] = int(val.split()[0])/1024
494 elif key in ('MemFree', 'Buffers', 'Cached'):
495 sum_free += int(val.split()[0])/1024
496 elif key == 'Active':
497 result['memory_dom0'] = int(val.split()[0])/1024
499 result['memory_free'] = sum_free
503 def GetShellCommandForConsole(instance_name):
504 """Return a command for connecting to the console of an instance.
507 return "echo Console not available for fake hypervisor"
510 """Verify the hypervisor.
512 For the fake hypervisor, it just checks the existence of the base
516 if not os.path.exists(self._ROOT_DIR):
517 return "The required directory '%s' does not exist." % self._ROOT_DIR