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
29 from cStringIO import StringIO
31 from ganeti import utils
32 from ganeti import logger
33 from ganeti import ssconf
34 from ganeti import constants
35 from ganeti import errors
36 from ganeti.errors import HypervisorError
40 """Return a Hypervisor instance.
42 This function parses the cluster hypervisor configuration file and
43 instantiates a class based on the value of this file.
46 ht_kind = ssconf.SimpleStore().GetHypervisorType()
47 if ht_kind == constants.HT_XEN_PVM30:
48 cls = XenPvmHypervisor
49 elif ht_kind == constants.HT_FAKE:
51 elif ht_kind == constants.HT_XEN_HVM31:
52 cls = XenHvmHypervisor
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):
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 generic hypervisor interface
133 This is the Xen base class used for both Xen PVM and HVM. It contains
134 all the functionality that is identical for both.
139 def _WriteConfigFile(instance, block_devices, extra_args):
140 """Write the Xen config file for the instance.
143 raise NotImplementedError
146 def _RemoveConfigFile(instance):
147 """Remove the xen configuration file.
150 utils.RemoveFile("/etc/xen/%s" % instance.name)
153 def _GetXMList(include_node):
154 """Return the list of running instances.
156 If the `include_node` argument is True, then we return information
157 for dom0 also, otherwise we filter that from the return value.
159 The return value is a list of (name, id, memory, vcpus, state, time spent)
162 for dummy in range(5):
163 result = utils.RunCmd(["xm", "list"])
164 if not result.failed:
166 logger.Error("xm list failed (%s): %s" % (result.fail_reason,
171 raise HypervisorError("xm list failed, retries exceeded (%s): %s" %
172 (result.fail_reason, result.stderr))
174 # skip over the heading
175 lines = result.stdout.splitlines()[1:]
178 # The format of lines is:
179 # Name ID Mem(MiB) VCPUs State Time(s)
180 # Domain-0 0 3418 4 r----- 266.2
183 raise HypervisorError("Can't parse output of xm list, line: %s" % line)
185 data[1] = int(data[1])
186 data[2] = int(data[2])
187 data[3] = int(data[3])
188 data[5] = float(data[5])
189 except ValueError, err:
190 raise HypervisorError("Can't parse output of xm list,"
191 " line: %s, error: %s" % (line, err))
193 # skip the Domain-0 (optional)
194 if include_node or data[0] != 'Domain-0':
199 def ListInstances(self):
200 """Get the list of running instances.
203 xm_list = self._GetXMList(False)
204 names = [info[0] for info in xm_list]
207 def GetInstanceInfo(self, instance_name):
208 """Get instance properties.
211 instance_name: the instance name
214 (name, id, memory, vcpus, stat, times)
216 xm_list = self._GetXMList(instance_name=="Domain-0")
219 if data[0] == instance_name:
224 def GetAllInstancesInfo(self):
225 """Get properties of all instances.
228 [(name, id, memory, vcpus, stat, times),...]
230 xm_list = self._GetXMList(False)
233 def StartInstance(self, instance, block_devices, extra_args):
234 """Start an instance."""
235 self._WriteConfigFile(instance, block_devices, extra_args)
236 result = utils.RunCmd(["xm", "create", instance.name])
239 raise HypervisorError("Failed to start instance %s: %s (%s)" %
240 (instance.name, result.fail_reason, result.output))
242 def StopInstance(self, instance, force=False):
243 """Stop an instance."""
244 self._RemoveConfigFile(instance)
246 command = ["xm", "destroy", instance.name]
248 command = ["xm", "shutdown", instance.name]
249 result = utils.RunCmd(command)
252 raise HypervisorError("Failed to stop instance %s: %s" %
253 (instance.name, result.fail_reason))
255 def RebootInstance(self, instance):
256 """Reboot an instance."""
257 result = utils.RunCmd(["xm", "reboot", instance.name])
260 raise HypervisorError("Failed to reboot instance %s: %s" %
261 (instance.name, result.fail_reason))
263 def GetNodeInfo(self):
264 """Return information about the node.
266 The return value is a dict, which has to have the following items:
268 - memory_total: the total memory size on the node
269 - memory_free: the available memory on the node for instances
270 - memory_dom0: the memory used by the node itself, if available
273 # note: in xen 3, memory has changed to total_memory
274 result = utils.RunCmd(["xm", "info"])
276 logger.Error("Can't run 'xm info': %s" % result.fail_reason)
279 xmoutput = result.stdout.splitlines()
281 for line in xmoutput:
282 splitfields = line.split(":", 1)
284 if len(splitfields) > 1:
285 key = splitfields[0].strip()
286 val = splitfields[1].strip()
287 if key == 'memory' or key == 'total_memory':
288 result['memory_total'] = int(val)
289 elif key == 'free_memory':
290 result['memory_free'] = int(val)
291 elif key == 'nr_cpus':
292 result['cpu_total'] = int(val)
293 dom0_info = self.GetInstanceInfo("Domain-0")
294 if dom0_info is not None:
295 result['memory_dom0'] = dom0_info[2]
300 def GetShellCommandForConsole(instance):
301 """Return a command for connecting to the console of an instance.
304 raise NotImplementedError
308 """Verify the hypervisor.
310 For Xen, this verifies that the xend process is running.
313 if not utils.CheckDaemonAlive('/var/run/xend.pid', 'xend'):
314 return "xend daemon is not running"
317 class XenPvmHypervisor(XenHypervisor):
318 """Xen PVM hypervisor interface"""
321 def _WriteConfigFile(instance, block_devices, extra_args):
322 """Write the Xen config file for the instance.
326 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
329 if instance.kernel_path in (None, constants.VALUE_DEFAULT):
330 kpath = constants.XEN_KERNEL
332 if not os.path.exists(instance.kernel_path):
333 raise errors.HypervisorError("The kernel %s for instance %s is"
334 " missing" % (instance.kernel_path,
336 kpath = instance.kernel_path
337 config.write("kernel = '%s'\n" % kpath)
340 if instance.initrd_path in (None, constants.VALUE_DEFAULT):
341 if os.path.exists(constants.XEN_INITRD):
342 initrd_path = constants.XEN_INITRD
345 elif instance.initrd_path == constants.VALUE_NONE:
348 if not os.path.exists(instance.initrd_path):
349 raise errors.HypervisorError("The initrd %s for instance %s is"
350 " missing" % (instance.initrd_path,
352 initrd_path = instance.initrd_path
355 config.write("ramdisk = '%s'\n" % initrd_path)
357 # rest of the settings
358 config.write("memory = %d\n" % instance.memory)
359 config.write("vcpus = %d\n" % instance.vcpus)
360 config.write("name = '%s'\n" % instance.name)
363 for nic in instance.nics:
364 nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
365 ip = getattr(nic, "ip", None)
367 nic_str += ", ip=%s" % ip
368 vif_data.append("'%s'" % nic_str)
370 config.write("vif = [%s]\n" % ",".join(vif_data))
372 disk_data = ["'phy:%s,%s,w'" % (rldev.dev_path, cfdev.iv_name)
373 for cfdev, rldev in block_devices]
374 config.write("disk = [%s]\n" % ",".join(disk_data))
376 config.write("root = '/dev/sda ro'\n")
377 config.write("on_poweroff = 'destroy'\n")
378 config.write("on_reboot = 'restart'\n")
379 config.write("on_crash = 'restart'\n")
381 config.write("extra = '%s'\n" % extra_args)
382 # just in case it exists
383 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
385 f = open("/etc/xen/%s" % instance.name, "w")
387 f.write(config.getvalue())
391 raise errors.OpExecError("Cannot write Xen instance confile"
392 " file /etc/xen/%s: %s" % (instance.name, err))
396 def GetShellCommandForConsole(instance):
397 """Return a command for connecting to the console of an instance.
400 return "xm console %s" % instance.name
403 class FakeHypervisor(BaseHypervisor):
404 """Fake hypervisor interface.
406 This can be used for testing the ganeti code without having to have
407 a real virtualisation software installed.
410 _ROOT_DIR = constants.RUN_DIR + "/ganeti-fake-hypervisor"
413 BaseHypervisor.__init__(self)
414 if not os.path.exists(self._ROOT_DIR):
415 os.mkdir(self._ROOT_DIR)
417 def ListInstances(self):
418 """Get the list of running instances.
421 return os.listdir(self._ROOT_DIR)
423 def GetInstanceInfo(self, instance_name):
424 """Get instance properties.
427 instance_name: the instance name
430 (name, id, memory, vcpus, stat, times)
432 file_name = "%s/%s" % (self._ROOT_DIR, instance_name)
433 if not os.path.exists(file_name):
436 fh = file(file_name, "r")
438 inst_id = fh.readline().strip()
439 memory = fh.readline().strip()
440 vcpus = fh.readline().strip()
443 return (instance_name, inst_id, memory, vcpus, stat, times)
447 raise HypervisorError("Failed to list instance %s: %s" %
448 (instance_name, err))
450 def GetAllInstancesInfo(self):
451 """Get properties of all instances.
454 [(name, id, memory, vcpus, stat, times),...]
457 for file_name in os.listdir(self._ROOT_DIR):
459 fh = file(self._ROOT_DIR+"/"+file_name, "r")
465 inst_id = fh.readline().strip()
466 memory = fh.readline().strip()
467 vcpus = fh.readline().strip()
472 data.append((file_name, inst_id, memory, vcpus, stat, times))
474 raise HypervisorError("Failed to list instances: %s" % err)
477 def StartInstance(self, instance, force, extra_args):
478 """Start an instance.
480 For the fake hypervisor, it just creates a file in the base dir,
481 creating an exception if it already exists. We don't actually
482 handle race conditions properly, since these are *FAKE* instances.
485 file_name = self._ROOT_DIR + "/%s" % instance.name
486 if os.path.exists(file_name):
487 raise HypervisorError("Failed to start instance %s: %s" %
488 (instance.name, "already running"))
490 fh = file(file_name, "w")
492 fh.write("0\n%d\n%d\n" % (instance.memory, instance.vcpus))
496 raise HypervisorError("Failed to start instance %s: %s" %
497 (instance.name, err))
499 def StopInstance(self, instance, force=False):
502 For the fake hypervisor, this just removes the file in the base
503 dir, if it exist, otherwise we raise an exception.
506 file_name = self._ROOT_DIR + "/%s" % instance.name
507 if not os.path.exists(file_name):
508 raise HypervisorError("Failed to stop instance %s: %s" %
509 (instance.name, "not running"))
510 utils.RemoveFile(file_name)
512 def RebootInstance(self, instance):
513 """Reboot an instance.
515 For the fake hypervisor, this does nothing.
520 def GetNodeInfo(self):
521 """Return information about the node.
523 The return value is a dict, which has to have the following items:
525 - memory_total: the total memory size on the node
526 - memory_free: the available memory on the node for instances
527 - memory_dom0: the memory used by the node itself, if available
530 # global ram usage from the xm info command
533 # note: in xen 3, memory has changed to total_memory
535 fh = file("/proc/meminfo")
537 data = fh.readlines()
541 raise HypervisorError("Failed to list node info: %s" % err)
546 splitfields = line.split(":", 1)
548 if len(splitfields) > 1:
549 key = splitfields[0].strip()
550 val = splitfields[1].strip()
551 if key == 'MemTotal':
552 result['memory_total'] = int(val.split()[0])/1024
553 elif key in ('MemFree', 'Buffers', 'Cached'):
554 sum_free += int(val.split()[0])/1024
555 elif key == 'Active':
556 result['memory_dom0'] = int(val.split()[0])/1024
557 result['memory_free'] = sum_free
561 fh = open("/proc/cpuinfo")
563 cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
567 except EnvironmentError, err:
568 raise HypervisorError("Failed to list node info: %s" % err)
569 result['cpu_total'] = cpu_total
574 def GetShellCommandForConsole(instance):
575 """Return a command for connecting to the console of an instance.
578 return "echo Console not available for fake hypervisor"
581 """Verify the hypervisor.
583 For the fake hypervisor, it just checks the existence of the base
587 if not os.path.exists(self._ROOT_DIR):
588 return "The required directory '%s' does not exist." % self._ROOT_DIR
591 class XenHvmHypervisor(XenHypervisor):
592 """Xen HVM hypervisor interface"""
595 def _WriteConfigFile(instance, block_devices, extra_args):
596 """Create a Xen 3.1 HVM config file.
600 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
601 config.write("kernel = '/usr/lib/xen/boot/hvmloader'\n")
602 config.write("builder = 'hvm'\n")
603 config.write("memory = %d\n" % instance.memory)
604 config.write("vcpus = %d\n" % instance.vcpus)
605 config.write("name = '%s'\n" % instance.name)
607 config.write("pae = 1\n")
609 config.write("pae = 0\n")
610 if instance.hvm_acpi:
611 config.write("acpi = 1\n")
613 config.write("acpi = 0\n")
614 config.write("apic = 1\n")
617 config.write("device_model = '/usr/lib64/xen/bin/qemu-dm'\n")
619 config.write("device_model = '/usr/lib/xen/bin/qemu-dm'\n")
620 if instance.hvm_boot_order is None:
621 config.write("boot = '%s'\n" % constants.HT_HVM_DEFAULT_BOOT_ORDER)
623 config.write("boot = '%s'\n" % instance.hvm_boot_order)
624 config.write("sdl = 0\n")
625 config.write("usb = 1\n");
626 config.write("usbdevice = 'tablet'\n");
627 config.write("vnc = 1\n")
628 config.write("vnclisten = '%s'\n" % instance.vnc_bind_address)
630 if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
631 display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
632 config.write("vncdisplay = %s\n" % display)
633 config.write("vncunused = 0\n")
635 config.write("# vncdisplay = 1\n")
636 config.write("vncunused = 1\n")
639 password_file = open(constants.VNC_PASSWORD_FILE, "r")
641 password = password_file.readline()
643 password_file.close()
645 raise errors.OpExecError("failed to open VNC password file %s " %
646 constants.VNC_PASSWORD_FILE)
648 config.write("vncpasswd = '%s'\n" % password.rstrip())
650 config.write("serial = 'pty'\n")
651 config.write("localtime = 1\n")
654 for nic in instance.nics:
655 nic_str = "mac=%s, bridge=%s, type=ioemu" % (nic.mac, nic.bridge)
656 ip = getattr(nic, "ip", None)
658 nic_str += ", ip=%s" % ip
659 vif_data.append("'%s'" % nic_str)
661 config.write("vif = [%s]\n" % ",".join(vif_data))
663 disk_data = ["'phy:%s,%s,w'" %
664 (rldev.dev_path, cfdev.iv_name.replace("sd", "ioemu:hd"))
665 for cfdev, rldev in block_devices]
667 if instance.hvm_cdrom_image_path is None:
668 config.write("disk = [%s]\n" % (",".join(disk_data)))
670 iso = "'file:%s,hdc:cdrom,r'" % (instance.hvm_cdrom_image_path)
671 config.write("disk = [%s, %s]\n" % (",".join(disk_data), iso))
673 config.write("on_poweroff = 'destroy'\n")
674 config.write("on_reboot = 'restart'\n")
675 config.write("on_crash = 'restart'\n")
677 config.write("extra = '%s'\n" % extra_args)
678 # just in case it exists
679 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
681 f = open("/etc/xen/%s" % instance.name, "w")
683 f.write(config.getvalue())
687 raise errors.OpExecError("Cannot write Xen instance confile"
688 " file /etc/xen/%s: %s" % (instance.name, err))
692 def GetShellCommandForConsole(instance):
693 """Return a command for connecting to the console of an instance.
696 if instance.network_port is None:
697 raise errors.OpExecError("no console port defined for %s"
699 elif instance.vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
700 raise errors.OpExecError("no PTY console, connect to %s:%s via VNC"
701 % (instance.primary_node,
702 instance.network_port))
704 raise errors.OpExecError("no PTY console, connect to %s:%s via VNC"
705 % (instance.vnc_bind_address,
706 instance.network_port))