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 _StashConfigFile(self, instance_name):
418 """Move the Xen config file to the log directory and return its new path.
421 old_filename = self._ConfigFileName(instance_name)
423 (instance_name, utils.TimestampForFilename()))
424 new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
425 utils.RenameFile(old_filename, new_filename)
428 def _GetXmList(self, include_node):
429 """Wrapper around module level L{_GetXmList}.
432 return _GetXmList(lambda: self._RunXen(["list"]), include_node)
434 def ListInstances(self):
435 """Get the list of running instances.
438 xm_list = self._GetXmList(False)
439 names = [info[0] for info in xm_list]
442 def GetInstanceInfo(self, instance_name):
443 """Get instance properties.
445 @param instance_name: the instance name
447 @return: tuple (name, id, memory, vcpus, stat, times)
450 xm_list = self._GetXmList(instance_name == _DOM0_NAME)
453 if data[0] == instance_name:
458 def GetAllInstancesInfo(self):
459 """Get properties of all instances.
461 @return: list of tuples (name, id, memory, vcpus, stat, times)
464 xm_list = self._GetXmList(False)
467 def _MakeConfigFile(self, instance, startup_memory, block_devices):
468 """Gather configuration details and write to disk.
470 See L{_GetConfig} for arguments.
474 buf.write("# Automatically generated by Ganeti. Do not edit!\n")
476 buf.write(self._GetConfig(instance, startup_memory, block_devices))
479 self._WriteConfigFile(instance.name, buf.getvalue())
481 def StartInstance(self, instance, block_devices, startup_paused):
482 """Start an instance.
485 startup_memory = self._InstanceStartupMemory(instance)
487 self._MakeConfigFile(instance, startup_memory, block_devices)
492 cmd.append(self._ConfigFileName(instance.name))
494 result = self._RunXen(cmd)
496 # Move the Xen configuration file to the log directory to avoid
497 # leaving a stale config file behind.
498 stashed_config = self._StashConfigFile(instance.name)
499 raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved"
500 " config file to %s" %
501 (instance.name, result.fail_reason,
502 result.output, stashed_config))
504 def StopInstance(self, instance, force=False, retry=False, name=None):
511 return self._StopInstance(name, force)
513 def _StopInstance(self, name, force):
522 result = self._RunXen([action, name])
524 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
525 (name, result.fail_reason, result.output))
527 # Remove configuration file if stopping/starting instance was successful
528 self._RemoveConfigFile(name)
530 def RebootInstance(self, instance):
531 """Reboot an instance.
534 ini_info = self.GetInstanceInfo(instance.name)
537 raise errors.HypervisorError("Failed to reboot instance %s,"
538 " not running" % instance.name)
540 result = self._RunXen(["reboot", instance.name])
542 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
543 (instance.name, result.fail_reason,
546 def _CheckInstance():
547 new_info = self.GetInstanceInfo(instance.name)
549 # check if the domain ID has changed or the run time has decreased
550 if (new_info is not None and
551 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
554 raise utils.RetryAgain()
557 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
558 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
559 except utils.RetryTimeout:
560 raise errors.HypervisorError("Failed to reboot instance %s: instance"
561 " did not reboot in the expected interval" %
564 def BalloonInstanceMemory(self, instance, mem):
565 """Balloon an instance memory to a certain value.
567 @type instance: L{objects.Instance}
568 @param instance: instance to be accepted
570 @param mem: actual memory size to use for instance runtime
573 result = self._RunXen(["mem-set", instance.name, mem])
575 raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
576 (instance.name, result.fail_reason,
579 # Update configuration file
580 cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
581 cmd.append(self._ConfigFileName(instance.name))
583 result = utils.RunCmd(cmd)
585 raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
586 (instance.name, result.fail_reason,
589 def GetNodeInfo(self):
590 """Return information about the node.
592 @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
595 result = self._RunXen(["info"])
597 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
601 return _GetNodeInfo(result.stdout, self._GetXmList)
604 def GetInstanceConsole(cls, instance, hvparams, beparams):
605 """Return a command for connecting to the console of an instance.
608 return objects.InstanceConsole(instance=instance.name,
609 kind=constants.CONS_SSH,
610 host=instance.primary_node,
611 user=constants.SSH_CONSOLE_USER,
612 command=[pathutils.XEN_CONSOLE_WRAPPER,
613 constants.XEN_CMD, instance.name])
616 """Verify the hypervisor.
618 For Xen, this verifies that the xend process is running.
620 @return: Problem description if something is wrong, C{None} otherwise
623 result = self._RunXen(["info"])
625 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
629 def MigrationInfo(self, instance):
630 """Get instance information to perform a migration.
632 @type instance: L{objects.Instance}
633 @param instance: instance to be migrated
635 @return: content of the xen config file
638 return self._ReadConfigFile(instance.name)
640 def AcceptInstance(self, instance, info, target):
641 """Prepare to accept an instance.
643 @type instance: L{objects.Instance}
644 @param instance: instance to be accepted
646 @param info: content of the xen config file on the source node
648 @param target: target host (usually ip), on this node
653 def FinalizeMigrationDst(self, instance, info, success):
654 """Finalize an instance migration.
656 After a successful migration we write the xen config file.
657 We do nothing on a failure, as we did not change anything at accept time.
659 @type instance: L{objects.Instance}
660 @param instance: instance whose migration is being finalized
662 @param info: content of the xen config file on the source node
663 @type success: boolean
664 @param success: whether the migration was a success or a failure
668 self._WriteConfigFile(instance.name, info)
670 def MigrateInstance(self, instance, target, live):
671 """Migrate an instance to a target node.
673 The migration will not be attempted if the instance is not
676 @type instance: L{objects.Instance}
677 @param instance: the instance to be migrated
679 @param target: ip address of the target node
681 @param live: perform a live migration
684 port = instance.hvparams[constants.HV_MIGRATION_PORT]
686 # TODO: Pass cluster name via RPC
687 cluster_name = ssconf.SimpleStore().GetClusterName()
689 return self._MigrateInstance(cluster_name, instance.name, target, port,
692 def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
693 _ping_fn=netutils.TcpPing):
694 """Migrate an instance to a target node.
696 @see: L{MigrateInstance} for details
699 if self.GetInstanceInfo(instance_name) is None:
700 raise errors.HypervisorError("Instance not running, cannot migrate")
702 cmd = self._GetCommand()
704 if (cmd == constants.XEN_CMD_XM and
705 not _ping_fn(target, port, live_port_needed=True)):
706 raise errors.HypervisorError("Remote host %s not listening on port"
707 " %s, cannot migrate" % (target, port))
711 if cmd == constants.XEN_CMD_XM:
712 args.extend(["-p", "%d" % port])
716 elif cmd == constants.XEN_CMD_XL:
718 "-s", constants.XL_SSH_CMD % cluster_name,
719 "-C", self._ConfigFileName(instance_name),
723 raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
725 args.extend([instance_name, target])
727 result = self._RunXen(args)
729 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
730 (instance_name, result.output))
732 def FinalizeMigrationSource(self, instance, success, live):
733 """Finalize the instance migration on the source node.
735 @type instance: L{objects.Instance}
736 @param instance: the instance that was migrated
738 @param success: whether the migration succeeded or not
740 @param live: whether the user requested a live migration or not
743 # pylint: disable=W0613
745 # remove old xen file after migration succeeded
747 self._RemoveConfigFile(instance.name)
748 except EnvironmentError:
749 logging.exception("Failure while removing instance config file")
751 def GetMigrationStatus(self, instance):
752 """Get the migration status
754 As MigrateInstance for Xen is still blocking, if this method is called it
755 means that MigrateInstance has completed successfully. So we can safely
756 assume that the migration was successful and notify this fact to the client.
758 @type instance: L{objects.Instance}
759 @param instance: the instance that is being migrated
760 @rtype: L{objects.MigrationStatus}
761 @return: the status of the current migration (one of
762 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
763 progress info that can be retrieved from the hypervisor
766 return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
769 def PowercycleNode(cls):
770 """Xen-specific powercycle.
772 This first does a Linux reboot (which triggers automatically a Xen
773 reboot), and if that fails it tries to do a Xen reboot. The reason
774 we don't try a Xen reboot first is that the xen reboot launches an
775 external command which connects to the Xen hypervisor, and that
776 won't work in case the root filesystem is broken and/or the xend
777 daemon is not working.
781 cls.LinuxPowercycle()
783 utils.RunCmd([constants.XEN_CMD, "debug", "R"])
786 class XenPvmHypervisor(XenHypervisor):
787 """Xen PVM hypervisor interface"""
790 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
791 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
792 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
793 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
794 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
795 constants.HV_ROOT_PATH: hv_base.NO_CHECK,
796 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
797 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
798 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
799 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
800 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
801 constants.HV_REBOOT_BEHAVIOR:
802 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
803 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
804 constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
805 constants.HV_CPU_WEIGHT:
806 (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
809 def _GetConfig(self, instance, startup_memory, block_devices):
810 """Write the Xen config file for the instance.
813 hvp = instance.hvparams
815 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
817 # if bootloader is True, use bootloader instead of kernel and ramdisk
819 if hvp[constants.HV_USE_BOOTLOADER]:
820 # bootloader handling
821 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
823 config.write("bootloader = '%s'\n" % bootloader_path)
825 raise errors.HypervisorError("Bootloader enabled, but missing"
828 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
830 config.write("bootargs = '%s'\n" % bootloader_args)
833 kpath = hvp[constants.HV_KERNEL_PATH]
834 config.write("kernel = '%s'\n" % kpath)
837 initrd_path = hvp[constants.HV_INITRD_PATH]
839 config.write("ramdisk = '%s'\n" % initrd_path)
841 # rest of the settings
842 config.write("memory = %d\n" % startup_memory)
843 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
844 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
845 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
847 config.write("%s\n" % cpu_pinning)
848 cpu_cap = hvp[constants.HV_CPU_CAP]
850 config.write("cpu_cap=%d\n" % cpu_cap)
851 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
853 config.write("cpu_weight=%d\n" % cpu_weight)
855 config.write("name = '%s'\n" % instance.name)
858 for nic in instance.nics:
859 nic_str = "mac=%s" % (nic.mac)
860 ip = getattr(nic, "ip", None)
862 nic_str += ", ip=%s" % ip
863 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
864 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
865 vif_data.append("'%s'" % nic_str)
868 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
870 config.write("vif = [%s]\n" % ",".join(vif_data))
871 config.write("disk = [%s]\n" % ",".join(disk_data))
873 if hvp[constants.HV_ROOT_PATH]:
874 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
875 config.write("on_poweroff = 'destroy'\n")
876 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
877 config.write("on_reboot = 'restart'\n")
879 config.write("on_reboot = 'destroy'\n")
880 config.write("on_crash = 'restart'\n")
881 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
883 return config.getvalue()
886 class XenHvmHypervisor(XenHypervisor):
887 """Xen HVM hypervisor interface"""
889 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
890 pathutils.VNC_PASSWORD_FILE,
892 ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
893 pathutils.VNC_PASSWORD_FILE,
897 constants.HV_ACPI: hv_base.NO_CHECK,
898 constants.HV_BOOT_ORDER: (True, ) +
899 (lambda x: x and len(x.strip("acdn")) == 0,
900 "Invalid boot order specified, must be one or more of [acdn]",
902 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
903 constants.HV_DISK_TYPE:
904 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
905 constants.HV_NIC_TYPE:
906 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
907 constants.HV_PAE: hv_base.NO_CHECK,
908 constants.HV_VNC_BIND_ADDRESS:
909 (False, netutils.IP4Address.IsValid,
910 "VNC bind address is not a valid IP address", None, None),
911 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
912 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
913 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
914 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
915 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
916 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
917 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
918 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
919 # Add PCI passthrough
920 constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
921 constants.HV_REBOOT_BEHAVIOR:
922 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
923 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
924 constants.HV_CPU_CAP: hv_base.NO_CHECK,
925 constants.HV_CPU_WEIGHT:
926 (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
927 constants.HV_VIF_TYPE:
928 hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
931 def _GetConfig(self, instance, startup_memory, block_devices):
932 """Create a Xen 3.1 HVM config file.
935 hvp = instance.hvparams
940 kpath = hvp[constants.HV_KERNEL_PATH]
941 config.write("kernel = '%s'\n" % kpath)
943 config.write("builder = 'hvm'\n")
944 config.write("memory = %d\n" % startup_memory)
945 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
946 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
947 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
949 config.write("%s\n" % cpu_pinning)
950 cpu_cap = hvp[constants.HV_CPU_CAP]
952 config.write("cpu_cap=%d\n" % cpu_cap)
953 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
955 config.write("cpu_weight=%d\n" % cpu_weight)
957 config.write("name = '%s'\n" % instance.name)
958 if hvp[constants.HV_PAE]:
959 config.write("pae = 1\n")
961 config.write("pae = 0\n")
962 if hvp[constants.HV_ACPI]:
963 config.write("acpi = 1\n")
965 config.write("acpi = 0\n")
966 config.write("apic = 1\n")
967 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
968 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
969 config.write("sdl = 0\n")
970 config.write("usb = 1\n")
971 config.write("usbdevice = 'tablet'\n")
972 config.write("vnc = 1\n")
973 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
974 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
976 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
978 if instance.network_port > constants.VNC_BASE_PORT:
979 display = instance.network_port - constants.VNC_BASE_PORT
980 config.write("vncdisplay = %s\n" % display)
981 config.write("vncunused = 0\n")
983 config.write("# vncdisplay = 1\n")
984 config.write("vncunused = 1\n")
986 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
988 password = utils.ReadFile(vnc_pwd_file)
989 except EnvironmentError, err:
990 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
993 config.write("vncpasswd = '%s'\n" % password.rstrip())
995 config.write("serial = 'pty'\n")
996 if hvp[constants.HV_USE_LOCALTIME]:
997 config.write("localtime = 1\n")
1000 # Note: what is called 'nic_type' here, is used as value for the xen nic
1001 # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1002 # the 'vif_type' to avoid a clash of notation.
1003 nic_type = hvp[constants.HV_NIC_TYPE]
1005 if nic_type is None:
1007 if hvp[constants.HV_VIF_TYPE]:
1008 vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1009 # ensure old instances don't change
1010 nic_type_str = vif_type_str
1011 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1012 nic_type_str = ", type=paravirtualized"
1014 # parameter 'model' is only valid with type 'ioemu'
1015 nic_type_str = ", model=%s, type=%s" % \
1016 (nic_type, constants.HT_HVM_VIF_IOEMU)
1017 for nic in instance.nics:
1018 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1019 ip = getattr(nic, "ip", None)
1021 nic_str += ", ip=%s" % ip
1022 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1023 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1024 vif_data.append("'%s'" % nic_str)
1026 config.write("vif = [%s]\n" % ",".join(vif_data))
1029 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1031 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1033 iso = "'file:%s,hdc:cdrom,r'" % iso_path
1034 disk_data.append(iso)
1036 config.write("disk = [%s]\n" % (",".join(disk_data)))
1037 # Add PCI passthrough
1039 pci_pass = hvp[constants.HV_PASSTHROUGH]
1041 pci_pass_arr = pci_pass.split(";")
1042 config.write("pci = %s\n" % pci_pass_arr)
1043 config.write("on_poweroff = 'destroy'\n")
1044 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1045 config.write("on_reboot = 'restart'\n")
1047 config.write("on_reboot = 'destroy'\n")
1048 config.write("on_crash = 'restart'\n")
1050 return config.getvalue()