4 # Copyright (C) 2006, 2007, 2008, 2009, 2010 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
27 from cStringIO import StringIO
29 from ganeti import constants
30 from ganeti import errors
31 from ganeti import utils
32 from ganeti.hypervisor import hv_base
33 from ganeti import netutils
34 from ganeti import objects
37 class XenHypervisor(hv_base.BaseHypervisor):
38 """Xen generic hypervisor interface
40 This is the Xen base class used for both Xen PVM and HVM. It contains
41 all the functionality that is identical for both.
45 REBOOT_RETRY_COUNT = 60
46 REBOOT_RETRY_INTERVAL = 10
49 "/etc/xen/xend-config.sxp",
50 "/etc/xen/scripts/vif-bridge",
54 def _WriteConfigFile(cls, instance, block_devices):
55 """Write the Xen config file for the instance.
58 raise NotImplementedError
61 def _WriteConfigFileStatic(instance_name, data):
62 """Write the Xen config file for the instance.
64 This version of the function just writes the config file from static data.
67 utils.WriteFile("/etc/xen/%s" % instance_name, data=data)
70 def _ReadConfigFile(instance_name):
71 """Returns the contents of the instance config file.
75 file_content = utils.ReadFile("/etc/xen/%s" % instance_name)
76 except EnvironmentError, err:
77 raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
81 def _RemoveConfigFile(instance_name):
82 """Remove the xen configuration file.
85 utils.RemoveFile("/etc/xen/%s" % instance_name)
88 def _CreateConfigCpus(cls, cpu_mask):
89 """Create a CPU config string that's compatible with Xen's
93 # Convert the string CPU mask to a list of list of int's
94 cpu_list = utils.ParseMultiCpuMask(cpu_mask)
96 if len(cpu_list) == 1:
97 all_cpu_mapping = cpu_list[0]
98 if (len(all_cpu_mapping) == 1 and
99 all_cpu_mapping[0] == constants.CPU_PINNING_ALL_VAL):
100 # If CPU pinning has 1 entry that's "all", then remove the
101 # parameter from the config file
104 # If CPU pinning has one non-all entry, mapping all vCPUS (the entire
105 # VM) to one physical CPU, using format 'cpu = "C"'
106 return "cpu = \"%s\"" % ",".join(map(str, all_cpu_mapping))
108 def _GetCPUMap(vcpu):
109 if vcpu[0] == constants.CPU_PINNING_ALL_VAL:
110 cpu_map = constants.CPU_PINNING_ALL_XEN
112 cpu_map = ",".join(map(str, vcpu))
113 return "\"%s\"" % cpu_map
115 # build the result string in format 'cpus = [ "c", "c", "c" ]',
116 # where each c is a physical CPU number, a range, a list, or any
118 return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
121 def _RunXmList(xmlist_errors):
122 """Helper function for L{_GetXMList} to run "xm list".
125 result = utils.RunCmd(["xm", "list"])
127 logging.error("xm list failed (%s): %s", result.fail_reason,
129 xmlist_errors.append(result)
130 raise utils.RetryAgain()
132 # skip over the heading
133 return result.stdout.splitlines()[1:]
136 def _GetXMList(cls, include_node):
137 """Return the list of running instances.
139 If the include_node argument is True, then we return information
140 for dom0 also, otherwise we filter that from the return value.
142 @return: list of (name, id, memory, vcpus, state, time spent)
147 lines = utils.Retry(cls._RunXmList, 1, 5, args=(xmlist_errors, ))
148 except utils.RetryTimeout:
150 xmlist_result = xmlist_errors.pop()
152 errmsg = ("xm list failed, timeout exceeded (%s): %s" %
153 (xmlist_result.fail_reason, xmlist_result.output))
155 errmsg = "xm list failed"
157 raise errors.HypervisorError(errmsg)
161 # The format of lines is:
162 # Name ID Mem(MiB) VCPUs State Time(s)
163 # Domain-0 0 3418 4 r----- 266.2
166 raise errors.HypervisorError("Can't parse output of xm list,"
169 data[1] = int(data[1])
170 data[2] = int(data[2])
171 data[3] = int(data[3])
172 data[5] = float(data[5])
173 except (TypeError, ValueError), err:
174 raise errors.HypervisorError("Can't parse output of xm list,"
175 " line: %s, error: %s" % (line, err))
177 # skip the Domain-0 (optional)
178 if include_node or data[0] != "Domain-0":
183 def ListInstances(self):
184 """Get the list of running instances.
187 xm_list = self._GetXMList(False)
188 names = [info[0] for info in xm_list]
191 def GetInstanceInfo(self, instance_name):
192 """Get instance properties.
194 @param instance_name: the instance name
196 @return: tuple (name, id, memory, vcpus, stat, times)
199 xm_list = self._GetXMList(instance_name == "Domain-0")
202 if data[0] == instance_name:
207 def GetAllInstancesInfo(self):
208 """Get properties of all instances.
210 @return: list of tuples (name, id, memory, vcpus, stat, times)
213 xm_list = self._GetXMList(False)
216 def StartInstance(self, instance, block_devices, startup_paused):
217 """Start an instance.
220 self._WriteConfigFile(instance, block_devices)
221 cmd = ["xm", "create"]
223 cmd.extend(["--paused"])
224 cmd.extend([instance.name])
225 result = utils.RunCmd(cmd)
228 raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
229 (instance.name, result.fail_reason,
232 def StopInstance(self, instance, force=False, retry=False, name=None):
238 self._RemoveConfigFile(name)
240 command = ["xm", "destroy", name]
242 command = ["xm", "shutdown", name]
243 result = utils.RunCmd(command)
246 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
247 (name, result.fail_reason, result.output))
249 def RebootInstance(self, instance):
250 """Reboot an instance.
253 ini_info = self.GetInstanceInfo(instance.name)
256 raise errors.HypervisorError("Failed to reboot instance %s,"
257 " not running" % instance.name)
259 result = utils.RunCmd(["xm", "reboot", instance.name])
261 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
262 (instance.name, result.fail_reason,
265 def _CheckInstance():
266 new_info = self.GetInstanceInfo(instance.name)
268 # check if the domain ID has changed or the run time has decreased
269 if (new_info is not None and
270 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
273 raise utils.RetryAgain()
276 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
277 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
278 except utils.RetryTimeout:
279 raise errors.HypervisorError("Failed to reboot instance %s: instance"
280 " did not reboot in the expected interval" %
283 def GetNodeInfo(self):
284 """Return information about the node.
286 @return: a dict with the following keys (memory values in MiB):
287 - memory_total: the total memory size on the node
288 - memory_free: the available memory on the node for instances
289 - memory_dom0: the memory used by the node itself, if available
290 - nr_cpus: total number of CPUs
291 - nr_nodes: in a NUMA system, the number of domains
292 - nr_sockets: the number of physical CPU sockets in the node
295 # note: in xen 3, memory has changed to total_memory
296 result = utils.RunCmd(["xm", "info"])
298 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
302 xmoutput = result.stdout.splitlines()
304 cores_per_socket = threads_per_core = nr_cpus = None
305 for line in xmoutput:
306 splitfields = line.split(":", 1)
308 if len(splitfields) > 1:
309 key = splitfields[0].strip()
310 val = splitfields[1].strip()
311 if key == "memory" or key == "total_memory":
312 result["memory_total"] = int(val)
313 elif key == "free_memory":
314 result["memory_free"] = int(val)
315 elif key == "nr_cpus":
316 nr_cpus = result["cpu_total"] = int(val)
317 elif key == "nr_nodes":
318 result["cpu_nodes"] = int(val)
319 elif key == "cores_per_socket":
320 cores_per_socket = int(val)
321 elif key == "threads_per_core":
322 threads_per_core = int(val)
324 if (cores_per_socket is not None and
325 threads_per_core is not None and nr_cpus is not None):
326 result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
328 dom0_info = self.GetInstanceInfo("Domain-0")
329 if dom0_info is not None:
330 result["memory_dom0"] = dom0_info[2]
335 def GetInstanceConsole(cls, instance, hvparams, beparams):
336 """Return a command for connecting to the console of an instance.
339 return objects.InstanceConsole(instance=instance.name,
340 kind=constants.CONS_SSH,
341 host=instance.primary_node,
342 user=constants.GANETI_RUNAS,
343 command=[constants.XM_CONSOLE_WRAPPER,
347 """Verify the hypervisor.
349 For Xen, this verifies that the xend process is running.
352 result = utils.RunCmd(["xm", "info"])
354 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
357 def _GetConfigFileDiskData(block_devices, blockdev_prefix):
358 """Get disk directive for xen config file.
360 This method builds the xen config disk directive according to the
361 given disk_template and block_devices.
363 @param block_devices: list of tuples (cfdev, rldev):
364 - cfdev: dict containing ganeti config disk part
365 - rldev: ganeti.bdev.BlockDev object
366 @param blockdev_prefix: a string containing blockdevice prefix,
367 e.g. "sd" for /dev/sda
369 @return: string containing disk directive for xen instance config file
373 constants.FD_LOOP: "file",
374 constants.FD_BLKTAP: "tap:aio",
377 if len(block_devices) > 24:
379 raise errors.HypervisorError("Too many disks")
380 namespace = [blockdev_prefix + chr(i + ord("a")) for i in range(24)]
381 for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
382 if cfdev.mode == constants.DISK_RDWR:
386 if cfdev.dev_type == constants.LD_FILE:
387 line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
388 dev_path, sd_name, mode)
390 line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
391 disk_data.append(line)
395 def MigrationInfo(self, instance):
396 """Get instance information to perform a migration.
398 @type instance: L{objects.Instance}
399 @param instance: instance to be migrated
401 @return: content of the xen config file
404 return self._ReadConfigFile(instance.name)
406 def AcceptInstance(self, instance, info, target):
407 """Prepare to accept an instance.
409 @type instance: L{objects.Instance}
410 @param instance: instance to be accepted
412 @param info: content of the xen config file on the source node
414 @param target: target host (usually ip), on this node
419 def FinalizeMigration(self, instance, info, success):
420 """Finalize an instance migration.
422 After a successful migration we write the xen config file.
423 We do nothing on a failure, as we did not change anything at accept time.
425 @type instance: L{objects.Instance}
426 @param instance: instance whose migration is being finalized
428 @param info: content of the xen config file on the source node
429 @type success: boolean
430 @param success: whether the migration was a success or a failure
434 self._WriteConfigFileStatic(instance.name, info)
436 def MigrateInstance(self, instance, target, live):
437 """Migrate an instance to a target node.
439 The migration will not be attempted if the instance is not
442 @type instance: L{objects.Instance}
443 @param instance: the instance to be migrated
445 @param target: ip address of the target node
447 @param live: perform a live migration
450 if self.GetInstanceInfo(instance.name) is None:
451 raise errors.HypervisorError("Instance not running, cannot migrate")
453 port = instance.hvparams[constants.HV_MIGRATION_PORT]
455 if not netutils.TcpPing(target, port, live_port_needed=True):
456 raise errors.HypervisorError("Remote host %s not listening on port"
457 " %s, cannot migrate" % (target, port))
459 args = ["xm", "migrate", "-p", "%d" % port]
462 args.extend([instance.name, target])
463 result = utils.RunCmd(args)
465 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
466 (instance.name, result.output))
467 # remove old xen file after migration succeeded
469 self._RemoveConfigFile(instance.name)
470 except EnvironmentError:
471 logging.exception("Failure while removing instance config file")
474 def PowercycleNode(cls):
475 """Xen-specific powercycle.
477 This first does a Linux reboot (which triggers automatically a Xen
478 reboot), and if that fails it tries to do a Xen reboot. The reason
479 we don't try a Xen reboot first is that the xen reboot launches an
480 external command which connects to the Xen hypervisor, and that
481 won't work in case the root filesystem is broken and/or the xend
482 daemon is not working.
486 cls.LinuxPowercycle()
488 utils.RunCmd(["xm", "debug", "R"])
491 class XenPvmHypervisor(XenHypervisor):
492 """Xen PVM hypervisor interface"""
495 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
496 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
497 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
498 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
499 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
500 constants.HV_ROOT_PATH: hv_base.NO_CHECK,
501 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
502 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
503 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
504 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
505 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
506 constants.HV_REBOOT_BEHAVIOR:
507 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
508 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
512 def _WriteConfigFile(cls, instance, block_devices):
513 """Write the Xen config file for the instance.
516 hvp = instance.hvparams
518 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
520 # if bootloader is True, use bootloader instead of kernel and ramdisk
522 if hvp[constants.HV_USE_BOOTLOADER]:
523 # bootloader handling
524 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
526 config.write("bootloader = '%s'\n" % bootloader_path)
528 raise errors.HypervisorError("Bootloader enabled, but missing"
531 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
533 config.write("bootargs = '%s'\n" % bootloader_args)
536 kpath = hvp[constants.HV_KERNEL_PATH]
537 config.write("kernel = '%s'\n" % kpath)
540 initrd_path = hvp[constants.HV_INITRD_PATH]
542 config.write("ramdisk = '%s'\n" % initrd_path)
544 # rest of the settings
545 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
546 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
547 cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
549 config.write("%s\n" % cpu_pinning)
551 config.write("name = '%s'\n" % instance.name)
554 for nic in instance.nics:
555 nic_str = "mac=%s" % (nic.mac)
556 ip = getattr(nic, "ip", None)
558 nic_str += ", ip=%s" % ip
559 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
560 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
561 vif_data.append("'%s'" % nic_str)
563 disk_data = cls._GetConfigFileDiskData(block_devices,
564 hvp[constants.HV_BLOCKDEV_PREFIX])
566 config.write("vif = [%s]\n" % ",".join(vif_data))
567 config.write("disk = [%s]\n" % ",".join(disk_data))
569 if hvp[constants.HV_ROOT_PATH]:
570 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
571 config.write("on_poweroff = 'destroy'\n")
572 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
573 config.write("on_reboot = 'restart'\n")
575 config.write("on_reboot = 'destroy'\n")
576 config.write("on_crash = 'restart'\n")
577 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
578 # just in case it exists
579 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
581 utils.WriteFile("/etc/xen/%s" % instance.name, data=config.getvalue())
582 except EnvironmentError, err:
583 raise errors.HypervisorError("Cannot write Xen instance confile"
584 " file /etc/xen/%s: %s" %
585 (instance.name, err))
590 class XenHvmHypervisor(XenHypervisor):
591 """Xen HVM hypervisor interface"""
593 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
594 constants.VNC_PASSWORD_FILE,
598 constants.HV_ACPI: hv_base.NO_CHECK,
599 constants.HV_BOOT_ORDER: (True, ) +
600 (lambda x: x and len(x.strip("acdn")) == 0,
601 "Invalid boot order specified, must be one or more of [acdn]",
603 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
604 constants.HV_DISK_TYPE:
605 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
606 constants.HV_NIC_TYPE:
607 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
608 constants.HV_PAE: hv_base.NO_CHECK,
609 constants.HV_VNC_BIND_ADDRESS:
610 (False, netutils.IP4Address.IsValid,
611 "VNC bind address is not a valid IP address", None, None),
612 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
613 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
614 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
615 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
616 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
617 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
618 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
619 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
620 constants.HV_REBOOT_BEHAVIOR:
621 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
622 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
626 def _WriteConfigFile(cls, instance, block_devices):
627 """Create a Xen 3.1 HVM config file.
630 hvp = instance.hvparams
633 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
636 kpath = hvp[constants.HV_KERNEL_PATH]
637 config.write("kernel = '%s'\n" % kpath)
639 config.write("builder = 'hvm'\n")
640 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
641 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
642 cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
644 config.write("%s\n" % cpu_pinning)
646 config.write("name = '%s'\n" % instance.name)
647 if hvp[constants.HV_PAE]:
648 config.write("pae = 1\n")
650 config.write("pae = 0\n")
651 if hvp[constants.HV_ACPI]:
652 config.write("acpi = 1\n")
654 config.write("acpi = 0\n")
655 config.write("apic = 1\n")
656 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
657 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
658 config.write("sdl = 0\n")
659 config.write("usb = 1\n")
660 config.write("usbdevice = 'tablet'\n")
661 config.write("vnc = 1\n")
662 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
663 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
665 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
667 if instance.network_port > constants.VNC_BASE_PORT:
668 display = instance.network_port - constants.VNC_BASE_PORT
669 config.write("vncdisplay = %s\n" % display)
670 config.write("vncunused = 0\n")
672 config.write("# vncdisplay = 1\n")
673 config.write("vncunused = 1\n")
675 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
677 password = utils.ReadFile(vnc_pwd_file)
678 except EnvironmentError, err:
679 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
682 config.write("vncpasswd = '%s'\n" % password.rstrip())
684 config.write("serial = 'pty'\n")
685 if hvp[constants.HV_USE_LOCALTIME]:
686 config.write("localtime = 1\n")
689 nic_type = hvp[constants.HV_NIC_TYPE]
691 # ensure old instances don't change
692 nic_type_str = ", type=ioemu"
693 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
694 nic_type_str = ", type=paravirtualized"
696 nic_type_str = ", model=%s, type=ioemu" % nic_type
697 for nic in instance.nics:
698 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
699 ip = getattr(nic, "ip", None)
701 nic_str += ", ip=%s" % ip
702 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
703 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
704 vif_data.append("'%s'" % nic_str)
706 config.write("vif = [%s]\n" % ",".join(vif_data))
708 disk_data = cls._GetConfigFileDiskData(block_devices,
709 hvp[constants.HV_BLOCKDEV_PREFIX])
711 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
713 iso = "'file:%s,hdc:cdrom,r'" % iso_path
714 disk_data.append(iso)
716 config.write("disk = [%s]\n" % (",".join(disk_data)))
718 config.write("on_poweroff = 'destroy'\n")
719 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
720 config.write("on_reboot = 'restart'\n")
722 config.write("on_reboot = 'destroy'\n")
723 config.write("on_crash = 'restart'\n")
724 # just in case it exists
725 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
727 utils.WriteFile("/etc/xen/%s" % instance.name,
728 data=config.getvalue())
729 except EnvironmentError, err:
730 raise errors.HypervisorError("Cannot write Xen instance confile"
731 " file /etc/xen/%s: %s" %
732 (instance.name, err))