4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 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 import string # pylint: disable=W0402
28 from cStringIO import StringIO
30 from ganeti import constants
31 from ganeti import errors
32 from ganeti import utils
33 from ganeti.hypervisor import hv_base
34 from ganeti import netutils
35 from ganeti import objects
36 from ganeti import pathutils
37 from ganeti import ssconf
40 XEND_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xend-config.sxp")
41 XL_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xen/xl.conf")
42 VIF_BRIDGE_SCRIPT = utils.PathJoin(pathutils.XEN_CONFIG_DIR,
44 _DOM0_NAME = "Domain-0"
45 _DISK_LETTERS = string.ascii_lowercase
48 constants.FD_LOOP: "file",
49 constants.FD_BLKTAP: "tap:aio",
53 def _CreateConfigCpus(cpu_mask):
54 """Create a CPU config string for Xen's config file.
57 # Convert the string CPU mask to a list of list of int's
58 cpu_list = utils.ParseMultiCpuMask(cpu_mask)
60 if len(cpu_list) == 1:
61 all_cpu_mapping = cpu_list[0]
62 if all_cpu_mapping == constants.CPU_PINNING_OFF:
63 # If CPU pinning has 1 entry that's "all", then remove the
64 # parameter from the config file
67 # If CPU pinning has one non-all entry, mapping all vCPUS (the entire
68 # VM) to one physical CPU, using format 'cpu = "C"'
69 return "cpu = \"%s\"" % ",".join(map(str, all_cpu_mapping))
73 if vcpu[0] == constants.CPU_PINNING_ALL_VAL:
74 cpu_map = constants.CPU_PINNING_ALL_XEN
76 cpu_map = ",".join(map(str, vcpu))
77 return "\"%s\"" % cpu_map
79 # build the result string in format 'cpus = [ "c", "c", "c" ]',
80 # where each c is a physical CPU number, a range, a list, or any
82 return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
85 def _RunInstanceList(fn, instance_list_errors):
86 """Helper function for L{_GetInstanceList} to retrieve the list of instances
90 @param fn: Function to query xen for the list of instances
91 @type instance_list_errors: list
92 @param instance_list_errors: Error list
98 logging.error("Retrieving the instance list from xen failed (%s): %s",
99 result.fail_reason, result.output)
100 instance_list_errors.append(result)
101 raise utils.RetryAgain()
103 # skip over the heading
104 return result.stdout.splitlines()
107 def _ParseXmList(lines, include_node):
108 """Parses the output of C{xm list}.
111 @param lines: Output lines of C{xm list}
112 @type include_node: boolean
113 @param include_node: If True, return information for Dom0
114 @return: list of tuple containing (name, id, memory, vcpus, state, time
120 # Iterate through all lines while ignoring header
121 for line in lines[1:]:
122 # The format of lines is:
123 # Name ID Mem(MiB) VCPUs State Time(s)
124 # Domain-0 0 3418 4 r----- 266.2
127 raise errors.HypervisorError("Can't parse output of xm list,"
130 data[1] = int(data[1])
131 data[2] = int(data[2])
132 data[3] = int(data[3])
133 data[5] = float(data[5])
134 except (TypeError, ValueError), err:
135 raise errors.HypervisorError("Can't parse output of xm list,"
136 " line: %s, error: %s" % (line, err))
138 # skip the Domain-0 (optional)
139 if include_node or data[0] != _DOM0_NAME:
145 def _GetInstanceList(fn, include_node, _timeout=5):
146 """Return the list of running instances.
148 See L{_RunInstanceList} and L{_ParseXmList} for parameter details.
151 instance_list_errors = []
153 lines = utils.Retry(_RunInstanceList, (0.3, 1.5, 1.0), _timeout,
154 args=(fn, instance_list_errors))
155 except utils.RetryTimeout:
156 if instance_list_errors:
157 instance_list_result = instance_list_errors.pop()
159 errmsg = ("listing instances failed, timeout exceeded (%s): %s" %
160 (instance_list_result.fail_reason, instance_list_result.output))
162 errmsg = "listing instances failed"
164 raise errors.HypervisorError(errmsg)
166 return _ParseXmList(lines, include_node)
169 def _ParseNodeInfo(info):
170 """Return information about the node.
172 @return: a dict with the following keys (memory values in MiB):
173 - memory_total: the total memory size on the node
174 - memory_free: the available memory on the node for instances
175 - nr_cpus: total number of CPUs
176 - nr_nodes: in a NUMA system, the number of domains
177 - nr_sockets: the number of physical CPU sockets in the node
178 - hv_version: the hypervisor version in the form (major, minor)
182 cores_per_socket = threads_per_core = nr_cpus = None
183 xen_major, xen_minor = None, None
187 for line in info.splitlines():
188 fields = line.split(":", 1)
193 (key, val) = map(lambda s: s.strip(), fields)
195 # Note: in Xen 3, memory has changed to total_memory
196 if key in ("memory", "total_memory"):
197 memory_total = int(val)
198 elif key == "free_memory":
199 memory_free = int(val)
200 elif key == "nr_cpus":
201 nr_cpus = result["cpu_total"] = int(val)
202 elif key == "nr_nodes":
203 result["cpu_nodes"] = int(val)
204 elif key == "cores_per_socket":
205 cores_per_socket = int(val)
206 elif key == "threads_per_core":
207 threads_per_core = int(val)
208 elif key == "xen_major":
210 elif key == "xen_minor":
213 if None not in [cores_per_socket, threads_per_core, nr_cpus]:
214 result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
216 if memory_free is not None:
217 result["memory_free"] = memory_free
219 if memory_total is not None:
220 result["memory_total"] = memory_total
222 if not (xen_major is None or xen_minor is None):
223 result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
228 def _MergeInstanceInfo(info, fn):
229 """Updates node information from L{_ParseNodeInfo} with instance info.
232 @param info: Result from L{_ParseNodeInfo}
234 @param fn: Function returning result of running C{xm list}
240 for (name, _, mem, vcpus, _, _) in fn(True):
241 if name == _DOM0_NAME:
242 info["memory_dom0"] = mem
243 info["dom0_cpus"] = vcpus
245 # Include Dom0 in total memory usage
248 memory_free = info.get("memory_free")
249 memory_total = info.get("memory_total")
251 # Calculate memory used by hypervisor
252 if None not in [memory_total, memory_free, total_instmem]:
253 info["memory_hv"] = memory_total - memory_free - total_instmem
258 def _GetNodeInfo(info, fn):
259 """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
262 return _MergeInstanceInfo(_ParseNodeInfo(info), fn)
265 def _GetConfigFileDiskData(block_devices, blockdev_prefix,
266 _letters=_DISK_LETTERS):
267 """Get disk directives for Xen config file.
269 This method builds the xen config disk directive according to the
270 given disk_template and block_devices.
272 @param block_devices: list of tuples (cfdev, rldev):
273 - cfdev: dict containing ganeti config disk part
274 - rldev: ganeti.block.bdev.BlockDev object
275 @param blockdev_prefix: a string containing blockdevice prefix,
276 e.g. "sd" for /dev/sda
278 @return: string containing disk directive for xen instance config file
281 if len(block_devices) > len(_letters):
282 raise errors.HypervisorError("Too many disks")
286 for sd_suffix, (cfdev, dev_path) in zip(_letters, block_devices):
287 sd_name = blockdev_prefix + sd_suffix
289 if cfdev.mode == constants.DISK_RDWR:
294 if cfdev.dev_type == constants.LD_FILE:
295 driver = _FILE_DRIVER_MAP[cfdev.physical_id[0]]
299 disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode))
304 class XenHypervisor(hv_base.BaseHypervisor):
305 """Xen generic hypervisor interface
307 This is the Xen base class used for both Xen PVM and HVM. It contains
308 all the functionality that is identical for both.
312 REBOOT_RETRY_COUNT = 60
313 REBOOT_RETRY_INTERVAL = 10
320 ANCILLARY_FILES_OPT = [
324 def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
325 hv_base.BaseHypervisor.__init__(self)
328 self._cfgdir = pathutils.XEN_CONFIG_DIR
330 self._cfgdir = _cfgdir
332 if _run_cmd_fn is None:
333 self._run_cmd_fn = utils.RunCmd
335 self._run_cmd_fn = _run_cmd_fn
339 def _GetCommand(self, hvparams=None):
340 """Returns Xen command to use.
342 @type hvparams: dict of strings
343 @param hvparams: hypervisor parameters
346 if self._cmd is None:
347 if hvparams is not None:
348 cmd = hvparams[constants.HV_XEN_CMD]
350 # TODO: Remove autoconf option once retrieving the command from
351 # the hvparams is fully implemented.
352 cmd = constants.XEN_CMD
356 if cmd not in constants.KNOWN_XEN_COMMANDS:
357 raise errors.ProgrammerError("Unknown Xen command '%s'" % cmd)
361 def _RunXen(self, args, hvparams=None):
362 """Wrapper around L{utils.process.RunCmd} to run Xen command.
364 @type hvparams: dict of strings
365 @param hvparams: dictionary of hypervisor params
366 @see: L{utils.process.RunCmd}
369 cmd = [self._GetCommand(hvparams=hvparams)]
372 return self._run_cmd_fn(cmd)
374 def _ConfigFileName(self, instance_name):
375 """Get the config file name for an instance.
377 @param instance_name: instance name
378 @type instance_name: str
379 @return: fully qualified path to instance config file
383 return utils.PathJoin(self._cfgdir, instance_name)
386 def _GetConfig(cls, instance, startup_memory, block_devices):
387 """Build Xen configuration for an instance.
390 raise NotImplementedError
392 def _WriteConfigFile(self, instance_name, data):
393 """Write the Xen config file for the instance.
395 This version of the function just writes the config file from static data.
398 # just in case it exists
399 utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
401 cfg_file = self._ConfigFileName(instance_name)
403 utils.WriteFile(cfg_file, data=data)
404 except EnvironmentError, err:
405 raise errors.HypervisorError("Cannot write Xen instance configuration"
406 " file %s: %s" % (cfg_file, err))
408 def _ReadConfigFile(self, instance_name):
409 """Returns the contents of the instance config file.
412 filename = self._ConfigFileName(instance_name)
415 file_content = utils.ReadFile(filename)
416 except EnvironmentError, err:
417 raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
421 def _RemoveConfigFile(self, instance_name):
422 """Remove the xen configuration file.
425 utils.RemoveFile(self._ConfigFileName(instance_name))
427 def _StashConfigFile(self, instance_name):
428 """Move the Xen config file to the log directory and return its new path.
431 old_filename = self._ConfigFileName(instance_name)
433 (instance_name, utils.TimestampForFilename()))
434 new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
435 utils.RenameFile(old_filename, new_filename)
438 def _GetInstanceList(self, include_node, hvparams=None):
439 """Wrapper around module level L{_GetInstanceList}.
442 return _GetInstanceList(lambda: self._RunXen(["list"], hvparams=hvparams),
445 def ListInstances(self, hvparams=None):
446 """Get the list of running instances.
449 instance_list = self._GetInstanceList(False, hvparams=hvparams)
450 names = [info[0] for info in instance_list]
453 def GetInstanceInfo(self, instance_name):
454 """Get instance properties.
456 @param instance_name: the instance name
458 @return: tuple (name, id, memory, vcpus, stat, times)
461 instance_list = self._GetInstanceList(instance_name == _DOM0_NAME)
463 for data in instance_list:
464 if data[0] == instance_name:
469 def GetAllInstancesInfo(self):
470 """Get properties of all instances.
472 @return: list of tuples (name, id, memory, vcpus, stat, times)
475 return self._GetInstanceList(False)
477 def _MakeConfigFile(self, instance, startup_memory, block_devices):
478 """Gather configuration details and write to disk.
480 See L{_GetConfig} for arguments.
484 buf.write("# Automatically generated by Ganeti. Do not edit!\n")
486 buf.write(self._GetConfig(instance, startup_memory, block_devices))
489 self._WriteConfigFile(instance.name, buf.getvalue())
491 def StartInstance(self, instance, block_devices, startup_paused):
492 """Start an instance.
495 startup_memory = self._InstanceStartupMemory(instance)
497 self._MakeConfigFile(instance, startup_memory, block_devices)
502 cmd.append(self._ConfigFileName(instance.name))
504 result = self._RunXen(cmd, hvparams=instance.hvparams)
506 # Move the Xen configuration file to the log directory to avoid
507 # leaving a stale config file behind.
508 stashed_config = self._StashConfigFile(instance.name)
509 raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved"
510 " config file to %s" %
511 (instance.name, result.fail_reason,
512 result.output, stashed_config))
514 def StopInstance(self, instance, force=False, retry=False, name=None):
521 return self._StopInstance(name, force, instance.hvparams)
523 def _StopInstance(self, name, force, hvparams):
527 @param name: name of the instance to be shutdown
529 @param force: flag specifying whether shutdown should be forced
530 @type hvparams: dict of string
531 @param hvparams: hypervisor parameters of the instance
539 result = self._RunXen([action, name], hvparams=hvparams)
541 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
542 (name, result.fail_reason, result.output))
544 # Remove configuration file if stopping/starting instance was successful
545 self._RemoveConfigFile(name)
547 def RebootInstance(self, instance):
548 """Reboot an instance.
551 ini_info = self.GetInstanceInfo(instance.name)
554 raise errors.HypervisorError("Failed to reboot instance %s,"
555 " not running" % instance.name)
557 result = self._RunXen(["reboot", instance.name], hvparams=instance.hvparams)
559 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
560 (instance.name, result.fail_reason,
563 def _CheckInstance():
564 new_info = self.GetInstanceInfo(instance.name)
566 # check if the domain ID has changed or the run time has decreased
567 if (new_info is not None and
568 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
571 raise utils.RetryAgain()
574 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
575 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
576 except utils.RetryTimeout:
577 raise errors.HypervisorError("Failed to reboot instance %s: instance"
578 " did not reboot in the expected interval" %
581 def BalloonInstanceMemory(self, instance, mem):
582 """Balloon an instance memory to a certain value.
584 @type instance: L{objects.Instance}
585 @param instance: instance to be accepted
587 @param mem: actual memory size to use for instance runtime
590 result = self._RunXen(["mem-set", instance.name, mem],
591 hvparams=instance.hvparams)
593 raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
594 (instance.name, result.fail_reason,
597 # Update configuration file
598 cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
599 cmd.append(self._ConfigFileName(instance.name))
601 result = utils.RunCmd(cmd)
603 raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
604 (instance.name, result.fail_reason,
607 def GetNodeInfo(self):
608 """Return information about the node.
610 @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
613 result = self._RunXen(["info"])
615 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
619 return _GetNodeInfo(result.stdout, self._GetInstanceList)
622 def GetInstanceConsole(cls, instance, hvparams, beparams):
623 """Return a command for connecting to the console of an instance.
626 return objects.InstanceConsole(instance=instance.name,
627 kind=constants.CONS_SSH,
628 host=instance.primary_node,
629 user=constants.SSH_CONSOLE_USER,
630 command=[pathutils.XEN_CONSOLE_WRAPPER,
631 constants.XEN_CMD, instance.name])
633 def Verify(self, hvparams=None):
634 """Verify the hypervisor.
636 For Xen, this verifies that the xend process is running.
638 @type hvparams: dict of strings
639 @param hvparams: hypervisor parameters to be verified against
641 @return: Problem description if something is wrong, C{None} otherwise
645 return "Could not verify the hypervisor, because no hvparams were" \
648 if constants.HV_XEN_CMD in hvparams:
649 xen_cmd = hvparams[constants.HV_XEN_CMD]
651 self._CheckToolstack(xen_cmd)
652 except errors.HypervisorError:
653 return "The configured xen toolstack '%s' is not available on this" \
656 result = self._RunXen(["info"], hvparams=hvparams)
658 return "Retrieving information from xen failed: %s, %s" % \
659 (result.fail_reason, result.output)
663 def MigrationInfo(self, instance):
664 """Get instance information to perform a migration.
666 @type instance: L{objects.Instance}
667 @param instance: instance to be migrated
669 @return: content of the xen config file
672 return self._ReadConfigFile(instance.name)
674 def AcceptInstance(self, instance, info, target):
675 """Prepare to accept an instance.
677 @type instance: L{objects.Instance}
678 @param instance: instance to be accepted
680 @param info: content of the xen config file on the source node
682 @param target: target host (usually ip), on this node
687 def FinalizeMigrationDst(self, instance, info, success):
688 """Finalize an instance migration.
690 After a successful migration we write the xen config file.
691 We do nothing on a failure, as we did not change anything at accept time.
693 @type instance: L{objects.Instance}
694 @param instance: instance whose migration is being finalized
696 @param info: content of the xen config file on the source node
697 @type success: boolean
698 @param success: whether the migration was a success or a failure
702 self._WriteConfigFile(instance.name, info)
704 def MigrateInstance(self, instance, target, live):
705 """Migrate an instance to a target node.
707 The migration will not be attempted if the instance is not
710 @type instance: L{objects.Instance}
711 @param instance: the instance to be migrated
713 @param target: ip address of the target node
715 @param live: perform a live migration
718 port = instance.hvparams[constants.HV_MIGRATION_PORT]
720 # TODO: Pass cluster name via RPC
721 cluster_name = ssconf.SimpleStore().GetClusterName()
723 return self._MigrateInstance(cluster_name, instance.name, target, port,
726 def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
727 _ping_fn=netutils.TcpPing):
728 """Migrate an instance to a target node.
730 @see: L{MigrateInstance} for details
733 if self.GetInstanceInfo(instance_name) is None:
734 raise errors.HypervisorError("Instance not running, cannot migrate")
736 cmd = self._GetCommand()
738 if (cmd == constants.XEN_CMD_XM and
739 not _ping_fn(target, port, live_port_needed=True)):
740 raise errors.HypervisorError("Remote host %s not listening on port"
741 " %s, cannot migrate" % (target, port))
745 if cmd == constants.XEN_CMD_XM:
746 args.extend(["-p", "%d" % port])
750 elif cmd == constants.XEN_CMD_XL:
752 "-s", constants.XL_SSH_CMD % cluster_name,
753 "-C", self._ConfigFileName(instance_name),
757 raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
759 args.extend([instance_name, target])
761 result = self._RunXen(args, hvparams=hvparams)
763 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
764 (instance_name, result.output))
766 def FinalizeMigrationSource(self, instance, success, live):
767 """Finalize the instance migration on the source node.
769 @type instance: L{objects.Instance}
770 @param instance: the instance that was migrated
772 @param success: whether the migration succeeded or not
774 @param live: whether the user requested a live migration or not
777 # pylint: disable=W0613
779 # remove old xen file after migration succeeded
781 self._RemoveConfigFile(instance.name)
782 except EnvironmentError:
783 logging.exception("Failure while removing instance config file")
785 def GetMigrationStatus(self, instance):
786 """Get the migration status
788 As MigrateInstance for Xen is still blocking, if this method is called it
789 means that MigrateInstance has completed successfully. So we can safely
790 assume that the migration was successful and notify this fact to the client.
792 @type instance: L{objects.Instance}
793 @param instance: the instance that is being migrated
794 @rtype: L{objects.MigrationStatus}
795 @return: the status of the current migration (one of
796 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
797 progress info that can be retrieved from the hypervisor
800 return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
803 def PowercycleNode(cls):
804 """Xen-specific powercycle.
806 This first does a Linux reboot (which triggers automatically a Xen
807 reboot), and if that fails it tries to do a Xen reboot. The reason
808 we don't try a Xen reboot first is that the xen reboot launches an
809 external command which connects to the Xen hypervisor, and that
810 won't work in case the root filesystem is broken and/or the xend
811 daemon is not working.
815 cls.LinuxPowercycle()
817 utils.RunCmd([constants.XEN_CMD, "debug", "R"])
819 def _CheckToolstack(self, xen_cmd):
820 """Check whether the given toolstack is available on the node.
822 @type xen_cmd: string
823 @param xen_cmd: xen command (e.g. 'xm' or 'xl')
826 binary_found = self._CheckToolstackBinary(xen_cmd)
828 raise errors.HypervisorError("No '%s' binary found on node." % xen_cmd)
829 elif xen_cmd == constants.XEN_CMD_XL:
830 if not self._CheckToolstackXlConfigured():
831 raise errors.HypervisorError("Toolstack '%s' is not enabled on this"
834 def _CheckToolstackBinary(self, xen_cmd):
835 """Checks whether the xen command's binary is found on the machine.
838 if xen_cmd not in constants.KNOWN_XEN_COMMANDS:
839 raise errors.HypervisorError("Unknown xen command '%s'." % xen_cmd)
840 result = self._run_cmd_fn(["which", xen_cmd])
841 return not result.failed
843 def _CheckToolstackXlConfigured(self):
844 """Checks whether xl is enabled on an xl-capable node.
847 @returns: C{True} if 'xl' is enabled, C{False} otherwise
850 result = self._run_cmd_fn([constants.XEN_CMD_XL, "help"])
851 if not result.failed:
854 if "toolstack" in result.stderr:
856 # xl fails for some other reason than the toolstack
858 raise errors.HypervisorError("Cannot run xen ('%s'). Error: %s."
859 % (constants.XEN_CMD_XL, result.stderr))
862 class XenPvmHypervisor(XenHypervisor):
863 """Xen PVM hypervisor interface"""
866 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
867 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
868 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
869 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
870 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
871 constants.HV_ROOT_PATH: hv_base.NO_CHECK,
872 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
873 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
874 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
875 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
876 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
877 constants.HV_REBOOT_BEHAVIOR:
878 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
879 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
880 constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
881 constants.HV_CPU_WEIGHT:
882 (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
883 constants.HV_XEN_CMD:
884 hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
887 def _GetConfig(self, instance, startup_memory, block_devices):
888 """Write the Xen config file for the instance.
891 hvp = instance.hvparams
893 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
895 # if bootloader is True, use bootloader instead of kernel and ramdisk
897 if hvp[constants.HV_USE_BOOTLOADER]:
898 # bootloader handling
899 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
901 config.write("bootloader = '%s'\n" % bootloader_path)
903 raise errors.HypervisorError("Bootloader enabled, but missing"
906 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
908 config.write("bootargs = '%s'\n" % bootloader_args)
911 kpath = hvp[constants.HV_KERNEL_PATH]
912 config.write("kernel = '%s'\n" % kpath)
915 initrd_path = hvp[constants.HV_INITRD_PATH]
917 config.write("ramdisk = '%s'\n" % initrd_path)
919 # rest of the settings
920 config.write("memory = %d\n" % startup_memory)
921 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
922 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
923 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
925 config.write("%s\n" % cpu_pinning)
926 cpu_cap = hvp[constants.HV_CPU_CAP]
928 config.write("cpu_cap=%d\n" % cpu_cap)
929 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
931 config.write("cpu_weight=%d\n" % cpu_weight)
933 config.write("name = '%s'\n" % instance.name)
936 for nic in instance.nics:
937 nic_str = "mac=%s" % (nic.mac)
938 ip = getattr(nic, "ip", None)
940 nic_str += ", ip=%s" % ip
941 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
942 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
943 vif_data.append("'%s'" % nic_str)
946 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
948 config.write("vif = [%s]\n" % ",".join(vif_data))
949 config.write("disk = [%s]\n" % ",".join(disk_data))
951 if hvp[constants.HV_ROOT_PATH]:
952 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
953 config.write("on_poweroff = 'destroy'\n")
954 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
955 config.write("on_reboot = 'restart'\n")
957 config.write("on_reboot = 'destroy'\n")
958 config.write("on_crash = 'restart'\n")
959 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
961 return config.getvalue()
964 class XenHvmHypervisor(XenHypervisor):
965 """Xen HVM hypervisor interface"""
967 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
968 pathutils.VNC_PASSWORD_FILE,
970 ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
971 pathutils.VNC_PASSWORD_FILE,
975 constants.HV_ACPI: hv_base.NO_CHECK,
976 constants.HV_BOOT_ORDER: (True, ) +
977 (lambda x: x and len(x.strip("acdn")) == 0,
978 "Invalid boot order specified, must be one or more of [acdn]",
980 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
981 constants.HV_DISK_TYPE:
982 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
983 constants.HV_NIC_TYPE:
984 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
985 constants.HV_PAE: hv_base.NO_CHECK,
986 constants.HV_VNC_BIND_ADDRESS:
987 (False, netutils.IP4Address.IsValid,
988 "VNC bind address is not a valid IP address", None, None),
989 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
990 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
991 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
992 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
993 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
994 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
995 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
996 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
997 # Add PCI passthrough
998 constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
999 constants.HV_REBOOT_BEHAVIOR:
1000 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
1001 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
1002 constants.HV_CPU_CAP: hv_base.NO_CHECK,
1003 constants.HV_CPU_WEIGHT:
1004 (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
1005 constants.HV_VIF_TYPE:
1006 hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
1007 constants.HV_XEN_CMD:
1008 hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
1011 def _GetConfig(self, instance, startup_memory, block_devices):
1012 """Create a Xen 3.1 HVM config file.
1015 hvp = instance.hvparams
1020 kpath = hvp[constants.HV_KERNEL_PATH]
1021 config.write("kernel = '%s'\n" % kpath)
1023 config.write("builder = 'hvm'\n")
1024 config.write("memory = %d\n" % startup_memory)
1025 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1026 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1027 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1029 config.write("%s\n" % cpu_pinning)
1030 cpu_cap = hvp[constants.HV_CPU_CAP]
1032 config.write("cpu_cap=%d\n" % cpu_cap)
1033 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1035 config.write("cpu_weight=%d\n" % cpu_weight)
1037 config.write("name = '%s'\n" % instance.name)
1038 if hvp[constants.HV_PAE]:
1039 config.write("pae = 1\n")
1041 config.write("pae = 0\n")
1042 if hvp[constants.HV_ACPI]:
1043 config.write("acpi = 1\n")
1045 config.write("acpi = 0\n")
1046 config.write("apic = 1\n")
1047 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1048 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1049 config.write("sdl = 0\n")
1050 config.write("usb = 1\n")
1051 config.write("usbdevice = 'tablet'\n")
1052 config.write("vnc = 1\n")
1053 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1054 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1056 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1058 if instance.network_port > constants.VNC_BASE_PORT:
1059 display = instance.network_port - constants.VNC_BASE_PORT
1060 config.write("vncdisplay = %s\n" % display)
1061 config.write("vncunused = 0\n")
1063 config.write("# vncdisplay = 1\n")
1064 config.write("vncunused = 1\n")
1066 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1068 password = utils.ReadFile(vnc_pwd_file)
1069 except EnvironmentError, err:
1070 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1071 (vnc_pwd_file, err))
1073 config.write("vncpasswd = '%s'\n" % password.rstrip())
1075 config.write("serial = 'pty'\n")
1076 if hvp[constants.HV_USE_LOCALTIME]:
1077 config.write("localtime = 1\n")
1080 # Note: what is called 'nic_type' here, is used as value for the xen nic
1081 # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1082 # the 'vif_type' to avoid a clash of notation.
1083 nic_type = hvp[constants.HV_NIC_TYPE]
1085 if nic_type is None:
1087 if hvp[constants.HV_VIF_TYPE]:
1088 vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1089 # ensure old instances don't change
1090 nic_type_str = vif_type_str
1091 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1092 nic_type_str = ", type=paravirtualized"
1094 # parameter 'model' is only valid with type 'ioemu'
1095 nic_type_str = ", model=%s, type=%s" % \
1096 (nic_type, constants.HT_HVM_VIF_IOEMU)
1097 for nic in instance.nics:
1098 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1099 ip = getattr(nic, "ip", None)
1101 nic_str += ", ip=%s" % ip
1102 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1103 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1104 vif_data.append("'%s'" % nic_str)
1106 config.write("vif = [%s]\n" % ",".join(vif_data))
1109 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1111 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1113 iso = "'file:%s,hdc:cdrom,r'" % iso_path
1114 disk_data.append(iso)
1116 config.write("disk = [%s]\n" % (",".join(disk_data)))
1117 # Add PCI passthrough
1119 pci_pass = hvp[constants.HV_PASSTHROUGH]
1121 pci_pass_arr = pci_pass.split(";")
1122 config.write("pci = %s\n" % pci_pass_arr)
1123 config.write("on_poweroff = 'destroy'\n")
1124 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1125 config.write("on_reboot = 'restart'\n")
1127 config.write("on_reboot = 'destroy'\n")
1128 config.write("on_crash = 'restart'\n")
1130 return config.getvalue()