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
129 def MigrateInstance(self, name, target, live):
130 """Migrate an instance.
133 - name: the name of the instance
134 - target: the target of the migration (usually will be IP and not name)
135 - live: whether to do live migration or not
137 Returns: none, errors will be signaled by exception.
140 raise NotImplementedError
143 class XenHypervisor(BaseHypervisor):
144 """Xen generic hypervisor interface
146 This is the Xen base class used for both Xen PVM and HVM. It contains
147 all the functionality that is identical for both.
152 def _WriteConfigFile(instance, block_devices, extra_args):
153 """Write the Xen config file for the instance.
156 raise NotImplementedError
159 def _RemoveConfigFile(instance):
160 """Remove the xen configuration file.
163 utils.RemoveFile("/etc/xen/%s" % instance.name)
166 def _GetXMList(include_node):
167 """Return the list of running instances.
169 If the `include_node` argument is True, then we return information
170 for dom0 also, otherwise we filter that from the return value.
172 The return value is a list of (name, id, memory, vcpus, state, time spent)
175 for dummy in range(5):
176 result = utils.RunCmd(["xm", "list"])
177 if not result.failed:
179 logger.Error("xm list failed (%s): %s" % (result.fail_reason,
184 raise HypervisorError("xm list failed, retries exceeded (%s): %s" %
185 (result.fail_reason, result.stderr))
187 # skip over the heading
188 lines = result.stdout.splitlines()[1:]
191 # The format of lines is:
192 # Name ID Mem(MiB) VCPUs State Time(s)
193 # Domain-0 0 3418 4 r----- 266.2
196 raise HypervisorError("Can't parse output of xm list, line: %s" % line)
198 data[1] = int(data[1])
199 data[2] = int(data[2])
200 data[3] = int(data[3])
201 data[5] = float(data[5])
202 except ValueError, err:
203 raise HypervisorError("Can't parse output of xm list,"
204 " line: %s, error: %s" % (line, err))
206 # skip the Domain-0 (optional)
207 if include_node or data[0] != 'Domain-0':
212 def ListInstances(self):
213 """Get the list of running instances.
216 xm_list = self._GetXMList(False)
217 names = [info[0] for info in xm_list]
220 def GetInstanceInfo(self, instance_name):
221 """Get instance properties.
224 instance_name: the instance name
227 (name, id, memory, vcpus, stat, times)
229 xm_list = self._GetXMList(instance_name=="Domain-0")
232 if data[0] == instance_name:
237 def GetAllInstancesInfo(self):
238 """Get properties of all instances.
241 [(name, id, memory, vcpus, stat, times),...]
243 xm_list = self._GetXMList(False)
246 def StartInstance(self, instance, block_devices, extra_args):
247 """Start an instance."""
248 self._WriteConfigFile(instance, block_devices, extra_args)
249 result = utils.RunCmd(["xm", "create", instance.name])
252 raise HypervisorError("Failed to start instance %s: %s (%s)" %
253 (instance.name, result.fail_reason, result.output))
255 def StopInstance(self, instance, force=False):
256 """Stop an instance."""
257 self._RemoveConfigFile(instance)
259 command = ["xm", "destroy", instance.name]
261 command = ["xm", "shutdown", instance.name]
262 result = utils.RunCmd(command)
265 raise HypervisorError("Failed to stop instance %s: %s" %
266 (instance.name, result.fail_reason))
268 def RebootInstance(self, instance):
269 """Reboot an instance."""
270 result = utils.RunCmd(["xm", "reboot", instance.name])
273 raise HypervisorError("Failed to reboot instance %s: %s" %
274 (instance.name, result.fail_reason))
276 def GetNodeInfo(self):
277 """Return information about the node.
279 The return value is a dict, which has to have the following items:
281 - memory_total: the total memory size on the node
282 - memory_free: the available memory on the node for instances
283 - memory_dom0: the memory used by the node itself, if available
286 # note: in xen 3, memory has changed to total_memory
287 result = utils.RunCmd(["xm", "info"])
289 logger.Error("Can't run 'xm info': %s" % result.fail_reason)
292 xmoutput = result.stdout.splitlines()
294 for line in xmoutput:
295 splitfields = line.split(":", 1)
297 if len(splitfields) > 1:
298 key = splitfields[0].strip()
299 val = splitfields[1].strip()
300 if key == 'memory' or key == 'total_memory':
301 result['memory_total'] = int(val)
302 elif key == 'free_memory':
303 result['memory_free'] = int(val)
304 elif key == 'nr_cpus':
305 result['cpu_total'] = int(val)
306 dom0_info = self.GetInstanceInfo("Domain-0")
307 if dom0_info is not None:
308 result['memory_dom0'] = dom0_info[2]
313 def GetShellCommandForConsole(instance):
314 """Return a command for connecting to the console of an instance.
317 raise NotImplementedError
321 """Verify the hypervisor.
323 For Xen, this verifies that the xend process is running.
326 if not utils.CheckDaemonAlive('/var/run/xend.pid', 'xend'):
327 return "xend daemon is not running"
329 def MigrateInstance(self, instance, target, live):
330 """Migrate an instance to a target node.
333 - instance: the name of the instance
334 - target: the ip of the target node
335 - live: whether to do live migration or not
337 Returns: none, errors will be signaled by exception.
339 The migration will not be attempted if the instance is not
343 if self.GetInstanceInfo(instance) is None:
344 raise errors.HypervisorError("Instance not running, cannot migrate")
345 args = ["xm", "migrate"]
348 args.extend([instance, target])
349 result = utils.RunCmd(args)
351 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
352 (instance, result.output))
355 class XenPvmHypervisor(XenHypervisor):
356 """Xen PVM hypervisor interface"""
359 def _WriteConfigFile(instance, block_devices, extra_args):
360 """Write the Xen config file for the instance.
364 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
367 if instance.kernel_path in (None, constants.VALUE_DEFAULT):
368 kpath = constants.XEN_KERNEL
370 if not os.path.exists(instance.kernel_path):
371 raise errors.HypervisorError("The kernel %s for instance %s is"
372 " missing" % (instance.kernel_path,
374 kpath = instance.kernel_path
375 config.write("kernel = '%s'\n" % kpath)
378 if instance.initrd_path in (None, constants.VALUE_DEFAULT):
379 if os.path.exists(constants.XEN_INITRD):
380 initrd_path = constants.XEN_INITRD
383 elif instance.initrd_path == constants.VALUE_NONE:
386 if not os.path.exists(instance.initrd_path):
387 raise errors.HypervisorError("The initrd %s for instance %s is"
388 " missing" % (instance.initrd_path,
390 initrd_path = instance.initrd_path
393 config.write("ramdisk = '%s'\n" % initrd_path)
395 # rest of the settings
396 config.write("memory = %d\n" % instance.memory)
397 config.write("vcpus = %d\n" % instance.vcpus)
398 config.write("name = '%s'\n" % instance.name)
401 for nic in instance.nics:
402 nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
403 ip = getattr(nic, "ip", None)
405 nic_str += ", ip=%s" % ip
406 vif_data.append("'%s'" % nic_str)
408 config.write("vif = [%s]\n" % ",".join(vif_data))
410 disk_data = ["'phy:%s,%s,w'" % (rldev.dev_path, cfdev.iv_name)
411 for cfdev, rldev in block_devices]
412 config.write("disk = [%s]\n" % ",".join(disk_data))
414 config.write("root = '/dev/sda ro'\n")
415 config.write("on_poweroff = 'destroy'\n")
416 config.write("on_reboot = 'restart'\n")
417 config.write("on_crash = 'restart'\n")
419 config.write("extra = '%s'\n" % extra_args)
420 # just in case it exists
421 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
423 f = open("/etc/xen/%s" % instance.name, "w")
425 f.write(config.getvalue())
429 raise errors.OpExecError("Cannot write Xen instance confile"
430 " file /etc/xen/%s: %s" % (instance.name, err))
434 def GetShellCommandForConsole(instance):
435 """Return a command for connecting to the console of an instance.
438 return "xm console %s" % instance.name
441 class FakeHypervisor(BaseHypervisor):
442 """Fake hypervisor interface.
444 This can be used for testing the ganeti code without having to have
445 a real virtualisation software installed.
448 _ROOT_DIR = constants.RUN_DIR + "/ganeti-fake-hypervisor"
451 BaseHypervisor.__init__(self)
452 if not os.path.exists(self._ROOT_DIR):
453 os.mkdir(self._ROOT_DIR)
455 def ListInstances(self):
456 """Get the list of running instances.
459 return os.listdir(self._ROOT_DIR)
461 def GetInstanceInfo(self, instance_name):
462 """Get instance properties.
465 instance_name: the instance name
468 (name, id, memory, vcpus, stat, times)
470 file_name = "%s/%s" % (self._ROOT_DIR, instance_name)
471 if not os.path.exists(file_name):
474 fh = file(file_name, "r")
476 inst_id = fh.readline().strip()
477 memory = fh.readline().strip()
478 vcpus = fh.readline().strip()
481 return (instance_name, inst_id, memory, vcpus, stat, times)
485 raise HypervisorError("Failed to list instance %s: %s" %
486 (instance_name, err))
488 def GetAllInstancesInfo(self):
489 """Get properties of all instances.
492 [(name, id, memory, vcpus, stat, times),...]
495 for file_name in os.listdir(self._ROOT_DIR):
497 fh = file(self._ROOT_DIR+"/"+file_name, "r")
503 inst_id = fh.readline().strip()
504 memory = fh.readline().strip()
505 vcpus = fh.readline().strip()
510 data.append((file_name, inst_id, memory, vcpus, stat, times))
512 raise HypervisorError("Failed to list instances: %s" % err)
515 def StartInstance(self, instance, force, extra_args):
516 """Start an instance.
518 For the fake hypervisor, it just creates a file in the base dir,
519 creating an exception if it already exists. We don't actually
520 handle race conditions properly, since these are *FAKE* instances.
523 file_name = self._ROOT_DIR + "/%s" % instance.name
524 if os.path.exists(file_name):
525 raise HypervisorError("Failed to start instance %s: %s" %
526 (instance.name, "already running"))
528 fh = file(file_name, "w")
530 fh.write("0\n%d\n%d\n" % (instance.memory, instance.vcpus))
534 raise HypervisorError("Failed to start instance %s: %s" %
535 (instance.name, err))
537 def StopInstance(self, instance, force=False):
540 For the fake hypervisor, this just removes the file in the base
541 dir, if it exist, otherwise we raise an exception.
544 file_name = self._ROOT_DIR + "/%s" % instance.name
545 if not os.path.exists(file_name):
546 raise HypervisorError("Failed to stop instance %s: %s" %
547 (instance.name, "not running"))
548 utils.RemoveFile(file_name)
550 def RebootInstance(self, instance):
551 """Reboot an instance.
553 For the fake hypervisor, this does nothing.
558 def GetNodeInfo(self):
559 """Return information about the node.
561 The return value is a dict, which has to have the following items:
563 - memory_total: the total memory size on the node
564 - memory_free: the available memory on the node for instances
565 - memory_dom0: the memory used by the node itself, if available
568 # global ram usage from the xm info command
571 # note: in xen 3, memory has changed to total_memory
573 fh = file("/proc/meminfo")
575 data = fh.readlines()
579 raise HypervisorError("Failed to list node info: %s" % err)
584 splitfields = line.split(":", 1)
586 if len(splitfields) > 1:
587 key = splitfields[0].strip()
588 val = splitfields[1].strip()
589 if key == 'MemTotal':
590 result['memory_total'] = int(val.split()[0])/1024
591 elif key in ('MemFree', 'Buffers', 'Cached'):
592 sum_free += int(val.split()[0])/1024
593 elif key == 'Active':
594 result['memory_dom0'] = int(val.split()[0])/1024
595 result['memory_free'] = sum_free
599 fh = open("/proc/cpuinfo")
601 cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
605 except EnvironmentError, err:
606 raise HypervisorError("Failed to list node info: %s" % err)
607 result['cpu_total'] = cpu_total
612 def GetShellCommandForConsole(instance):
613 """Return a command for connecting to the console of an instance.
616 return "echo Console not available for fake hypervisor"
619 """Verify the hypervisor.
621 For the fake hypervisor, it just checks the existence of the base
625 if not os.path.exists(self._ROOT_DIR):
626 return "The required directory '%s' does not exist." % self._ROOT_DIR
629 class XenHvmHypervisor(XenHypervisor):
630 """Xen HVM hypervisor interface"""
633 def _WriteConfigFile(instance, block_devices, extra_args):
634 """Create a Xen 3.1 HVM config file.
638 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
639 config.write("kernel = '/usr/lib/xen/boot/hvmloader'\n")
640 config.write("builder = 'hvm'\n")
641 config.write("memory = %d\n" % instance.memory)
642 config.write("vcpus = %d\n" % instance.vcpus)
643 config.write("name = '%s'\n" % instance.name)
644 if instance.hvm_pae is None: # use default value if not specified
645 config.write("pae = %s\n" % constants.HT_HVM_DEFAULT_PAE_MODE)
646 elif instance.hvm_pae:
647 config.write("pae = 1\n")
649 config.write("pae = 0\n")
650 if instance.hvm_acpi is None: # use default value if not specified
651 config.write("acpi = %s\n" % constants.HT_HVM_DEFAULT_ACPI_MODE)
652 elif instance.hvm_acpi:
653 config.write("acpi = 1\n")
655 config.write("acpi = 0\n")
656 config.write("apic = 1\n")
659 config.write("device_model = '/usr/lib64/xen/bin/qemu-dm'\n")
661 config.write("device_model = '/usr/lib/xen/bin/qemu-dm'\n")
662 if instance.hvm_boot_order is None:
663 config.write("boot = '%s'\n" % constants.HT_HVM_DEFAULT_BOOT_ORDER)
665 config.write("boot = '%s'\n" % instance.hvm_boot_order)
666 config.write("sdl = 0\n")
667 config.write("usb = 1\n");
668 config.write("usbdevice = 'tablet'\n");
669 config.write("vnc = 1\n")
670 config.write("vnclisten = '%s'\n" % instance.vnc_bind_address)
672 if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
673 display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
674 config.write("vncdisplay = %s\n" % display)
675 config.write("vncunused = 0\n")
677 config.write("# vncdisplay = 1\n")
678 config.write("vncunused = 1\n")
681 password_file = open(constants.VNC_PASSWORD_FILE, "r")
683 password = password_file.readline()
685 password_file.close()
687 raise errors.OpExecError("failed to open VNC password file %s " %
688 constants.VNC_PASSWORD_FILE)
690 config.write("vncpasswd = '%s'\n" % password.rstrip())
692 config.write("serial = 'pty'\n")
693 config.write("localtime = 1\n")
696 for nic in instance.nics:
697 nic_str = "mac=%s, bridge=%s, type=ioemu" % (nic.mac, nic.bridge)
698 ip = getattr(nic, "ip", None)
700 nic_str += ", ip=%s" % ip
701 vif_data.append("'%s'" % nic_str)
703 config.write("vif = [%s]\n" % ",".join(vif_data))
705 disk_data = ["'phy:%s,%s,w'" %
706 (rldev.dev_path, cfdev.iv_name.replace("sd", "ioemu:hd"))
707 for cfdev, rldev in block_devices]
709 if instance.hvm_cdrom_image_path is None:
710 config.write("disk = [%s]\n" % (",".join(disk_data)))
712 iso = "'file:%s,hdc:cdrom,r'" % (instance.hvm_cdrom_image_path)
713 config.write("disk = [%s, %s]\n" % (",".join(disk_data), iso))
715 config.write("on_poweroff = 'destroy'\n")
716 config.write("on_reboot = 'restart'\n")
717 config.write("on_crash = 'restart'\n")
719 config.write("extra = '%s'\n" % extra_args)
720 # just in case it exists
721 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
723 f = open("/etc/xen/%s" % instance.name, "w")
725 f.write(config.getvalue())
729 raise errors.OpExecError("Cannot write Xen instance confile"
730 " file /etc/xen/%s: %s" % (instance.name, err))
734 def GetShellCommandForConsole(instance):
735 """Return a command for connecting to the console of an instance.
738 if instance.network_port is None:
739 raise errors.OpExecError("no console port defined for %s"
741 elif instance.vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
742 raise errors.OpExecError("no PTY console, connect to %s:%s via VNC"
743 % (instance.primary_node,
744 instance.network_port))
746 raise errors.OpExecError("no PTY console, connect to %s:%s via VNC"
747 % (instance.vnc_bind_address,
748 instance.network_port))