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 import errors
35 from ganeti.errors import HypervisorError
39 """Return a Hypervisor instance.
41 This function parses the cluster hypervisor configuration file and
42 instantiates a class based on the value of this file.
45 ht_kind = ssconf.SimpleStore().GetHypervisorType()
46 if ht_kind == constants.HT_XEN_PVM30:
47 cls = XenPvmHypervisor
48 elif ht_kind == constants.HT_FAKE:
50 elif ht_kind == constants.HT_XEN_HVM31:
51 cls = XenHvmHypervisor
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 RebootInstance(self, instance):
76 """Reboot an instance."""
77 raise NotImplementedError
79 def ListInstances(self):
80 """Get the list of running instances."""
81 raise NotImplementedError
83 def GetInstanceInfo(self, instance_name):
84 """Get instance properties.
87 instance_name: the instance name
90 (name, id, memory, vcpus, state, times)
93 raise NotImplementedError
95 def GetAllInstancesInfo(self):
96 """Get properties of all instances.
99 [(name, id, memory, vcpus, stat, times),...]
101 raise NotImplementedError
103 def GetNodeInfo(self):
104 """Return information about the node.
106 The return value is a dict, which has to have the following items:
108 - memory_total: the total memory size on the node
109 - memory_free: the available memory on the node for instances
110 - memory_dom0: the memory used by the node itself, if available
113 raise NotImplementedError
116 def GetShellCommandForConsole(instance):
117 """Return a command for connecting to the console of an instance.
120 raise NotImplementedError
123 """Verify the hypervisor.
126 raise NotImplementedError
129 class XenHypervisor(BaseHypervisor):
130 """Xen generic hypervisor interface
132 This is the Xen base class used for both Xen PVM and HVM. It contains
133 all the functionality that is identical for both.
138 def _WriteConfigFile(instance, block_devices, extra_args):
139 """Write the Xen config file for the instance.
142 raise NotImplementedError
145 def _RemoveConfigFile(instance):
146 """Remove the xen configuration file.
149 utils.RemoveFile("/etc/xen/%s" % instance.name)
152 def _GetXMList(include_node):
153 """Return the list of running instances.
155 If the `include_node` argument is True, then we return information
156 for dom0 also, otherwise we filter that from the return value.
158 The return value is a list of (name, id, memory, vcpus, state, time spent)
161 for dummy in range(5):
162 result = utils.RunCmd(["xm", "list"])
163 if not result.failed:
165 logger.Error("xm list failed (%s): %s" % (result.fail_reason,
170 raise HypervisorError("xm list failed, retries exceeded (%s): %s" %
171 (result.fail_reason, result.stderr))
173 # skip over the heading
174 lines = result.stdout.splitlines()[1:]
177 # The format of lines is:
178 # Name ID Mem(MiB) VCPUs State Time(s)
179 # Domain-0 0 3418 4 r----- 266.2
182 raise HypervisorError("Can't parse output of xm list, line: %s" % line)
184 data[1] = int(data[1])
185 data[2] = int(data[2])
186 data[3] = int(data[3])
187 data[5] = float(data[5])
188 except ValueError, err:
189 raise HypervisorError("Can't parse output of xm list,"
190 " line: %s, error: %s" % (line, err))
192 # skip the Domain-0 (optional)
193 if include_node or data[0] != 'Domain-0':
198 def ListInstances(self):
199 """Get the list of running instances.
202 xm_list = self._GetXMList(False)
203 names = [info[0] for info in xm_list]
206 def GetInstanceInfo(self, instance_name):
207 """Get instance properties.
210 instance_name: the instance name
213 (name, id, memory, vcpus, stat, times)
215 xm_list = self._GetXMList(instance_name=="Domain-0")
218 if data[0] == instance_name:
223 def GetAllInstancesInfo(self):
224 """Get properties of all instances.
227 [(name, id, memory, vcpus, stat, times),...]
229 xm_list = self._GetXMList(False)
232 def StartInstance(self, instance, block_devices, extra_args):
233 """Start an instance."""
234 self._WriteConfigFile(instance, block_devices, extra_args)
235 result = utils.RunCmd(["xm", "create", instance.name])
238 raise HypervisorError("Failed to start instance %s: %s (%s)" %
239 (instance.name, result.fail_reason, result.output))
241 def StopInstance(self, instance, force=False):
242 """Stop an instance."""
243 self._RemoveConfigFile(instance)
245 command = ["xm", "destroy", instance.name]
247 command = ["xm", "shutdown", instance.name]
248 result = utils.RunCmd(command)
251 raise HypervisorError("Failed to stop instance %s: %s" %
252 (instance.name, result.fail_reason))
254 def RebootInstance(self, instance):
255 """Reboot an instance."""
256 result = utils.RunCmd(["xm", "reboot", instance.name])
259 raise HypervisorError("Failed to reboot instance %s: %s" %
260 (instance.name, result.fail_reason))
262 def GetNodeInfo(self):
263 """Return information about the node.
265 The return value is a dict, which has to have the following items:
267 - memory_total: the total memory size on the node
268 - memory_free: the available memory on the node for instances
269 - memory_dom0: the memory used by the node itself, if available
272 # note: in xen 3, memory has changed to total_memory
273 result = utils.RunCmd(["xm", "info"])
275 logger.Error("Can't run 'xm info': %s" % result.fail_reason)
278 xmoutput = result.stdout.splitlines()
280 for line in xmoutput:
281 splitfields = line.split(":", 1)
283 if len(splitfields) > 1:
284 key = splitfields[0].strip()
285 val = splitfields[1].strip()
286 if key == 'memory' or key == 'total_memory':
287 result['memory_total'] = int(val)
288 elif key == 'free_memory':
289 result['memory_free'] = int(val)
290 dom0_info = self.GetInstanceInfo("Domain-0")
291 if dom0_info is not None:
292 result['memory_dom0'] = dom0_info[2]
297 def GetShellCommandForConsole(instance):
298 """Return a command for connecting to the console of an instance.
301 raise NotImplementedError
305 """Verify the hypervisor.
307 For Xen, this verifies that the xend process is running.
310 if not utils.CheckDaemonAlive('/var/run/xend.pid', 'xend'):
311 return "xend daemon is not running"
314 class XenPvmHypervisor(XenHypervisor):
315 """Xen PVM hypervisor interface"""
318 def _WriteConfigFile(instance, block_devices, extra_args):
319 """Write the Xen config file for the instance.
323 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
326 if instance.kernel_path in (None, constants.VALUE_DEFAULT):
327 kpath = constants.XEN_KERNEL
329 if not os.path.exists(instance.kernel_path):
330 raise errors.HypervisorError("The kernel %s for instance %s is"
331 " missing" % (instance.kernel_path,
333 kpath = instance.kernel_path
334 config.write("kernel = '%s'\n" % kpath)
337 if instance.initrd_path in (None, constants.VALUE_DEFAULT):
338 if os.path.exists(constants.XEN_INITRD):
339 initrd_path = constants.XEN_INITRD
342 elif instance.initrd_path == constants.VALUE_NONE:
345 if not os.path.exists(instance.initrd_path):
346 raise errors.HypervisorError("The initrd %s for instance %s is"
347 " missing" % (instance.initrd_path,
349 initrd_path = instance.initrd_path
352 config.write("ramdisk = '%s'\n" % initrd_path)
354 # rest of the settings
355 config.write("memory = %d\n" % instance.memory)
356 config.write("vcpus = %d\n" % instance.vcpus)
357 config.write("name = '%s'\n" % instance.name)
360 for nic in instance.nics:
361 nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
362 ip = getattr(nic, "ip", None)
364 nic_str += ", ip=%s" % ip
365 vif_data.append("'%s'" % nic_str)
367 config.write("vif = [%s]\n" % ",".join(vif_data))
369 disk_data = ["'phy:%s,%s,w'" % (rldev.dev_path, cfdev.iv_name)
370 for cfdev, rldev in block_devices]
371 config.write("disk = [%s]\n" % ",".join(disk_data))
373 config.write("root = '/dev/sda ro'\n")
374 config.write("on_poweroff = 'destroy'\n")
375 config.write("on_reboot = 'restart'\n")
376 config.write("on_crash = 'restart'\n")
378 config.write("extra = '%s'\n" % extra_args)
379 # just in case it exists
380 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
382 f = open("/etc/xen/%s" % instance.name, "w")
384 f.write(config.getvalue())
388 raise errors.OpExecError("Cannot write Xen instance confile"
389 " file /etc/xen/%s: %s" % (instance.name, err))
393 def GetShellCommandForConsole(instance):
394 """Return a command for connecting to the console of an instance.
397 return "xm console %s" % instance.name
400 class FakeHypervisor(BaseHypervisor):
401 """Fake hypervisor interface.
403 This can be used for testing the ganeti code without having to have
404 a real virtualisation software installed.
407 _ROOT_DIR = constants.RUN_DIR + "/ganeti-fake-hypervisor"
410 BaseHypervisor.__init__(self)
411 if not os.path.exists(self._ROOT_DIR):
412 os.mkdir(self._ROOT_DIR)
414 def ListInstances(self):
415 """Get the list of running instances.
418 return os.listdir(self._ROOT_DIR)
420 def GetInstanceInfo(self, instance_name):
421 """Get instance properties.
424 instance_name: the instance name
427 (name, id, memory, vcpus, stat, times)
429 file_name = "%s/%s" % (self._ROOT_DIR, instance_name)
430 if not os.path.exists(file_name):
433 fh = file(file_name, "r")
435 inst_id = fh.readline().strip()
436 memory = fh.readline().strip()
437 vcpus = fh.readline().strip()
440 return (instance_name, inst_id, memory, vcpus, stat, times)
444 raise HypervisorError("Failed to list instance %s: %s" %
445 (instance_name, err))
447 def GetAllInstancesInfo(self):
448 """Get properties of all instances.
451 [(name, id, memory, vcpus, stat, times),...]
454 for file_name in os.listdir(self._ROOT_DIR):
456 fh = file(self._ROOT_DIR+"/"+file_name, "r")
462 inst_id = fh.readline().strip()
463 memory = fh.readline().strip()
464 vcpus = fh.readline().strip()
469 data.append((file_name, inst_id, memory, vcpus, stat, times))
471 raise HypervisorError("Failed to list instances: %s" % err)
474 def StartInstance(self, instance, force, extra_args):
475 """Start an instance.
477 For the fake hypervisor, it just creates a file in the base dir,
478 creating an exception if it already exists. We don't actually
479 handle race conditions properly, since these are *FAKE* instances.
482 file_name = self._ROOT_DIR + "/%s" % instance.name
483 if os.path.exists(file_name):
484 raise HypervisorError("Failed to start instance %s: %s" %
485 (instance.name, "already running"))
487 fh = file(file_name, "w")
489 fh.write("0\n%d\n%d\n" % (instance.memory, instance.vcpus))
493 raise HypervisorError("Failed to start instance %s: %s" %
494 (instance.name, err))
496 def StopInstance(self, instance, force=False):
499 For the fake hypervisor, this just removes the file in the base
500 dir, if it exist, otherwise we raise an exception.
503 file_name = self._ROOT_DIR + "/%s" % instance.name
504 if not os.path.exists(file_name):
505 raise HypervisorError("Failed to stop instance %s: %s" %
506 (instance.name, "not running"))
507 utils.RemoveFile(file_name)
509 def RebootInstance(self, instance):
510 """Reboot an instance.
512 For the fake hypervisor, this does nothing.
517 def GetNodeInfo(self):
518 """Return information about the node.
520 The return value is a dict, which has to have the following items:
522 - memory_total: the total memory size on the node
523 - memory_free: the available memory on the node for instances
524 - memory_dom0: the memory used by the node itself, if available
527 # global ram usage from the xm info command
530 # note: in xen 3, memory has changed to total_memory
532 fh = file("/proc/meminfo")
534 data = fh.readlines()
538 raise HypervisorError("Failed to list node info: %s" % err)
543 splitfields = line.split(":", 1)
545 if len(splitfields) > 1:
546 key = splitfields[0].strip()
547 val = splitfields[1].strip()
548 if key == 'MemTotal':
549 result['memory_total'] = int(val.split()[0])/1024
550 elif key in ('MemFree', 'Buffers', 'Cached'):
551 sum_free += int(val.split()[0])/1024
552 elif key == 'Active':
553 result['memory_dom0'] = int(val.split()[0])/1024
555 result['memory_free'] = sum_free
559 def GetShellCommandForConsole(instance):
560 """Return a command for connecting to the console of an instance.
563 return "echo Console not available for fake hypervisor"
566 """Verify the hypervisor.
568 For the fake hypervisor, it just checks the existence of the base
572 if not os.path.exists(self._ROOT_DIR):
573 return "The required directory '%s' does not exist." % self._ROOT_DIR
576 class XenHvmHypervisor(XenHypervisor):
577 """Xen HVM hypervisor interface"""
580 def _WriteConfigFile(instance, block_devices, extra_args):
581 """Create a Xen 3.1 HVM config file.
585 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
586 config.write("kernel = '/usr/lib/xen/boot/hvmloader'\n")
587 config.write("builder = 'hvm'\n")
588 config.write("memory = %d\n" % instance.memory)
589 config.write("vcpus = %d\n" % instance.vcpus)
590 config.write("name = '%s'\n" % instance.name)
591 config.write("pae = 1\n")
592 config.write("acpi = 1\n")
593 config.write("apic = 1\n")
596 config.write("device_model = '/usr/lib64/xen/bin/qemu-dm'\n")
598 config.write("device_model = '/usr/lib/xen/bin/qemu-dm'\n")
599 if instance.hvm_boot_order is None:
600 config.write("boot = '%s'\n" % constants.HT_HVM_DEFAULT_BOOT_ORDER)
602 config.write("boot = '%s'\n" % instance.hvm_boot_order)
603 config.write("sdl = 0\n")
604 config.write("usb = 1\n");
605 config.write("usbdevice = 'tablet'\n");
606 config.write("vnc = 1\n")
607 config.write("vnclisten = '0.0.0.0'\n")
609 if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
610 display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
611 config.write("vncdisplay = %s\n" % display)
612 config.write("vncunused = 0\n")
614 config.write("# vncdisplay = 1\n")
615 config.write("vncunused = 1\n")
618 password_file = open(constants.VNC_PASSWORD_FILE, "r")
620 password = password_file.readline()
622 password_file.close()
624 raise errors.OpExecError("failed to open VNC password file %s " %
625 constants.VNC_PASSWORD_FILE)
627 config.write("vncpasswd = '%s'\n" % password.rstrip())
629 config.write("serial = 'pty'\n")
630 config.write("localtime = 1\n")
633 for nic in instance.nics:
634 nic_str = "mac=%s, bridge=%s, type=ioemu" % (nic.mac, nic.bridge)
635 ip = getattr(nic, "ip", None)
637 nic_str += ", ip=%s" % ip
638 vif_data.append("'%s'" % nic_str)
640 config.write("vif = [%s]\n" % ",".join(vif_data))
642 disk_data = ["'phy:%s,%s,w'" %
643 (rldev.dev_path, cfdev.iv_name.replace("sd", "ioemu:hd"))
644 for cfdev, rldev in block_devices]
645 iso = "'file:/srv/ganeti/iso/hvm-install.iso,hdc:cdrom,r'"
646 config.write("disk = [%s, %s]\n" % (",".join(disk_data), iso) )
648 config.write("on_poweroff = 'destroy'\n")
649 config.write("on_reboot = 'restart'\n")
650 config.write("on_crash = 'restart'\n")
652 config.write("extra = '%s'\n" % extra_args)
653 # just in case it exists
654 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
656 f = open("/etc/xen/%s" % instance.name, "w")
658 f.write(config.getvalue())
662 raise errors.OpExecError("Cannot write Xen instance confile"
663 " file /etc/xen/%s: %s" % (instance.name, err))
667 def GetShellCommandForConsole(instance):
668 """Return a command for connecting to the console of an instance.
671 if instance.network_port is None:
672 raise errors.OpExecError("no console port defined for %s"
675 raise errors.OpExecError("no PTY console, connect to %s:%s via VNC"
676 % (instance.primary_node,
677 instance.network_port))