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 _RunXmList(fn, xmllist_errors):
86 """Helper function for L{_GetXmList} to run "xm list".
89 @param fn: Function returning result of running C{xm list}
90 @type xmllist_errors: list
91 @param xmllist_errors: Error list
97 logging.error("xm list failed (%s): %s", result.fail_reason,
99 xmllist_errors.append(result)
100 raise utils.RetryAgain()
102 # skip over the heading
103 return result.stdout.splitlines()
106 def _ParseXmList(lines, include_node):
107 """Parses the output of C{xm list}.
110 @param lines: Output lines of C{xm list}
111 @type include_node: boolean
112 @param include_node: If True, return information for Dom0
113 @return: list of tuple containing (name, id, memory, vcpus, state, time
119 # Iterate through all lines while ignoring header
120 for line in lines[1:]:
121 # The format of lines is:
122 # Name ID Mem(MiB) VCPUs State Time(s)
123 # Domain-0 0 3418 4 r----- 266.2
126 raise errors.HypervisorError("Can't parse output of xm list,"
129 data[1] = int(data[1])
130 data[2] = int(data[2])
131 data[3] = int(data[3])
132 data[5] = float(data[5])
133 except (TypeError, ValueError), err:
134 raise errors.HypervisorError("Can't parse output of xm list,"
135 " line: %s, error: %s" % (line, err))
137 # skip the Domain-0 (optional)
138 if include_node or data[0] != _DOM0_NAME:
144 def _GetXmList(fn, include_node, _timeout=5):
145 """Return the list of running instances.
147 See L{_RunXmList} and L{_ParseXmList} for parameter details.
152 lines = utils.Retry(_RunXmList, (0.3, 1.5, 1.0), _timeout,
153 args=(fn, xmllist_errors))
154 except utils.RetryTimeout:
156 xmlist_result = xmllist_errors.pop()
158 errmsg = ("xm list failed, timeout exceeded (%s): %s" %
159 (xmlist_result.fail_reason, xmlist_result.output))
161 errmsg = "xm list failed"
163 raise errors.HypervisorError(errmsg)
165 return _ParseXmList(lines, include_node)
168 def _ParseNodeInfo(info):
169 """Return information about the node.
171 @return: a dict with the following keys (memory values in MiB):
172 - memory_total: the total memory size on the node
173 - memory_free: the available memory on the node for instances
174 - nr_cpus: total number of CPUs
175 - nr_nodes: in a NUMA system, the number of domains
176 - nr_sockets: the number of physical CPU sockets in the node
177 - hv_version: the hypervisor version in the form (major, minor)
181 cores_per_socket = threads_per_core = nr_cpus = None
182 xen_major, xen_minor = None, None
186 for line in info.splitlines():
187 fields = line.split(":", 1)
192 (key, val) = map(lambda s: s.strip(), fields)
194 # Note: in Xen 3, memory has changed to total_memory
195 if key in ("memory", "total_memory"):
196 memory_total = int(val)
197 elif key == "free_memory":
198 memory_free = int(val)
199 elif key == "nr_cpus":
200 nr_cpus = result["cpu_total"] = int(val)
201 elif key == "nr_nodes":
202 result["cpu_nodes"] = int(val)
203 elif key == "cores_per_socket":
204 cores_per_socket = int(val)
205 elif key == "threads_per_core":
206 threads_per_core = int(val)
207 elif key == "xen_major":
209 elif key == "xen_minor":
212 if None not in [cores_per_socket, threads_per_core, nr_cpus]:
213 result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
215 if memory_free is not None:
216 result["memory_free"] = memory_free
218 if memory_total is not None:
219 result["memory_total"] = memory_total
221 if not (xen_major is None or xen_minor is None):
222 result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
227 def _MergeInstanceInfo(info, fn):
228 """Updates node information from L{_ParseNodeInfo} with instance info.
231 @param info: Result from L{_ParseNodeInfo}
233 @param fn: Function returning result of running C{xm list}
239 for (name, _, mem, vcpus, _, _) in fn(True):
240 if name == _DOM0_NAME:
241 info["memory_dom0"] = mem
242 info["dom0_cpus"] = vcpus
244 # Include Dom0 in total memory usage
247 memory_free = info.get("memory_free")
248 memory_total = info.get("memory_total")
250 # Calculate memory used by hypervisor
251 if None not in [memory_total, memory_free, total_instmem]:
252 info["memory_hv"] = memory_total - memory_free - total_instmem
257 def _GetNodeInfo(info, fn):
258 """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
261 return _MergeInstanceInfo(_ParseNodeInfo(info), fn)
264 def _GetConfigFileDiskData(block_devices, blockdev_prefix,
265 _letters=_DISK_LETTERS):
266 """Get disk directives for Xen config file.
268 This method builds the xen config disk directive according to the
269 given disk_template and block_devices.
271 @param block_devices: list of tuples (cfdev, rldev):
272 - cfdev: dict containing ganeti config disk part
273 - rldev: ganeti.bdev.BlockDev object
274 @param blockdev_prefix: a string containing blockdevice prefix,
275 e.g. "sd" for /dev/sda
277 @return: string containing disk directive for xen instance config file
280 if len(block_devices) > len(_letters):
281 raise errors.HypervisorError("Too many disks")
285 for sd_suffix, (cfdev, dev_path) in zip(_letters, block_devices):
286 sd_name = blockdev_prefix + sd_suffix
288 if cfdev.mode == constants.DISK_RDWR:
293 if cfdev.dev_type == constants.LD_FILE:
294 driver = _FILE_DRIVER_MAP[cfdev.physical_id[0]]
298 disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode))
303 class XenHypervisor(hv_base.BaseHypervisor):
304 """Xen generic hypervisor interface
306 This is the Xen base class used for both Xen PVM and HVM. It contains
307 all the functionality that is identical for both.
311 REBOOT_RETRY_COUNT = 60
312 REBOOT_RETRY_INTERVAL = 10
319 ANCILLARY_FILES_OPT = [
323 def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
324 hv_base.BaseHypervisor.__init__(self)
327 self._cfgdir = pathutils.XEN_CONFIG_DIR
329 self._cfgdir = _cfgdir
331 if _run_cmd_fn is None:
332 self._run_cmd_fn = utils.RunCmd
334 self._run_cmd_fn = _run_cmd_fn
338 def _GetCommand(self):
339 """Returns Xen command to use.
342 if self._cmd is None:
343 # TODO: Make command a hypervisor parameter
344 cmd = constants.XEN_CMD
348 if cmd not in constants.KNOWN_XEN_COMMANDS:
349 raise errors.ProgrammerError("Unknown Xen command '%s'" % cmd)
353 def _RunXen(self, args):
354 """Wrapper around L{utils.process.RunCmd} to run Xen command.
356 @see: L{utils.process.RunCmd}
359 cmd = [self._GetCommand()]
362 return self._run_cmd_fn(cmd)
364 def _ConfigFileName(self, instance_name):
365 """Get the config file name for an instance.
367 @param instance_name: instance name
368 @type instance_name: str
369 @return: fully qualified path to instance config file
373 return utils.PathJoin(self._cfgdir, instance_name)
376 def _GetConfig(cls, instance, startup_memory, block_devices):
377 """Build Xen configuration for an instance.
380 raise NotImplementedError
382 def _WriteConfigFile(self, instance_name, data):
383 """Write the Xen config file for the instance.
385 This version of the function just writes the config file from static data.
388 # just in case it exists
389 utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
391 cfg_file = self._ConfigFileName(instance_name)
393 utils.WriteFile(cfg_file, data=data)
394 except EnvironmentError, err:
395 raise errors.HypervisorError("Cannot write Xen instance configuration"
396 " file %s: %s" % (cfg_file, err))
398 def _ReadConfigFile(self, instance_name):
399 """Returns the contents of the instance config file.
402 filename = self._ConfigFileName(instance_name)
405 file_content = utils.ReadFile(filename)
406 except EnvironmentError, err:
407 raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
411 def _RemoveConfigFile(self, instance_name):
412 """Remove the xen configuration file.
415 utils.RemoveFile(self._ConfigFileName(instance_name))
417 def _GetXmList(self, include_node):
418 """Wrapper around module level L{_GetXmList}.
421 return _GetXmList(lambda: self._RunXen(["list"]), include_node)
423 def ListInstances(self):
424 """Get the list of running instances.
427 xm_list = self._GetXmList(False)
428 names = [info[0] for info in xm_list]
431 def GetInstanceInfo(self, instance_name):
432 """Get instance properties.
434 @param instance_name: the instance name
436 @return: tuple (name, id, memory, vcpus, stat, times)
439 xm_list = self._GetXmList(instance_name == _DOM0_NAME)
442 if data[0] == instance_name:
447 def GetAllInstancesInfo(self):
448 """Get properties of all instances.
450 @return: list of tuples (name, id, memory, vcpus, stat, times)
453 xm_list = self._GetXmList(False)
456 def _MakeConfigFile(self, instance, startup_memory, block_devices):
457 """Gather configuration details and write to disk.
459 See L{_GetConfig} for arguments.
463 buf.write("# Automatically generated by Ganeti. Do not edit!\n")
465 buf.write(self._GetConfig(instance, startup_memory, block_devices))
468 self._WriteConfigFile(instance.name, buf.getvalue())
470 def StartInstance(self, instance, block_devices, startup_paused):
471 """Start an instance.
474 startup_memory = self._InstanceStartupMemory(instance)
476 self._MakeConfigFile(instance, startup_memory, block_devices)
481 cmd.append(self._ConfigFileName(instance.name))
483 result = self._RunXen(cmd)
485 raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
486 (instance.name, result.fail_reason,
489 def StopInstance(self, instance, force=False, retry=False, name=None):
496 return self._StopInstance(name, force)
498 def _StopInstance(self, name, force):
507 result = self._RunXen([action, name])
509 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
510 (name, result.fail_reason, result.output))
512 # Remove configuration file if stopping/starting instance was successful
513 self._RemoveConfigFile(name)
515 def RebootInstance(self, instance):
516 """Reboot an instance.
519 ini_info = self.GetInstanceInfo(instance.name)
522 raise errors.HypervisorError("Failed to reboot instance %s,"
523 " not running" % instance.name)
525 result = self._RunXen(["reboot", instance.name])
527 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
528 (instance.name, result.fail_reason,
531 def _CheckInstance():
532 new_info = self.GetInstanceInfo(instance.name)
534 # check if the domain ID has changed or the run time has decreased
535 if (new_info is not None and
536 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
539 raise utils.RetryAgain()
542 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
543 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
544 except utils.RetryTimeout:
545 raise errors.HypervisorError("Failed to reboot instance %s: instance"
546 " did not reboot in the expected interval" %
549 def BalloonInstanceMemory(self, instance, mem):
550 """Balloon an instance memory to a certain value.
552 @type instance: L{objects.Instance}
553 @param instance: instance to be accepted
555 @param mem: actual memory size to use for instance runtime
558 result = self._RunXen(["mem-set", instance.name, mem])
560 raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
561 (instance.name, result.fail_reason,
564 # Update configuration file
565 cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
566 cmd.append(self._ConfigFileName(instance.name))
568 result = utils.RunCmd(cmd)
570 raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
571 (instance.name, result.fail_reason,
574 def GetNodeInfo(self):
575 """Return information about the node.
577 @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
580 result = self._RunXen(["info"])
582 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
586 return _GetNodeInfo(result.stdout, self._GetXmList)
589 def GetInstanceConsole(cls, instance, hvparams, beparams):
590 """Return a command for connecting to the console of an instance.
593 return objects.InstanceConsole(instance=instance.name,
594 kind=constants.CONS_SSH,
595 host=instance.primary_node,
596 user=constants.SSH_CONSOLE_USER,
597 command=[pathutils.XEN_CONSOLE_WRAPPER,
598 constants.XEN_CMD, instance.name])
601 """Verify the hypervisor.
603 For Xen, this verifies that the xend process is running.
605 @return: Problem description if something is wrong, C{None} otherwise
608 result = self._RunXen(["info"])
610 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
614 def MigrationInfo(self, instance):
615 """Get instance information to perform a migration.
617 @type instance: L{objects.Instance}
618 @param instance: instance to be migrated
620 @return: content of the xen config file
623 return self._ReadConfigFile(instance.name)
625 def AcceptInstance(self, instance, info, target):
626 """Prepare to accept an instance.
628 @type instance: L{objects.Instance}
629 @param instance: instance to be accepted
631 @param info: content of the xen config file on the source node
633 @param target: target host (usually ip), on this node
638 def FinalizeMigrationDst(self, instance, info, success):
639 """Finalize an instance migration.
641 After a successful migration we write the xen config file.
642 We do nothing on a failure, as we did not change anything at accept time.
644 @type instance: L{objects.Instance}
645 @param instance: instance whose migration is being finalized
647 @param info: content of the xen config file on the source node
648 @type success: boolean
649 @param success: whether the migration was a success or a failure
653 self._WriteConfigFile(instance.name, info)
655 def MigrateInstance(self, instance, target, live):
656 """Migrate an instance to a target node.
658 The migration will not be attempted if the instance is not
661 @type instance: L{objects.Instance}
662 @param instance: the instance to be migrated
664 @param target: ip address of the target node
666 @param live: perform a live migration
669 port = instance.hvparams[constants.HV_MIGRATION_PORT]
671 # TODO: Pass cluster name via RPC
672 cluster_name = ssconf.SimpleStore().GetClusterName()
674 return self._MigrateInstance(cluster_name, instance.name, target, port,
677 def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
678 _ping_fn=netutils.TcpPing):
679 """Migrate an instance to a target node.
681 @see: L{MigrateInstance} for details
684 if self.GetInstanceInfo(instance_name) is None:
685 raise errors.HypervisorError("Instance not running, cannot migrate")
687 cmd = self._GetCommand()
689 if (cmd == constants.XEN_CMD_XM and
690 not _ping_fn(target, port, live_port_needed=True)):
691 raise errors.HypervisorError("Remote host %s not listening on port"
692 " %s, cannot migrate" % (target, port))
696 if cmd == constants.XEN_CMD_XM:
697 args.extend(["-p", "%d" % port])
701 elif cmd == constants.XEN_CMD_XL:
703 "-s", constants.XL_SSH_CMD % cluster_name,
704 "-C", self._ConfigFileName(instance_name),
708 raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
710 args.extend([instance_name, target])
712 result = self._RunXen(args)
714 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
715 (instance_name, result.output))
717 def FinalizeMigrationSource(self, instance, success, live):
718 """Finalize the instance migration on the source node.
720 @type instance: L{objects.Instance}
721 @param instance: the instance that was migrated
723 @param success: whether the migration succeeded or not
725 @param live: whether the user requested a live migration or not
728 # pylint: disable=W0613
730 # remove old xen file after migration succeeded
732 self._RemoveConfigFile(instance.name)
733 except EnvironmentError:
734 logging.exception("Failure while removing instance config file")
736 def GetMigrationStatus(self, instance):
737 """Get the migration status
739 As MigrateInstance for Xen is still blocking, if this method is called it
740 means that MigrateInstance has completed successfully. So we can safely
741 assume that the migration was successful and notify this fact to the client.
743 @type instance: L{objects.Instance}
744 @param instance: the instance that is being migrated
745 @rtype: L{objects.MigrationStatus}
746 @return: the status of the current migration (one of
747 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
748 progress info that can be retrieved from the hypervisor
751 return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
754 def PowercycleNode(cls):
755 """Xen-specific powercycle.
757 This first does a Linux reboot (which triggers automatically a Xen
758 reboot), and if that fails it tries to do a Xen reboot. The reason
759 we don't try a Xen reboot first is that the xen reboot launches an
760 external command which connects to the Xen hypervisor, and that
761 won't work in case the root filesystem is broken and/or the xend
762 daemon is not working.
766 cls.LinuxPowercycle()
768 utils.RunCmd([constants.XEN_CMD, "debug", "R"])
771 class XenPvmHypervisor(XenHypervisor):
772 """Xen PVM hypervisor interface"""
775 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
776 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
777 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
778 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
779 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
780 constants.HV_ROOT_PATH: hv_base.NO_CHECK,
781 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
782 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
783 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
784 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
785 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
786 constants.HV_REBOOT_BEHAVIOR:
787 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
788 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
789 constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
790 constants.HV_CPU_WEIGHT:
791 (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
794 def _GetConfig(self, instance, startup_memory, block_devices):
795 """Write the Xen config file for the instance.
798 hvp = instance.hvparams
800 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
802 # if bootloader is True, use bootloader instead of kernel and ramdisk
804 if hvp[constants.HV_USE_BOOTLOADER]:
805 # bootloader handling
806 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
808 config.write("bootloader = '%s'\n" % bootloader_path)
810 raise errors.HypervisorError("Bootloader enabled, but missing"
813 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
815 config.write("bootargs = '%s'\n" % bootloader_args)
818 kpath = hvp[constants.HV_KERNEL_PATH]
819 config.write("kernel = '%s'\n" % kpath)
822 initrd_path = hvp[constants.HV_INITRD_PATH]
824 config.write("ramdisk = '%s'\n" % initrd_path)
826 # rest of the settings
827 config.write("memory = %d\n" % startup_memory)
828 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
829 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
830 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
832 config.write("%s\n" % cpu_pinning)
833 cpu_cap = hvp[constants.HV_CPU_CAP]
835 config.write("cpu_cap=%d\n" % cpu_cap)
836 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
838 config.write("cpu_weight=%d\n" % cpu_weight)
840 config.write("name = '%s'\n" % instance.name)
843 for nic in instance.nics:
844 nic_str = "mac=%s" % (nic.mac)
845 ip = getattr(nic, "ip", None)
847 nic_str += ", ip=%s" % ip
848 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
849 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
850 vif_data.append("'%s'" % nic_str)
853 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
855 config.write("vif = [%s]\n" % ",".join(vif_data))
856 config.write("disk = [%s]\n" % ",".join(disk_data))
858 if hvp[constants.HV_ROOT_PATH]:
859 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
860 config.write("on_poweroff = 'destroy'\n")
861 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
862 config.write("on_reboot = 'restart'\n")
864 config.write("on_reboot = 'destroy'\n")
865 config.write("on_crash = 'restart'\n")
866 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
868 return config.getvalue()
871 class XenHvmHypervisor(XenHypervisor):
872 """Xen HVM hypervisor interface"""
874 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
875 pathutils.VNC_PASSWORD_FILE,
877 ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
878 pathutils.VNC_PASSWORD_FILE,
882 constants.HV_ACPI: hv_base.NO_CHECK,
883 constants.HV_BOOT_ORDER: (True, ) +
884 (lambda x: x and len(x.strip("acdn")) == 0,
885 "Invalid boot order specified, must be one or more of [acdn]",
887 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
888 constants.HV_DISK_TYPE:
889 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
890 constants.HV_NIC_TYPE:
891 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
892 constants.HV_PAE: hv_base.NO_CHECK,
893 constants.HV_VNC_BIND_ADDRESS:
894 (False, netutils.IP4Address.IsValid,
895 "VNC bind address is not a valid IP address", None, None),
896 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
897 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
898 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
899 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
900 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
901 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
902 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
903 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
904 # Add PCI passthrough
905 constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
906 constants.HV_REBOOT_BEHAVIOR:
907 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
908 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
909 constants.HV_CPU_CAP: hv_base.NO_CHECK,
910 constants.HV_CPU_WEIGHT:
911 (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
912 constants.HV_VIF_TYPE:
913 hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
916 def _GetConfig(self, instance, startup_memory, block_devices):
917 """Create a Xen 3.1 HVM config file.
920 hvp = instance.hvparams
925 kpath = hvp[constants.HV_KERNEL_PATH]
926 config.write("kernel = '%s'\n" % kpath)
928 config.write("builder = 'hvm'\n")
929 config.write("memory = %d\n" % startup_memory)
930 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
931 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
932 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
934 config.write("%s\n" % cpu_pinning)
935 cpu_cap = hvp[constants.HV_CPU_CAP]
937 config.write("cpu_cap=%d\n" % cpu_cap)
938 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
940 config.write("cpu_weight=%d\n" % cpu_weight)
942 config.write("name = '%s'\n" % instance.name)
943 if hvp[constants.HV_PAE]:
944 config.write("pae = 1\n")
946 config.write("pae = 0\n")
947 if hvp[constants.HV_ACPI]:
948 config.write("acpi = 1\n")
950 config.write("acpi = 0\n")
951 config.write("apic = 1\n")
952 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
953 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
954 config.write("sdl = 0\n")
955 config.write("usb = 1\n")
956 config.write("usbdevice = 'tablet'\n")
957 config.write("vnc = 1\n")
958 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
959 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
961 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
963 if instance.network_port > constants.VNC_BASE_PORT:
964 display = instance.network_port - constants.VNC_BASE_PORT
965 config.write("vncdisplay = %s\n" % display)
966 config.write("vncunused = 0\n")
968 config.write("# vncdisplay = 1\n")
969 config.write("vncunused = 1\n")
971 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
973 password = utils.ReadFile(vnc_pwd_file)
974 except EnvironmentError, err:
975 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
978 config.write("vncpasswd = '%s'\n" % password.rstrip())
980 config.write("serial = 'pty'\n")
981 if hvp[constants.HV_USE_LOCALTIME]:
982 config.write("localtime = 1\n")
985 # Note: what is called 'nic_type' here, is used as value for the xen nic
986 # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
987 # the 'vif_type' to avoid a clash of notation.
988 nic_type = hvp[constants.HV_NIC_TYPE]
992 if hvp[constants.HV_VIF_TYPE]:
993 vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
994 # ensure old instances don't change
995 nic_type_str = vif_type_str
996 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
997 nic_type_str = ", type=paravirtualized"
999 # parameter 'model' is only valid with type 'ioemu'
1000 nic_type_str = ", model=%s, type=%s" % \
1001 (nic_type, constants.HT_HVM_VIF_IOEMU)
1002 for nic in instance.nics:
1003 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1004 ip = getattr(nic, "ip", None)
1006 nic_str += ", ip=%s" % ip
1007 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1008 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1009 vif_data.append("'%s'" % nic_str)
1011 config.write("vif = [%s]\n" % ",".join(vif_data))
1014 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1016 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1018 iso = "'file:%s,hdc:cdrom,r'" % iso_path
1019 disk_data.append(iso)
1021 config.write("disk = [%s]\n" % (",".join(disk_data)))
1022 # Add PCI passthrough
1024 pci_pass = hvp[constants.HV_PASSTHROUGH]
1026 pci_pass_arr = pci_pass.split(";")
1027 config.write("pci = %s\n" % pci_pass_arr)
1028 config.write("on_poweroff = 'destroy'\n")
1029 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1030 config.write("on_reboot = 'restart'\n")
1032 config.write("on_reboot = 'destroy'\n")
1033 config.write("on_crash = 'restart'\n")
1035 return config.getvalue()