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 _WriteConfigFile(cls, instance, startup_memory, block_devices):
344 """Write the Xen config file for the instance.
347 raise NotImplementedError
349 def _WriteConfigFileStatic(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 StartInstance(self, instance, block_devices, startup_paused):
427 """Start an instance.
430 startup_memory = self._InstanceStartupMemory(instance)
431 self._WriteConfigFile(instance, startup_memory, block_devices)
432 cmd = [constants.XEN_CMD, "create"]
435 cmd.extend([self._ConfigFileName(instance.name)])
436 result = utils.RunCmd(cmd)
439 raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
440 (instance.name, result.fail_reason,
443 def StopInstance(self, instance, force=False, retry=False, name=None):
449 self._RemoveConfigFile(name)
451 command = [constants.XEN_CMD, "destroy", name]
453 command = [constants.XEN_CMD, "shutdown", name]
454 result = utils.RunCmd(command)
457 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
458 (name, result.fail_reason, result.output))
460 def RebootInstance(self, instance):
461 """Reboot an instance.
464 ini_info = self.GetInstanceInfo(instance.name)
467 raise errors.HypervisorError("Failed to reboot instance %s,"
468 " not running" % instance.name)
470 result = utils.RunCmd([constants.XEN_CMD, "reboot", instance.name])
472 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
473 (instance.name, result.fail_reason,
476 def _CheckInstance():
477 new_info = self.GetInstanceInfo(instance.name)
479 # check if the domain ID has changed or the run time has decreased
480 if (new_info is not None and
481 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
484 raise utils.RetryAgain()
487 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
488 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
489 except utils.RetryTimeout:
490 raise errors.HypervisorError("Failed to reboot instance %s: instance"
491 " did not reboot in the expected interval" %
494 def BalloonInstanceMemory(self, instance, mem):
495 """Balloon an instance memory to a certain value.
497 @type instance: L{objects.Instance}
498 @param instance: instance to be accepted
500 @param mem: actual memory size to use for instance runtime
503 cmd = [constants.XEN_CMD, "mem-set", instance.name, mem]
504 result = utils.RunCmd(cmd)
506 raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
507 (instance.name, result.fail_reason,
509 cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
510 cmd.append(self._ConfigFileName(instance.name))
511 result = utils.RunCmd(cmd)
513 raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
514 (instance.name, result.fail_reason,
517 def GetNodeInfo(self):
518 """Return information about the node.
520 @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
523 # TODO: Abstract running Xen command for testing
524 result = utils.RunCmd([constants.XEN_CMD, "info"])
526 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
530 return _GetNodeInfo(result.stdout, self._GetXmList)
533 def GetInstanceConsole(cls, instance, hvparams, beparams):
534 """Return a command for connecting to the console of an instance.
537 return objects.InstanceConsole(instance=instance.name,
538 kind=constants.CONS_SSH,
539 host=instance.primary_node,
540 user=constants.SSH_CONSOLE_USER,
541 command=[pathutils.XEN_CONSOLE_WRAPPER,
542 constants.XEN_CMD, instance.name])
545 """Verify the hypervisor.
547 For Xen, this verifies that the xend process is running.
549 @return: Problem description if something is wrong, C{None} otherwise
552 result = utils.RunCmd([constants.XEN_CMD, "info"])
554 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
558 def MigrationInfo(self, instance):
559 """Get instance information to perform a migration.
561 @type instance: L{objects.Instance}
562 @param instance: instance to be migrated
564 @return: content of the xen config file
567 return self._ReadConfigFile(instance.name)
569 def AcceptInstance(self, instance, info, target):
570 """Prepare to accept an instance.
572 @type instance: L{objects.Instance}
573 @param instance: instance to be accepted
575 @param info: content of the xen config file on the source node
577 @param target: target host (usually ip), on this node
582 def FinalizeMigrationDst(self, instance, info, success):
583 """Finalize an instance migration.
585 After a successful migration we write the xen config file.
586 We do nothing on a failure, as we did not change anything at accept time.
588 @type instance: L{objects.Instance}
589 @param instance: instance whose migration is being finalized
591 @param info: content of the xen config file on the source node
592 @type success: boolean
593 @param success: whether the migration was a success or a failure
597 self._WriteConfigFileStatic(instance.name, info)
599 def MigrateInstance(self, instance, target, live):
600 """Migrate an instance to a target node.
602 The migration will not be attempted if the instance is not
605 @type instance: L{objects.Instance}
606 @param instance: the instance to be migrated
608 @param target: ip address of the target node
610 @param live: perform a live migration
613 if self.GetInstanceInfo(instance.name) is None:
614 raise errors.HypervisorError("Instance not running, cannot migrate")
616 port = instance.hvparams[constants.HV_MIGRATION_PORT]
618 if (constants.XEN_CMD == constants.XEN_CMD_XM and
619 not netutils.TcpPing(target, port, live_port_needed=True)):
620 raise errors.HypervisorError("Remote host %s not listening on port"
621 " %s, cannot migrate" % (target, port))
623 args = [constants.XEN_CMD, "migrate"]
624 if constants.XEN_CMD == constants.XEN_CMD_XM:
625 args.extend(["-p", "%d" % port])
628 elif constants.XEN_CMD == constants.XEN_CMD_XL:
629 cluster_name = ssconf.SimpleStore().GetClusterName()
630 args.extend(["-s", constants.XL_SSH_CMD % cluster_name])
631 args.extend(["-C", self._ConfigFileName(instance.name)])
633 raise errors.HypervisorError("Unsupported xen command: %s" %
636 args.extend([instance.name, target])
637 result = utils.RunCmd(args)
639 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
640 (instance.name, result.output))
642 def FinalizeMigrationSource(self, instance, success, live):
643 """Finalize the instance migration on the source node.
645 @type instance: L{objects.Instance}
646 @param instance: the instance that was migrated
648 @param success: whether the migration succeeded or not
650 @param live: whether the user requested a live migration or not
653 # pylint: disable=W0613
655 # remove old xen file after migration succeeded
657 self._RemoveConfigFile(instance.name)
658 except EnvironmentError:
659 logging.exception("Failure while removing instance config file")
661 def GetMigrationStatus(self, instance):
662 """Get the migration status
664 As MigrateInstance for Xen is still blocking, if this method is called it
665 means that MigrateInstance has completed successfully. So we can safely
666 assume that the migration was successful and notify this fact to the client.
668 @type instance: L{objects.Instance}
669 @param instance: the instance that is being migrated
670 @rtype: L{objects.MigrationStatus}
671 @return: the status of the current migration (one of
672 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
673 progress info that can be retrieved from the hypervisor
676 return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
679 def PowercycleNode(cls):
680 """Xen-specific powercycle.
682 This first does a Linux reboot (which triggers automatically a Xen
683 reboot), and if that fails it tries to do a Xen reboot. The reason
684 we don't try a Xen reboot first is that the xen reboot launches an
685 external command which connects to the Xen hypervisor, and that
686 won't work in case the root filesystem is broken and/or the xend
687 daemon is not working.
691 cls.LinuxPowercycle()
693 utils.RunCmd([constants.XEN_CMD, "debug", "R"])
696 class XenPvmHypervisor(XenHypervisor):
697 """Xen PVM hypervisor interface"""
700 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
701 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
702 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
703 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
704 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
705 constants.HV_ROOT_PATH: hv_base.NO_CHECK,
706 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
707 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
708 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
709 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
710 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
711 constants.HV_REBOOT_BEHAVIOR:
712 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
713 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
714 constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
715 constants.HV_CPU_WEIGHT:
716 (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
719 def _WriteConfigFile(self, instance, startup_memory, block_devices):
720 """Write the Xen config file for the instance.
723 hvp = instance.hvparams
725 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
727 # if bootloader is True, use bootloader instead of kernel and ramdisk
729 if hvp[constants.HV_USE_BOOTLOADER]:
730 # bootloader handling
731 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
733 config.write("bootloader = '%s'\n" % bootloader_path)
735 raise errors.HypervisorError("Bootloader enabled, but missing"
738 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
740 config.write("bootargs = '%s'\n" % bootloader_args)
743 kpath = hvp[constants.HV_KERNEL_PATH]
744 config.write("kernel = '%s'\n" % kpath)
747 initrd_path = hvp[constants.HV_INITRD_PATH]
749 config.write("ramdisk = '%s'\n" % initrd_path)
751 # rest of the settings
752 config.write("memory = %d\n" % startup_memory)
753 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
754 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
755 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
757 config.write("%s\n" % cpu_pinning)
758 cpu_cap = hvp[constants.HV_CPU_CAP]
760 config.write("cpu_cap=%d\n" % cpu_cap)
761 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
763 config.write("cpu_weight=%d\n" % cpu_weight)
765 config.write("name = '%s'\n" % instance.name)
768 for nic in instance.nics:
769 nic_str = "mac=%s" % (nic.mac)
770 ip = getattr(nic, "ip", None)
772 nic_str += ", ip=%s" % ip
773 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
774 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
775 vif_data.append("'%s'" % nic_str)
778 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
780 config.write("vif = [%s]\n" % ",".join(vif_data))
781 config.write("disk = [%s]\n" % ",".join(disk_data))
783 if hvp[constants.HV_ROOT_PATH]:
784 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
785 config.write("on_poweroff = 'destroy'\n")
786 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
787 config.write("on_reboot = 'restart'\n")
789 config.write("on_reboot = 'destroy'\n")
790 config.write("on_crash = 'restart'\n")
791 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
792 self._WriteConfigFileStatic(instance.name, config.getvalue())
797 class XenHvmHypervisor(XenHypervisor):
798 """Xen HVM hypervisor interface"""
800 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
801 pathutils.VNC_PASSWORD_FILE,
803 ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
804 pathutils.VNC_PASSWORD_FILE,
808 constants.HV_ACPI: hv_base.NO_CHECK,
809 constants.HV_BOOT_ORDER: (True, ) +
810 (lambda x: x and len(x.strip("acdn")) == 0,
811 "Invalid boot order specified, must be one or more of [acdn]",
813 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
814 constants.HV_DISK_TYPE:
815 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
816 constants.HV_NIC_TYPE:
817 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
818 constants.HV_PAE: hv_base.NO_CHECK,
819 constants.HV_VNC_BIND_ADDRESS:
820 (False, netutils.IP4Address.IsValid,
821 "VNC bind address is not a valid IP address", None, None),
822 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
823 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
824 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
825 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
826 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
827 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
828 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
829 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
830 # Add PCI passthrough
831 constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
832 constants.HV_REBOOT_BEHAVIOR:
833 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
834 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
835 constants.HV_CPU_CAP: hv_base.NO_CHECK,
836 constants.HV_CPU_WEIGHT:
837 (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
840 def _WriteConfigFile(self, instance, startup_memory, block_devices):
841 """Create a Xen 3.1 HVM config file.
844 hvp = instance.hvparams
847 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
850 kpath = hvp[constants.HV_KERNEL_PATH]
851 config.write("kernel = '%s'\n" % kpath)
853 config.write("builder = 'hvm'\n")
854 config.write("memory = %d\n" % startup_memory)
855 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
856 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
857 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
859 config.write("%s\n" % cpu_pinning)
860 cpu_cap = hvp[constants.HV_CPU_CAP]
862 config.write("cpu_cap=%d\n" % cpu_cap)
863 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
865 config.write("cpu_weight=%d\n" % cpu_weight)
867 config.write("name = '%s'\n" % instance.name)
868 if hvp[constants.HV_PAE]:
869 config.write("pae = 1\n")
871 config.write("pae = 0\n")
872 if hvp[constants.HV_ACPI]:
873 config.write("acpi = 1\n")
875 config.write("acpi = 0\n")
876 config.write("apic = 1\n")
877 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
878 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
879 config.write("sdl = 0\n")
880 config.write("usb = 1\n")
881 config.write("usbdevice = 'tablet'\n")
882 config.write("vnc = 1\n")
883 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
884 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
886 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
888 if instance.network_port > constants.VNC_BASE_PORT:
889 display = instance.network_port - constants.VNC_BASE_PORT
890 config.write("vncdisplay = %s\n" % display)
891 config.write("vncunused = 0\n")
893 config.write("# vncdisplay = 1\n")
894 config.write("vncunused = 1\n")
896 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
898 password = utils.ReadFile(vnc_pwd_file)
899 except EnvironmentError, err:
900 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
903 config.write("vncpasswd = '%s'\n" % password.rstrip())
905 config.write("serial = 'pty'\n")
906 if hvp[constants.HV_USE_LOCALTIME]:
907 config.write("localtime = 1\n")
910 nic_type = hvp[constants.HV_NIC_TYPE]
912 # ensure old instances don't change
913 nic_type_str = ", type=ioemu"
914 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
915 nic_type_str = ", type=paravirtualized"
917 nic_type_str = ", model=%s, type=ioemu" % nic_type
918 for nic in instance.nics:
919 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
920 ip = getattr(nic, "ip", None)
922 nic_str += ", ip=%s" % ip
923 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
924 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
925 vif_data.append("'%s'" % nic_str)
927 config.write("vif = [%s]\n" % ",".join(vif_data))
930 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
932 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
934 iso = "'file:%s,hdc:cdrom,r'" % iso_path
935 disk_data.append(iso)
937 config.write("disk = [%s]\n" % (",".join(disk_data)))
938 # Add PCI passthrough
940 pci_pass = hvp[constants.HV_PASSTHROUGH]
942 pci_pass_arr = pci_pass.split(";")
943 config.write("pci = %s\n" % pci_pass_arr)
944 config.write("on_poweroff = 'destroy'\n")
945 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
946 config.write("on_reboot = 'restart'\n")
948 config.write("on_reboot = 'destroy'\n")
949 config.write("on_crash = 'restart'\n")
950 self._WriteConfigFileStatic(instance.name, config.getvalue())