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):
324 hv_base.BaseHypervisor.__init__(self)
327 self._cfgdir = pathutils.XEN_CONFIG_DIR
329 self._cfgdir = _cfgdir
331 def _ConfigFileName(self, instance_name):
332 """Get the config file name for an instance.
334 @param instance_name: instance name
335 @type instance_name: str
336 @return: fully qualified path to instance config file
340 return utils.PathJoin(self._cfgdir, instance_name)
343 def _GetConfig(cls, instance, startup_memory, block_devices):
344 """Build Xen configuration for an instance.
347 raise NotImplementedError
349 def _WriteConfigFile(self, instance_name, data):
350 """Write the Xen config file for the instance.
352 This version of the function just writes the config file from static data.
355 # just in case it exists
356 utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
358 cfg_file = self._ConfigFileName(instance_name)
360 utils.WriteFile(cfg_file, data=data)
361 except EnvironmentError, err:
362 raise errors.HypervisorError("Cannot write Xen instance configuration"
363 " file %s: %s" % (cfg_file, err))
365 def _ReadConfigFile(self, instance_name):
366 """Returns the contents of the instance config file.
369 filename = self._ConfigFileName(instance_name)
372 file_content = utils.ReadFile(filename)
373 except EnvironmentError, err:
374 raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
378 def _RemoveConfigFile(self, instance_name):
379 """Remove the xen configuration file.
382 utils.RemoveFile(self._ConfigFileName(instance_name))
385 def _GetXmList(include_node):
386 """Wrapper around module level L{_GetXmList}.
389 # TODO: Abstract running Xen command for testing
390 return _GetXmList(lambda: utils.RunCmd([constants.XEN_CMD, "list"]),
393 def ListInstances(self):
394 """Get the list of running instances.
397 xm_list = self._GetXmList(False)
398 names = [info[0] for info in xm_list]
401 def GetInstanceInfo(self, instance_name):
402 """Get instance properties.
404 @param instance_name: the instance name
406 @return: tuple (name, id, memory, vcpus, stat, times)
409 xm_list = self._GetXmList(instance_name == _DOM0_NAME)
412 if data[0] == instance_name:
417 def GetAllInstancesInfo(self):
418 """Get properties of all instances.
420 @return: list of tuples (name, id, memory, vcpus, stat, times)
423 xm_list = self._GetXmList(False)
426 def _MakeConfigFile(self, instance, startup_memory, block_devices):
427 """Gather configuration details and write to disk.
429 See L{_GetConfig} for arguments.
433 buf.write("# Automatically generated by Ganeti. Do not edit!\n")
435 buf.write(self._GetConfig(instance, startup_memory, block_devices))
438 self._WriteConfigFile(instance.name, buf.getvalue())
440 def StartInstance(self, instance, block_devices, startup_paused):
441 """Start an instance.
444 startup_memory = self._InstanceStartupMemory(instance)
446 self._MakeConfigFile(instance, startup_memory, block_devices)
448 cmd = [constants.XEN_CMD, "create"]
451 cmd.extend([self._ConfigFileName(instance.name)])
452 result = utils.RunCmd(cmd)
455 raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
456 (instance.name, result.fail_reason,
459 def StopInstance(self, instance, force=False, retry=False, name=None):
465 self._RemoveConfigFile(name)
467 command = [constants.XEN_CMD, "destroy", name]
469 command = [constants.XEN_CMD, "shutdown", name]
470 result = utils.RunCmd(command)
473 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
474 (name, result.fail_reason, result.output))
476 def RebootInstance(self, instance):
477 """Reboot an instance.
480 ini_info = self.GetInstanceInfo(instance.name)
483 raise errors.HypervisorError("Failed to reboot instance %s,"
484 " not running" % instance.name)
486 result = utils.RunCmd([constants.XEN_CMD, "reboot", instance.name])
488 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
489 (instance.name, result.fail_reason,
492 def _CheckInstance():
493 new_info = self.GetInstanceInfo(instance.name)
495 # check if the domain ID has changed or the run time has decreased
496 if (new_info is not None and
497 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
500 raise utils.RetryAgain()
503 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
504 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
505 except utils.RetryTimeout:
506 raise errors.HypervisorError("Failed to reboot instance %s: instance"
507 " did not reboot in the expected interval" %
510 def BalloonInstanceMemory(self, instance, mem):
511 """Balloon an instance memory to a certain value.
513 @type instance: L{objects.Instance}
514 @param instance: instance to be accepted
516 @param mem: actual memory size to use for instance runtime
519 cmd = [constants.XEN_CMD, "mem-set", instance.name, mem]
520 result = utils.RunCmd(cmd)
522 raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
523 (instance.name, result.fail_reason,
525 cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
526 cmd.append(self._ConfigFileName(instance.name))
527 result = utils.RunCmd(cmd)
529 raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
530 (instance.name, result.fail_reason,
533 def GetNodeInfo(self):
534 """Return information about the node.
536 @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
539 # TODO: Abstract running Xen command for testing
540 result = utils.RunCmd([constants.XEN_CMD, "info"])
542 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
546 return _GetNodeInfo(result.stdout, self._GetXmList)
549 def GetInstanceConsole(cls, instance, hvparams, beparams):
550 """Return a command for connecting to the console of an instance.
553 return objects.InstanceConsole(instance=instance.name,
554 kind=constants.CONS_SSH,
555 host=instance.primary_node,
556 user=constants.SSH_CONSOLE_USER,
557 command=[pathutils.XEN_CONSOLE_WRAPPER,
558 constants.XEN_CMD, instance.name])
561 """Verify the hypervisor.
563 For Xen, this verifies that the xend process is running.
565 @return: Problem description if something is wrong, C{None} otherwise
568 result = utils.RunCmd([constants.XEN_CMD, "info"])
570 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
574 def MigrationInfo(self, instance):
575 """Get instance information to perform a migration.
577 @type instance: L{objects.Instance}
578 @param instance: instance to be migrated
580 @return: content of the xen config file
583 return self._ReadConfigFile(instance.name)
585 def AcceptInstance(self, instance, info, target):
586 """Prepare to accept an instance.
588 @type instance: L{objects.Instance}
589 @param instance: instance to be accepted
591 @param info: content of the xen config file on the source node
593 @param target: target host (usually ip), on this node
598 def FinalizeMigrationDst(self, instance, info, success):
599 """Finalize an instance migration.
601 After a successful migration we write the xen config file.
602 We do nothing on a failure, as we did not change anything at accept time.
604 @type instance: L{objects.Instance}
605 @param instance: instance whose migration is being finalized
607 @param info: content of the xen config file on the source node
608 @type success: boolean
609 @param success: whether the migration was a success or a failure
613 self._WriteConfigFile(instance.name, info)
615 def MigrateInstance(self, instance, target, live):
616 """Migrate an instance to a target node.
618 The migration will not be attempted if the instance is not
621 @type instance: L{objects.Instance}
622 @param instance: the instance to be migrated
624 @param target: ip address of the target node
626 @param live: perform a live migration
629 if self.GetInstanceInfo(instance.name) is None:
630 raise errors.HypervisorError("Instance not running, cannot migrate")
632 port = instance.hvparams[constants.HV_MIGRATION_PORT]
634 if (constants.XEN_CMD == constants.XEN_CMD_XM and
635 not netutils.TcpPing(target, port, live_port_needed=True)):
636 raise errors.HypervisorError("Remote host %s not listening on port"
637 " %s, cannot migrate" % (target, port))
639 args = [constants.XEN_CMD, "migrate"]
640 if constants.XEN_CMD == constants.XEN_CMD_XM:
641 args.extend(["-p", "%d" % port])
644 elif constants.XEN_CMD == constants.XEN_CMD_XL:
645 cluster_name = ssconf.SimpleStore().GetClusterName()
646 args.extend(["-s", constants.XL_SSH_CMD % cluster_name])
647 args.extend(["-C", self._ConfigFileName(instance.name)])
649 raise errors.HypervisorError("Unsupported xen command: %s" %
652 args.extend([instance.name, target])
653 result = utils.RunCmd(args)
655 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
656 (instance.name, result.output))
658 def FinalizeMigrationSource(self, instance, success, live):
659 """Finalize the instance migration on the source node.
661 @type instance: L{objects.Instance}
662 @param instance: the instance that was migrated
664 @param success: whether the migration succeeded or not
666 @param live: whether the user requested a live migration or not
669 # pylint: disable=W0613
671 # remove old xen file after migration succeeded
673 self._RemoveConfigFile(instance.name)
674 except EnvironmentError:
675 logging.exception("Failure while removing instance config file")
677 def GetMigrationStatus(self, instance):
678 """Get the migration status
680 As MigrateInstance for Xen is still blocking, if this method is called it
681 means that MigrateInstance has completed successfully. So we can safely
682 assume that the migration was successful and notify this fact to the client.
684 @type instance: L{objects.Instance}
685 @param instance: the instance that is being migrated
686 @rtype: L{objects.MigrationStatus}
687 @return: the status of the current migration (one of
688 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
689 progress info that can be retrieved from the hypervisor
692 return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
695 def PowercycleNode(cls):
696 """Xen-specific powercycle.
698 This first does a Linux reboot (which triggers automatically a Xen
699 reboot), and if that fails it tries to do a Xen reboot. The reason
700 we don't try a Xen reboot first is that the xen reboot launches an
701 external command which connects to the Xen hypervisor, and that
702 won't work in case the root filesystem is broken and/or the xend
703 daemon is not working.
707 cls.LinuxPowercycle()
709 utils.RunCmd([constants.XEN_CMD, "debug", "R"])
712 class XenPvmHypervisor(XenHypervisor):
713 """Xen PVM hypervisor interface"""
716 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
717 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
718 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
719 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
720 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
721 constants.HV_ROOT_PATH: hv_base.NO_CHECK,
722 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
723 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
724 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
725 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
726 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
727 constants.HV_REBOOT_BEHAVIOR:
728 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
729 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
730 constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
731 constants.HV_CPU_WEIGHT:
732 (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
735 def _GetConfig(self, instance, startup_memory, block_devices):
736 """Write the Xen config file for the instance.
739 hvp = instance.hvparams
741 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
743 # if bootloader is True, use bootloader instead of kernel and ramdisk
745 if hvp[constants.HV_USE_BOOTLOADER]:
746 # bootloader handling
747 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
749 config.write("bootloader = '%s'\n" % bootloader_path)
751 raise errors.HypervisorError("Bootloader enabled, but missing"
754 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
756 config.write("bootargs = '%s'\n" % bootloader_args)
759 kpath = hvp[constants.HV_KERNEL_PATH]
760 config.write("kernel = '%s'\n" % kpath)
763 initrd_path = hvp[constants.HV_INITRD_PATH]
765 config.write("ramdisk = '%s'\n" % initrd_path)
767 # rest of the settings
768 config.write("memory = %d\n" % startup_memory)
769 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
770 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
771 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
773 config.write("%s\n" % cpu_pinning)
774 cpu_cap = hvp[constants.HV_CPU_CAP]
776 config.write("cpu_cap=%d\n" % cpu_cap)
777 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
779 config.write("cpu_weight=%d\n" % cpu_weight)
781 config.write("name = '%s'\n" % instance.name)
784 for nic in instance.nics:
785 nic_str = "mac=%s" % (nic.mac)
786 ip = getattr(nic, "ip", None)
788 nic_str += ", ip=%s" % ip
789 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
790 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
791 vif_data.append("'%s'" % nic_str)
794 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
796 config.write("vif = [%s]\n" % ",".join(vif_data))
797 config.write("disk = [%s]\n" % ",".join(disk_data))
799 if hvp[constants.HV_ROOT_PATH]:
800 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
801 config.write("on_poweroff = 'destroy'\n")
802 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
803 config.write("on_reboot = 'restart'\n")
805 config.write("on_reboot = 'destroy'\n")
806 config.write("on_crash = 'restart'\n")
807 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
809 return config.getvalue()
812 class XenHvmHypervisor(XenHypervisor):
813 """Xen HVM hypervisor interface"""
815 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
816 pathutils.VNC_PASSWORD_FILE,
818 ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
819 pathutils.VNC_PASSWORD_FILE,
823 constants.HV_ACPI: hv_base.NO_CHECK,
824 constants.HV_BOOT_ORDER: (True, ) +
825 (lambda x: x and len(x.strip("acdn")) == 0,
826 "Invalid boot order specified, must be one or more of [acdn]",
828 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
829 constants.HV_DISK_TYPE:
830 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
831 constants.HV_NIC_TYPE:
832 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
833 constants.HV_PAE: hv_base.NO_CHECK,
834 constants.HV_VNC_BIND_ADDRESS:
835 (False, netutils.IP4Address.IsValid,
836 "VNC bind address is not a valid IP address", None, None),
837 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
838 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
839 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
840 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
841 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
842 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
843 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
844 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
845 # Add PCI passthrough
846 constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
847 constants.HV_REBOOT_BEHAVIOR:
848 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
849 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
850 constants.HV_CPU_CAP: hv_base.NO_CHECK,
851 constants.HV_CPU_WEIGHT:
852 (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
855 def _GetConfig(self, instance, startup_memory, block_devices):
856 """Create a Xen 3.1 HVM config file.
859 hvp = instance.hvparams
864 kpath = hvp[constants.HV_KERNEL_PATH]
865 config.write("kernel = '%s'\n" % kpath)
867 config.write("builder = 'hvm'\n")
868 config.write("memory = %d\n" % startup_memory)
869 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
870 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
871 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
873 config.write("%s\n" % cpu_pinning)
874 cpu_cap = hvp[constants.HV_CPU_CAP]
876 config.write("cpu_cap=%d\n" % cpu_cap)
877 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
879 config.write("cpu_weight=%d\n" % cpu_weight)
881 config.write("name = '%s'\n" % instance.name)
882 if hvp[constants.HV_PAE]:
883 config.write("pae = 1\n")
885 config.write("pae = 0\n")
886 if hvp[constants.HV_ACPI]:
887 config.write("acpi = 1\n")
889 config.write("acpi = 0\n")
890 config.write("apic = 1\n")
891 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
892 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
893 config.write("sdl = 0\n")
894 config.write("usb = 1\n")
895 config.write("usbdevice = 'tablet'\n")
896 config.write("vnc = 1\n")
897 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
898 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
900 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
902 if instance.network_port > constants.VNC_BASE_PORT:
903 display = instance.network_port - constants.VNC_BASE_PORT
904 config.write("vncdisplay = %s\n" % display)
905 config.write("vncunused = 0\n")
907 config.write("# vncdisplay = 1\n")
908 config.write("vncunused = 1\n")
910 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
912 password = utils.ReadFile(vnc_pwd_file)
913 except EnvironmentError, err:
914 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
917 config.write("vncpasswd = '%s'\n" % password.rstrip())
919 config.write("serial = 'pty'\n")
920 if hvp[constants.HV_USE_LOCALTIME]:
921 config.write("localtime = 1\n")
924 nic_type = hvp[constants.HV_NIC_TYPE]
926 # ensure old instances don't change
927 nic_type_str = ", type=ioemu"
928 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
929 nic_type_str = ", type=paravirtualized"
931 nic_type_str = ", model=%s, type=ioemu" % nic_type
932 for nic in instance.nics:
933 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
934 ip = getattr(nic, "ip", None)
936 nic_str += ", ip=%s" % ip
937 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
938 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
939 vif_data.append("'%s'" % nic_str)
941 config.write("vif = [%s]\n" % ",".join(vif_data))
944 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
946 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
948 iso = "'file:%s,hdc:cdrom,r'" % iso_path
949 disk_data.append(iso)
951 config.write("disk = [%s]\n" % (",".join(disk_data)))
952 # Add PCI passthrough
954 pci_pass = hvp[constants.HV_PASSTHROUGH]
956 pci_pass_arr = pci_pass.split(";")
957 config.write("pci = %s\n" % pci_pass_arr)
958 config.write("on_poweroff = 'destroy'\n")
959 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
960 config.write("on_reboot = 'restart'\n")
962 config.write("on_reboot = 'destroy'\n")
963 config.write("on_crash = 'restart'\n")
965 return config.getvalue()