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_name):
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.name)
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:
280 (memory values in MiB)
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
284 - nr_cpus: total number of CPUs
285 - nr_nodes: in a NUMA system, the number of domains
286 - nr_sockets: the number of physical CPU sockets in the node
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 cores_per_socket = threads_per_core = nr_cpus = None
298 for line in xmoutput:
299 splitfields = line.split(":", 1)
301 if len(splitfields) > 1:
302 key = splitfields[0].strip()
303 val = splitfields[1].strip()
304 if key == 'memory' or key == 'total_memory':
305 result['memory_total'] = int(val)
306 elif key == 'free_memory':
307 result['memory_free'] = int(val)
308 elif key == 'nr_cpus':
309 nr_cpus = result['cpu_total'] = int(val)
310 elif key == 'nr_nodes':
311 result['cpu_nodes'] = int(val)
312 elif key == 'cores_per_socket':
313 cores_per_socket = int(val)
314 elif key == 'threads_per_core':
315 threads_per_core = int(val)
317 if (cores_per_socket is not None and
318 threads_per_core is not None and nr_cpus is not None):
319 result['cpu_sockets'] = nr_cpus / (cores_per_socket * threads_per_core)
321 dom0_info = self.GetInstanceInfo("Domain-0")
322 if dom0_info is not None:
323 result['memory_dom0'] = dom0_info[2]
328 def GetShellCommandForConsole(instance):
329 """Return a command for connecting to the console of an instance.
332 return "xm console %s" % instance.name
337 """Verify the hypervisor.
339 For Xen, this verifies that the xend process is running.
342 if not utils.CheckDaemonAlive('/var/run/xend.pid', 'xend'):
343 return "xend daemon is not running"
345 def MigrateInstance(self, instance, target, live):
346 """Migrate an instance to a target node.
349 - instance: the name of the instance
350 - target: the ip of the target node
351 - live: whether to do live migration or not
353 Returns: none, errors will be signaled by exception.
355 The migration will not be attempted if the instance is not
359 if self.GetInstanceInfo(instance) is None:
360 raise errors.HypervisorError("Instance not running, cannot migrate")
361 args = ["xm", "migrate"]
364 args.extend([instance, target])
365 result = utils.RunCmd(args)
367 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
368 (instance, result.output))
369 # remove old xen file after migration succeeded
371 self._RemoveConfigFile(instance)
372 except EnvironmentError, err:
373 logger.Error("Failure while removing instance config file: %s" %
377 class XenPvmHypervisor(XenHypervisor):
378 """Xen PVM hypervisor interface"""
381 def _WriteConfigFile(instance, block_devices, extra_args):
382 """Write the Xen config file for the instance.
386 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
389 if instance.kernel_path in (None, constants.VALUE_DEFAULT):
390 kpath = constants.XEN_KERNEL
392 if not os.path.exists(instance.kernel_path):
393 raise errors.HypervisorError("The kernel %s for instance %s is"
394 " missing" % (instance.kernel_path,
396 kpath = instance.kernel_path
397 config.write("kernel = '%s'\n" % kpath)
400 if instance.initrd_path in (None, constants.VALUE_DEFAULT):
401 if os.path.exists(constants.XEN_INITRD):
402 initrd_path = constants.XEN_INITRD
405 elif instance.initrd_path == constants.VALUE_NONE:
408 if not os.path.exists(instance.initrd_path):
409 raise errors.HypervisorError("The initrd %s for instance %s is"
410 " missing" % (instance.initrd_path,
412 initrd_path = instance.initrd_path
415 config.write("ramdisk = '%s'\n" % initrd_path)
417 # rest of the settings
418 config.write("memory = %d\n" % instance.memory)
419 config.write("vcpus = %d\n" % instance.vcpus)
420 config.write("name = '%s'\n" % instance.name)
423 for nic in instance.nics:
424 nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
425 ip = getattr(nic, "ip", None)
427 nic_str += ", ip=%s" % ip
428 vif_data.append("'%s'" % nic_str)
430 config.write("vif = [%s]\n" % ",".join(vif_data))
432 disk_data = ["'phy:%s,%s,w'" % names for names in block_devices]
433 config.write("disk = [%s]\n" % ",".join(disk_data))
435 config.write("root = '/dev/sda ro'\n")
436 config.write("on_poweroff = 'destroy'\n")
437 config.write("on_reboot = 'restart'\n")
438 config.write("on_crash = 'restart'\n")
440 config.write("extra = '%s'\n" % extra_args)
441 # just in case it exists
442 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
444 f = open("/etc/xen/%s" % instance.name, "w")
446 f.write(config.getvalue())
450 raise errors.OpExecError("Cannot write Xen instance confile"
451 " file /etc/xen/%s: %s" % (instance.name, err))
455 class FakeHypervisor(BaseHypervisor):
456 """Fake hypervisor interface.
458 This can be used for testing the ganeti code without having to have
459 a real virtualisation software installed.
462 _ROOT_DIR = constants.RUN_DIR + "/ganeti-fake-hypervisor"
465 BaseHypervisor.__init__(self)
466 if not os.path.exists(self._ROOT_DIR):
467 os.mkdir(self._ROOT_DIR)
469 def ListInstances(self):
470 """Get the list of running instances.
473 return os.listdir(self._ROOT_DIR)
475 def GetInstanceInfo(self, instance_name):
476 """Get instance properties.
479 instance_name: the instance name
482 (name, id, memory, vcpus, stat, times)
484 file_name = "%s/%s" % (self._ROOT_DIR, instance_name)
485 if not os.path.exists(file_name):
488 fh = file(file_name, "r")
490 inst_id = fh.readline().strip()
491 memory = fh.readline().strip()
492 vcpus = fh.readline().strip()
495 return (instance_name, inst_id, memory, vcpus, stat, times)
499 raise HypervisorError("Failed to list instance %s: %s" %
500 (instance_name, err))
502 def GetAllInstancesInfo(self):
503 """Get properties of all instances.
506 [(name, id, memory, vcpus, stat, times),...]
509 for file_name in os.listdir(self._ROOT_DIR):
511 fh = file(self._ROOT_DIR+"/"+file_name, "r")
517 inst_id = fh.readline().strip()
518 memory = fh.readline().strip()
519 vcpus = fh.readline().strip()
524 data.append((file_name, inst_id, memory, vcpus, stat, times))
526 raise HypervisorError("Failed to list instances: %s" % err)
529 def StartInstance(self, instance, block_devices, extra_args):
530 """Start an instance.
532 For the fake hypervisor, it just creates a file in the base dir,
533 creating an exception if it already exists. We don't actually
534 handle race conditions properly, since these are *FAKE* instances.
537 file_name = self._ROOT_DIR + "/%s" % instance.name
538 if os.path.exists(file_name):
539 raise HypervisorError("Failed to start instance %s: %s" %
540 (instance.name, "already running"))
542 fh = file(file_name, "w")
544 fh.write("0\n%d\n%d\n" % (instance.memory, instance.vcpus))
548 raise HypervisorError("Failed to start instance %s: %s" %
549 (instance.name, err))
551 def StopInstance(self, instance, force=False):
554 For the fake hypervisor, this just removes the file in the base
555 dir, if it exist, otherwise we raise an exception.
558 file_name = self._ROOT_DIR + "/%s" % instance.name
559 if not os.path.exists(file_name):
560 raise HypervisorError("Failed to stop instance %s: %s" %
561 (instance.name, "not running"))
562 utils.RemoveFile(file_name)
564 def RebootInstance(self, instance):
565 """Reboot an instance.
567 For the fake hypervisor, this does nothing.
572 def GetNodeInfo(self):
573 """Return information about the node.
575 The return value is a dict, which has to have the following items:
577 - memory_total: the total memory size on the node
578 - memory_free: the available memory on the node for instances
579 - memory_dom0: the memory used by the node itself, if available
582 # global ram usage from the xm info command
585 # note: in xen 3, memory has changed to total_memory
587 fh = file("/proc/meminfo")
589 data = fh.readlines()
593 raise HypervisorError("Failed to list node info: %s" % err)
598 splitfields = line.split(":", 1)
600 if len(splitfields) > 1:
601 key = splitfields[0].strip()
602 val = splitfields[1].strip()
603 if key == 'MemTotal':
604 result['memory_total'] = int(val.split()[0])/1024
605 elif key in ('MemFree', 'Buffers', 'Cached'):
606 sum_free += int(val.split()[0])/1024
607 elif key == 'Active':
608 result['memory_dom0'] = int(val.split()[0])/1024
609 result['memory_free'] = sum_free
613 fh = open("/proc/cpuinfo")
615 cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
619 except EnvironmentError, err:
620 raise HypervisorError("Failed to list node info: %s" % err)
621 result['cpu_total'] = cpu_total
626 def GetShellCommandForConsole(instance):
627 """Return a command for connecting to the console of an instance.
630 return "echo Console not available for fake hypervisor"
633 """Verify the hypervisor.
635 For the fake hypervisor, it just checks the existence of the base
639 if not os.path.exists(self._ROOT_DIR):
640 return "The required directory '%s' does not exist." % self._ROOT_DIR
643 class XenHvmHypervisor(XenHypervisor):
644 """Xen HVM hypervisor interface"""
647 def _WriteConfigFile(instance, block_devices, extra_args):
648 """Create a Xen 3.1 HVM config file.
652 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
653 config.write("kernel = '/usr/lib/xen/boot/hvmloader'\n")
654 config.write("builder = 'hvm'\n")
655 config.write("memory = %d\n" % instance.memory)
656 config.write("vcpus = %d\n" % instance.vcpus)
657 config.write("name = '%s'\n" % instance.name)
658 if instance.hvm_pae is None: # use default value if not specified
659 config.write("pae = %s\n" % constants.HT_HVM_DEFAULT_PAE_MODE)
660 elif instance.hvm_pae:
661 config.write("pae = 1\n")
663 config.write("pae = 0\n")
664 if instance.hvm_acpi is None: # use default value if not specified
665 config.write("acpi = %s\n" % constants.HT_HVM_DEFAULT_ACPI_MODE)
666 elif instance.hvm_acpi:
667 config.write("acpi = 1\n")
669 config.write("acpi = 0\n")
670 config.write("apic = 1\n")
673 config.write("device_model = '/usr/lib64/xen/bin/qemu-dm'\n")
675 config.write("device_model = '/usr/lib/xen/bin/qemu-dm'\n")
676 if instance.hvm_boot_order is None:
677 config.write("boot = '%s'\n" % constants.HT_HVM_DEFAULT_BOOT_ORDER)
679 config.write("boot = '%s'\n" % instance.hvm_boot_order)
680 config.write("sdl = 0\n")
681 config.write("usb = 1\n")
682 config.write("usbdevice = 'tablet'\n")
683 config.write("vnc = 1\n")
684 if instance.vnc_bind_address is None:
685 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
687 config.write("vnclisten = '%s'\n" % instance.vnc_bind_address)
689 if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
690 display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
691 config.write("vncdisplay = %s\n" % display)
692 config.write("vncunused = 0\n")
694 config.write("# vncdisplay = 1\n")
695 config.write("vncunused = 1\n")
698 password_file = open(constants.VNC_PASSWORD_FILE, "r")
700 password = password_file.readline()
702 password_file.close()
704 raise errors.OpExecError("failed to open VNC password file %s " %
705 constants.VNC_PASSWORD_FILE)
707 config.write("vncpasswd = '%s'\n" % password.rstrip())
709 config.write("serial = 'pty'\n")
710 config.write("localtime = 1\n")
713 for nic in instance.nics:
714 if instance.hvm_nic_type is None: # ensure old instances don't change
715 nic_type = ", type=ioemu"
716 elif instance.hvm_nic_type == constants.HT_HVM_DEV_PARAVIRTUAL:
717 nic_type = ", type=paravirtualized"
719 nic_type = ", model=%s, type=ioemu" % instance.hvm_nic_type
721 nic_str = "mac=%s, bridge=%s%s" % (nic.mac, nic.bridge, nic_type)
722 ip = getattr(nic, "ip", None)
724 nic_str += ", ip=%s" % ip
725 vif_data.append("'%s'" % nic_str)
727 config.write("vif = [%s]\n" % ",".join(vif_data))
729 # TODO(2.0): This code changes the block device name, seen by the instance,
730 # from what Ganeti believes it should be. Different hypervisors may have
731 # different requirements, so we should probably review the design of
732 # storing it altogether, for the next major version.
733 if ((instance.hvm_disk_type is None) or
734 (instance.hvm_disk_type == constants.HT_HVM_DEV_IOEMU)):
739 disk_data = ["'phy:%s,%s,w'" %
740 (dev_path, iv_name.replace("sd", "%shd" % disk_type))
741 for dev_path, iv_name in block_devices]
743 if instance.hvm_cdrom_image_path is None:
744 config.write("disk = [%s]\n" % (",".join(disk_data)))
746 iso = "'file:%s,hdc:cdrom,r'" % (instance.hvm_cdrom_image_path)
747 config.write("disk = [%s, %s]\n" % (",".join(disk_data), iso))
749 config.write("on_poweroff = 'destroy'\n")
750 config.write("on_reboot = 'restart'\n")
751 config.write("on_crash = 'restart'\n")
753 config.write("extra = '%s'\n" % extra_args)
754 # just in case it exists
755 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
757 f = open("/etc/xen/%s" % instance.name, "w")
759 f.write(config.getvalue())
763 raise errors.OpExecError("Cannot write Xen instance confile"
764 " file /etc/xen/%s: %s" % (instance.name, err))