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
28 import string # pylint: disable=W0402
30 from cStringIO import StringIO
32 from ganeti import constants
33 from ganeti import errors
34 from ganeti import utils
35 from ganeti.hypervisor import hv_base
36 from ganeti import netutils
37 from ganeti import objects
38 from ganeti import pathutils
39 from ganeti import ssconf
42 XEND_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xend-config.sxp")
43 XL_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xen/xl.conf")
44 VIF_BRIDGE_SCRIPT = utils.PathJoin(pathutils.XEN_CONFIG_DIR,
46 _DOM0_NAME = "Domain-0"
47 _DISK_LETTERS = string.ascii_lowercase
50 constants.FD_LOOP: "file",
51 constants.FD_BLKTAP: "tap:aio",
52 constants.FD_BLKTAP2: "tap2:tapdisk:aio",
56 def _CreateConfigCpus(cpu_mask):
57 """Create a CPU config string for Xen's config file.
60 # Convert the string CPU mask to a list of list of int's
61 cpu_list = utils.ParseMultiCpuMask(cpu_mask)
63 if len(cpu_list) == 1:
64 all_cpu_mapping = cpu_list[0]
65 if all_cpu_mapping == constants.CPU_PINNING_OFF:
66 # If CPU pinning has 1 entry that's "all", then remove the
67 # parameter from the config file
70 # If CPU pinning has one non-all entry, mapping all vCPUS (the entire
71 # VM) to one physical CPU, using format 'cpu = "C"'
72 return "cpu = \"%s\"" % ",".join(map(str, all_cpu_mapping))
76 if vcpu[0] == constants.CPU_PINNING_ALL_VAL:
77 cpu_map = constants.CPU_PINNING_ALL_XEN
79 cpu_map = ",".join(map(str, vcpu))
80 return "\"%s\"" % cpu_map
82 # build the result string in format 'cpus = [ "c", "c", "c" ]',
83 # where each c is a physical CPU number, a range, a list, or any
85 return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
88 def _RunXmList(fn, xmllist_errors):
89 """Helper function for L{_GetXmList} to run "xm list".
92 @param fn: Function returning result of running C{xm list}
93 @type xmllist_errors: list
94 @param xmllist_errors: Error list
100 logging.error("xm list failed (%s): %s", result.fail_reason,
102 xmllist_errors.append(result)
103 raise utils.RetryAgain()
105 # skip over the heading
106 return result.stdout.splitlines()
109 def _ParseXmList(lines, include_node):
110 """Parses the output of C{xm list}.
113 @param lines: Output lines of C{xm list}
114 @type include_node: boolean
115 @param include_node: If True, return information for Dom0
116 @return: list of tuple containing (name, id, memory, vcpus, state, time
122 # Iterate through all lines while ignoring header
123 for line in lines[1:]:
124 # The format of lines is:
125 # Name ID Mem(MiB) VCPUs State Time(s)
126 # Domain-0 0 3418 4 r----- 266.2
129 raise errors.HypervisorError("Can't parse output of xm list,"
132 data[1] = int(data[1])
133 data[2] = int(data[2])
134 data[3] = int(data[3])
135 data[5] = float(data[5])
136 except (TypeError, ValueError), err:
137 raise errors.HypervisorError("Can't parse output of xm list,"
138 " line: %s, error: %s" % (line, err))
140 # skip the Domain-0 (optional)
141 if include_node or data[0] != _DOM0_NAME:
147 def _GetXmList(fn, include_node, _timeout=5):
148 """Return the list of running instances.
150 See L{_RunXmList} and L{_ParseXmList} for parameter details.
155 lines = utils.Retry(_RunXmList, (0.3, 1.5, 1.0), _timeout,
156 args=(fn, xmllist_errors))
157 except utils.RetryTimeout:
159 xmlist_result = xmllist_errors.pop()
161 errmsg = ("xm list failed, timeout exceeded (%s): %s" %
162 (xmlist_result.fail_reason, xmlist_result.output))
164 errmsg = "xm list failed"
166 raise errors.HypervisorError(errmsg)
168 return _ParseXmList(lines, include_node)
171 def _IsInstanceRunning(instance_info):
172 return instance_info == "r-----" \
173 or instance_info == "-b----"
176 def _IsInstanceShutdown(instance_info):
177 return instance_info == "---s--"
180 def _ParseNodeInfo(info):
181 """Return information about the node.
183 @return: a dict with the following keys (memory values in MiB):
184 - memory_total: the total memory size on the node
185 - memory_free: the available memory on the node for instances
186 - nr_cpus: total number of CPUs
187 - nr_nodes: in a NUMA system, the number of domains
188 - nr_sockets: the number of physical CPU sockets in the node
189 - hv_version: the hypervisor version in the form (major, minor)
193 cores_per_socket = threads_per_core = nr_cpus = None
194 xen_major, xen_minor = None, None
198 for line in info.splitlines():
199 fields = line.split(":", 1)
204 (key, val) = map(lambda s: s.strip(), fields)
206 # Note: in Xen 3, memory has changed to total_memory
207 if key in ("memory", "total_memory"):
208 memory_total = int(val)
209 elif key == "free_memory":
210 memory_free = int(val)
211 elif key == "nr_cpus":
212 nr_cpus = result["cpu_total"] = int(val)
213 elif key == "nr_nodes":
214 result["cpu_nodes"] = int(val)
215 elif key == "cores_per_socket":
216 cores_per_socket = int(val)
217 elif key == "threads_per_core":
218 threads_per_core = int(val)
219 elif key == "xen_major":
221 elif key == "xen_minor":
224 if None not in [cores_per_socket, threads_per_core, nr_cpus]:
225 result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
227 if memory_free is not None:
228 result["memory_free"] = memory_free
230 if memory_total is not None:
231 result["memory_total"] = memory_total
233 if not (xen_major is None or xen_minor is None):
234 result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
239 def _MergeInstanceInfo(info, fn):
240 """Updates node information from L{_ParseNodeInfo} with instance info.
243 @param info: Result from L{_ParseNodeInfo}
245 @param fn: Function returning result of running C{xm list}
251 for (name, _, mem, vcpus, _, _) in fn(True):
252 if name == _DOM0_NAME:
253 info["memory_dom0"] = mem
254 info["dom0_cpus"] = vcpus
256 # Include Dom0 in total memory usage
259 memory_free = info.get("memory_free")
260 memory_total = info.get("memory_total")
262 # Calculate memory used by hypervisor
263 if None not in [memory_total, memory_free, total_instmem]:
264 info["memory_hv"] = memory_total - memory_free - total_instmem
269 def _GetNodeInfo(info, fn):
270 """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
273 return _MergeInstanceInfo(_ParseNodeInfo(info), fn)
276 def _GetConfigFileDiskData(block_devices, blockdev_prefix,
277 _letters=_DISK_LETTERS):
278 """Get disk directives for Xen config file.
280 This method builds the xen config disk directive according to the
281 given disk_template and block_devices.
283 @param block_devices: list of tuples (cfdev, rldev):
284 - cfdev: dict containing ganeti config disk part
285 - rldev: ganeti.bdev.BlockDev object
286 @param blockdev_prefix: a string containing blockdevice prefix,
287 e.g. "sd" for /dev/sda
289 @return: string containing disk directive for xen instance config file
292 if len(block_devices) > len(_letters):
293 raise errors.HypervisorError("Too many disks")
297 for sd_suffix, (cfdev, dev_path) in zip(_letters, block_devices):
298 sd_name = blockdev_prefix + sd_suffix
300 if cfdev.mode == constants.DISK_RDWR:
305 if cfdev.dev_type == constants.LD_FILE:
306 driver = _FILE_DRIVER_MAP[cfdev.physical_id[0]]
310 disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode))
315 class XenHypervisor(hv_base.BaseHypervisor):
316 """Xen generic hypervisor interface
318 This is the Xen base class used for both Xen PVM and HVM. It contains
319 all the functionality that is identical for both.
323 REBOOT_RETRY_COUNT = 60
324 REBOOT_RETRY_INTERVAL = 10
325 _ROOT_DIR = pathutils.RUN_DIR + "/xen-hypervisor"
326 _NICS_DIR = _ROOT_DIR + "/nic" # contains NICs' info
327 _DIRS = [_ROOT_DIR, _NICS_DIR]
334 ANCILLARY_FILES_OPT = [
338 def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
339 hv_base.BaseHypervisor.__init__(self)
342 self._cfgdir = pathutils.XEN_CONFIG_DIR
344 self._cfgdir = _cfgdir
346 if _run_cmd_fn is None:
347 self._run_cmd_fn = utils.RunCmd
349 self._run_cmd_fn = _run_cmd_fn
353 def _GetCommand(self):
354 """Returns Xen command to use.
357 if self._cmd is None:
358 # TODO: Make command a hypervisor parameter
359 cmd = constants.XEN_CMD
363 if cmd not in constants.KNOWN_XEN_COMMANDS:
364 raise errors.ProgrammerError("Unknown Xen command '%s'" % cmd)
368 def _RunXen(self, args):
369 """Wrapper around L{utils.process.RunCmd} to run Xen command.
371 @see: L{utils.process.RunCmd}
374 cmd = [self._GetCommand()]
377 return self._run_cmd_fn(cmd)
379 def _ConfigFileName(self, instance_name):
380 """Get the config file name for an instance.
382 @param instance_name: instance name
383 @type instance_name: str
384 @return: fully qualified path to instance config file
388 return utils.PathJoin(self._cfgdir, instance_name)
391 def _WriteNICInfoFile(cls, instance, idx, nic):
392 """Write the Xen config file for the instance.
394 This version of the function just writes the config file from static data.
397 instance_name = instance.name
398 dirs = [(dname, constants.RUN_DIRS_MODE)
399 for dname in cls._DIRS + [cls._InstanceNICDir(instance_name)]]
400 utils.EnsureDirs(dirs)
402 cfg_file = cls._InstanceNICFile(instance_name, idx)
405 data.write("TAGS=%s\n" % "\ ".join(instance.GetTags()))
407 netinfo = objects.Network.FromDict(nic.netinfo)
408 for k, v in netinfo.HooksDict().iteritems():
409 data.write("%s=%s\n" % (k, v))
411 data.write("MAC=%s\n" % nic.mac)
413 data.write("IP=%s\n" % nic.ip)
414 data.write("INTERFACE_INDEX=%s\n" % str(idx))
416 data.write("INTERFACE_NAME=%s\n" % nic.name)
417 data.write("INTERFACE_UUID=%s\n" % nic.uuid)
418 data.write("MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
419 data.write("LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
422 utils.WriteFile(cfg_file, data=data.getvalue())
423 except EnvironmentError, err:
424 raise errors.HypervisorError("Cannot write Xen instance configuration"
425 " file %s: %s" % (cfg_file, err))
428 def _InstanceNICDir(cls, instance_name):
429 """Returns the directory holding the tap device files for a given instance.
432 return utils.PathJoin(cls._NICS_DIR, instance_name)
435 def _InstanceNICFile(cls, instance_name, seq):
436 """Returns the name of the file containing the tap device for a given NIC
439 return utils.PathJoin(cls._InstanceNICDir(instance_name), str(seq))
442 def _GetConfig(cls, instance, startup_memory, block_devices):
443 """Build Xen configuration for an instance.
446 raise NotImplementedError
448 def _WriteConfigFile(self, instance_name, data):
449 """Write the Xen config file for the instance.
451 This version of the function just writes the config file from static data.
454 # just in case it exists
455 utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
457 cfg_file = self._ConfigFileName(instance_name)
459 utils.WriteFile(cfg_file, data=data)
460 except EnvironmentError, err:
461 raise errors.HypervisorError("Cannot write Xen instance configuration"
462 " file %s: %s" % (cfg_file, err))
464 def _ReadConfigFile(self, instance_name):
465 """Returns the contents of the instance config file.
468 filename = self._ConfigFileName(instance_name)
471 file_content = utils.ReadFile(filename)
472 except EnvironmentError, err:
473 raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
477 def _RemoveConfigFile(self, instance_name):
478 """Remove the xen configuration file.
481 utils.RemoveFile(self._ConfigFileName(instance_name))
483 shutil.rmtree(self._InstanceNICDir(instance_name))
485 if err.errno != errno.ENOENT:
488 def _StashConfigFile(self, instance_name):
489 """Move the Xen config file to the log directory and return its new path.
492 old_filename = self._ConfigFileName(instance_name)
494 (instance_name, utils.TimestampForFilename()))
495 new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
496 utils.RenameFile(old_filename, new_filename)
499 def _GetXmList(self, include_node):
500 """Wrapper around module level L{_GetXmList}.
503 return _GetXmList(lambda: self._RunXen(["list"]), include_node)
505 def ListInstances(self):
506 """Get the list of running instances.
509 xm_list = self._GetXmList(False)
510 names = [info[0] for info in xm_list]
513 def GetInstanceInfo(self, instance_name):
514 """Get instance properties.
516 @param instance_name: the instance name
518 @return: tuple (name, id, memory, vcpus, stat, times)
521 xm_list = self._GetXmList(instance_name == _DOM0_NAME)
524 if data[0] == instance_name:
529 def GetAllInstancesInfo(self):
530 """Get properties of all instances.
532 @return: list of tuples (name, id, memory, vcpus, stat, times)
535 xm_list = self._GetXmList(False)
538 def _MakeConfigFile(self, instance, startup_memory, block_devices):
539 """Gather configuration details and write to disk.
541 See L{_GetConfig} for arguments.
545 buf.write("# Automatically generated by Ganeti. Do not edit!\n")
547 buf.write(self._GetConfig(instance, startup_memory, block_devices))
550 self._WriteConfigFile(instance.name, buf.getvalue())
552 def StartInstance(self, instance, block_devices, startup_paused):
553 """Start an instance.
556 startup_memory = self._InstanceStartupMemory(instance)
558 self._MakeConfigFile(instance, startup_memory, block_devices)
563 cmd.append(self._ConfigFileName(instance.name))
565 result = self._RunXen(cmd)
567 # Move the Xen configuration file to the log directory to avoid
568 # leaving a stale config file behind.
569 stashed_config = self._StashConfigFile(instance.name)
570 raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved"
571 " config file to %s" %
572 (instance.name, result.fail_reason,
573 result.output, stashed_config))
575 def StopInstance(self, instance, force=False, retry=False, name=None):
582 return self._StopInstance(name, force)
584 def _ShutdownInstance(self, name):
585 """Shutdown an instance if the instance is running.
588 @param name: name of the instance to stop
590 The '-w' flag waits for shutdown to complete which avoids the need
591 to poll in the case where we want to destroy the domain
592 immediately after shutdown.
595 instance_info = self.GetInstanceInfo(name)
597 if instance_info is None or _IsInstanceShutdown(instance_info[4]):
598 logging.info("Failed to shutdown instance %s, not running", name)
601 return self._RunXen(["shutdown", "-w", name])
603 def _DestroyInstance(self, name):
604 """Destroy an instance if the instance if the instance exists.
607 @param name: name of the instance to destroy
610 instance_info = self.GetInstanceInfo(name)
612 if instance_info is None:
613 logging.info("Failed to destroy instance %s, does not exist", name)
616 return self._RunXen(["destroy", name])
618 def _StopInstance(self, name, force):
622 @param name: name of the instance to destroy
625 @param force: whether to do a "hard" stop (destroy)
629 result = self._DestroyInstance(name)
631 self._ShutdownInstance(name)
632 result = self._DestroyInstance(name)
634 if result is not None and result.failed and \
635 self.GetInstanceInfo(name) is not None:
636 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
637 (name, result.fail_reason, result.output))
639 # Remove configuration file if stopping/starting instance was successful
640 self._RemoveConfigFile(name)
642 def RebootInstance(self, instance):
643 """Reboot an instance.
646 ini_info = self.GetInstanceInfo(instance.name)
649 raise errors.HypervisorError("Failed to reboot instance %s,"
650 " not running" % instance.name)
652 result = self._RunXen(["reboot", instance.name])
654 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
655 (instance.name, result.fail_reason,
658 def _CheckInstance():
659 new_info = self.GetInstanceInfo(instance.name)
661 # check if the domain ID has changed or the run time has decreased
662 if (new_info is not None and
663 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
666 raise utils.RetryAgain()
669 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
670 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
671 except utils.RetryTimeout:
672 raise errors.HypervisorError("Failed to reboot instance %s: instance"
673 " did not reboot in the expected interval" %
676 def BalloonInstanceMemory(self, instance, mem):
677 """Balloon an instance memory to a certain value.
679 @type instance: L{objects.Instance}
680 @param instance: instance to be accepted
682 @param mem: actual memory size to use for instance runtime
685 result = self._RunXen(["mem-set", instance.name, mem])
687 raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
688 (instance.name, result.fail_reason,
691 # Update configuration file
692 cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
693 cmd.append(self._ConfigFileName(instance.name))
695 result = utils.RunCmd(cmd)
697 raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
698 (instance.name, result.fail_reason,
701 def GetNodeInfo(self):
702 """Return information about the node.
704 @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
707 result = self._RunXen(["info"])
709 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
713 return _GetNodeInfo(result.stdout, self._GetXmList)
716 def GetInstanceConsole(cls, instance, hvparams, beparams):
717 """Return a command for connecting to the console of an instance.
720 return objects.InstanceConsole(instance=instance.name,
721 kind=constants.CONS_SSH,
722 host=instance.primary_node,
723 user=constants.SSH_CONSOLE_USER,
724 command=[pathutils.XEN_CONSOLE_WRAPPER,
725 constants.XEN_CMD, instance.name])
728 """Verify the hypervisor.
730 For Xen, this verifies that the xend process is running.
732 @return: Problem description if something is wrong, C{None} otherwise
735 result = self._RunXen(["info"])
737 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
741 def MigrationInfo(self, instance):
742 """Get instance information to perform a migration.
744 @type instance: L{objects.Instance}
745 @param instance: instance to be migrated
747 @return: content of the xen config file
750 return self._ReadConfigFile(instance.name)
752 def AcceptInstance(self, instance, info, target):
753 """Prepare to accept an instance.
755 @type instance: L{objects.Instance}
756 @param instance: instance to be accepted
758 @param info: content of the xen config file on the source node
760 @param target: target host (usually ip), on this node
765 def FinalizeMigrationDst(self, instance, info, success):
766 """Finalize an instance migration.
768 After a successful migration we write the xen config file.
769 We do nothing on a failure, as we did not change anything at accept time.
771 @type instance: L{objects.Instance}
772 @param instance: instance whose migration is being finalized
774 @param info: content of the xen config file on the source node
775 @type success: boolean
776 @param success: whether the migration was a success or a failure
780 self._WriteConfigFile(instance.name, info)
782 def MigrateInstance(self, instance, target, live):
783 """Migrate an instance to a target node.
785 The migration will not be attempted if the instance is not
788 @type instance: L{objects.Instance}
789 @param instance: the instance to be migrated
791 @param target: ip address of the target node
793 @param live: perform a live migration
796 port = instance.hvparams[constants.HV_MIGRATION_PORT]
798 # TODO: Pass cluster name via RPC
799 cluster_name = ssconf.SimpleStore().GetClusterName()
801 return self._MigrateInstance(cluster_name, instance.name, target, port,
804 def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
805 _ping_fn=netutils.TcpPing):
806 """Migrate an instance to a target node.
808 @see: L{MigrateInstance} for details
811 if self.GetInstanceInfo(instance_name) is None:
812 raise errors.HypervisorError("Instance not running, cannot migrate")
814 cmd = self._GetCommand()
816 if (cmd == constants.XEN_CMD_XM and
817 not _ping_fn(target, port, live_port_needed=True)):
818 raise errors.HypervisorError("Remote host %s not listening on port"
819 " %s, cannot migrate" % (target, port))
823 if cmd == constants.XEN_CMD_XM:
824 args.extend(["-p", "%d" % port])
828 elif cmd == constants.XEN_CMD_XL:
830 "-s", constants.XL_SSH_CMD % cluster_name,
831 "-C", self._ConfigFileName(instance_name),
835 raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
837 args.extend([instance_name, target])
839 result = self._RunXen(args)
841 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
842 (instance_name, result.output))
844 def FinalizeMigrationSource(self, instance, success, live):
845 """Finalize the instance migration on the source node.
847 @type instance: L{objects.Instance}
848 @param instance: the instance that was migrated
850 @param success: whether the migration succeeded or not
852 @param live: whether the user requested a live migration or not
855 # pylint: disable=W0613
857 # remove old xen file after migration succeeded
859 self._RemoveConfigFile(instance.name)
860 except EnvironmentError:
861 logging.exception("Failure while removing instance config file")
863 def GetMigrationStatus(self, instance):
864 """Get the migration status
866 As MigrateInstance for Xen is still blocking, if this method is called it
867 means that MigrateInstance has completed successfully. So we can safely
868 assume that the migration was successful and notify this fact to the client.
870 @type instance: L{objects.Instance}
871 @param instance: the instance that is being migrated
872 @rtype: L{objects.MigrationStatus}
873 @return: the status of the current migration (one of
874 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
875 progress info that can be retrieved from the hypervisor
878 return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
881 def PowercycleNode(cls):
882 """Xen-specific powercycle.
884 This first does a Linux reboot (which triggers automatically a Xen
885 reboot), and if that fails it tries to do a Xen reboot. The reason
886 we don't try a Xen reboot first is that the xen reboot launches an
887 external command which connects to the Xen hypervisor, and that
888 won't work in case the root filesystem is broken and/or the xend
889 daemon is not working.
893 cls.LinuxPowercycle()
895 utils.RunCmd([constants.XEN_CMD, "debug", "R"])
898 class XenPvmHypervisor(XenHypervisor):
899 """Xen PVM hypervisor interface"""
902 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
903 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
904 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
905 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
906 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
907 constants.HV_ROOT_PATH: hv_base.NO_CHECK,
908 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
909 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
910 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
911 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
912 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
913 constants.HV_REBOOT_BEHAVIOR:
914 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
915 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
916 constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
917 constants.HV_CPU_WEIGHT:
918 (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
919 constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
922 def _GetConfig(self, instance, startup_memory, block_devices):
923 """Write the Xen config file for the instance.
926 hvp = instance.hvparams
928 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
930 # if bootloader is True, use bootloader instead of kernel and ramdisk
932 if hvp[constants.HV_USE_BOOTLOADER]:
933 # bootloader handling
934 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
936 config.write("bootloader = '%s'\n" % bootloader_path)
938 raise errors.HypervisorError("Bootloader enabled, but missing"
941 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
943 config.write("bootargs = '%s'\n" % bootloader_args)
946 kpath = hvp[constants.HV_KERNEL_PATH]
947 config.write("kernel = '%s'\n" % kpath)
950 initrd_path = hvp[constants.HV_INITRD_PATH]
952 config.write("ramdisk = '%s'\n" % initrd_path)
954 # rest of the settings
955 config.write("memory = %d\n" % startup_memory)
956 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
957 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
958 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
960 config.write("%s\n" % cpu_pinning)
961 cpu_cap = hvp[constants.HV_CPU_CAP]
963 config.write("cpu_cap=%d\n" % cpu_cap)
964 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
966 config.write("cpu_weight=%d\n" % cpu_weight)
968 config.write("name = '%s'\n" % instance.name)
971 for idx, nic in enumerate(instance.nics):
972 nic_str = "mac=%s" % (nic.mac)
973 ip = getattr(nic, "ip", None)
975 nic_str += ", ip=%s" % ip
976 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
977 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
978 if hvp[constants.HV_VIF_SCRIPT]:
979 nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
980 vif_data.append("'%s'" % nic_str)
981 self._WriteNICInfoFile(instance, idx, nic)
984 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
986 config.write("vif = [%s]\n" % ",".join(vif_data))
987 config.write("disk = [%s]\n" % ",".join(disk_data))
989 if hvp[constants.HV_ROOT_PATH]:
990 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
991 config.write("on_poweroff = 'destroy'\n")
992 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
993 config.write("on_reboot = 'restart'\n")
995 config.write("on_reboot = 'destroy'\n")
996 config.write("on_crash = 'restart'\n")
997 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
999 return config.getvalue()
1002 class XenHvmHypervisor(XenHypervisor):
1003 """Xen HVM hypervisor interface"""
1005 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
1006 pathutils.VNC_PASSWORD_FILE,
1008 ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
1009 pathutils.VNC_PASSWORD_FILE,
1013 constants.HV_ACPI: hv_base.NO_CHECK,
1014 constants.HV_BOOT_ORDER: (True, ) +
1015 (lambda x: x and len(x.strip("acdn")) == 0,
1016 "Invalid boot order specified, must be one or more of [acdn]",
1018 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
1019 constants.HV_DISK_TYPE:
1020 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
1021 constants.HV_NIC_TYPE:
1022 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
1023 constants.HV_PAE: hv_base.NO_CHECK,
1024 constants.HV_VNC_BIND_ADDRESS:
1025 (False, netutils.IP4Address.IsValid,
1026 "VNC bind address is not a valid IP address", None, None),
1027 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
1028 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
1029 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
1030 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
1031 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
1032 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
1033 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
1034 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
1035 # Add PCI passthrough
1036 constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
1037 constants.HV_REBOOT_BEHAVIOR:
1038 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
1039 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
1040 constants.HV_CPU_CAP: hv_base.NO_CHECK,
1041 constants.HV_CPU_WEIGHT:
1042 (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
1043 constants.HV_VIF_TYPE:
1044 hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
1045 constants.HV_VIRIDIAN: hv_base.NO_CHECK,
1046 constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
1049 def _GetConfig(self, instance, startup_memory, block_devices):
1050 """Create a Xen 3.1 HVM config file.
1053 hvp = instance.hvparams
1058 kpath = hvp[constants.HV_KERNEL_PATH]
1059 config.write("kernel = '%s'\n" % kpath)
1061 config.write("builder = 'hvm'\n")
1062 config.write("memory = %d\n" % startup_memory)
1063 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1064 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1065 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1067 config.write("%s\n" % cpu_pinning)
1068 cpu_cap = hvp[constants.HV_CPU_CAP]
1070 config.write("cpu_cap=%d\n" % cpu_cap)
1071 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1073 config.write("cpu_weight=%d\n" % cpu_weight)
1075 config.write("name = '%s'\n" % instance.name)
1076 if hvp[constants.HV_PAE]:
1077 config.write("pae = 1\n")
1079 config.write("pae = 0\n")
1080 if hvp[constants.HV_ACPI]:
1081 config.write("acpi = 1\n")
1083 config.write("acpi = 0\n")
1084 if hvp[constants.HV_VIRIDIAN]:
1085 config.write("viridian = 1\n")
1087 config.write("viridian = 0\n")
1089 config.write("apic = 1\n")
1090 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1091 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1092 config.write("sdl = 0\n")
1093 config.write("usb = 1\n")
1094 config.write("usbdevice = 'tablet'\n")
1095 config.write("vnc = 1\n")
1096 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1097 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1099 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1101 if instance.network_port > constants.VNC_BASE_PORT:
1102 display = instance.network_port - constants.VNC_BASE_PORT
1103 config.write("vncdisplay = %s\n" % display)
1104 config.write("vncunused = 0\n")
1106 config.write("# vncdisplay = 1\n")
1107 config.write("vncunused = 1\n")
1109 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1111 password = utils.ReadFile(vnc_pwd_file)
1112 except EnvironmentError, err:
1113 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1114 (vnc_pwd_file, err))
1116 config.write("vncpasswd = '%s'\n" % password.rstrip())
1118 config.write("serial = 'pty'\n")
1119 if hvp[constants.HV_USE_LOCALTIME]:
1120 config.write("localtime = 1\n")
1123 # Note: what is called 'nic_type' here, is used as value for the xen nic
1124 # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1125 # the 'vif_type' to avoid a clash of notation.
1126 nic_type = hvp[constants.HV_NIC_TYPE]
1128 if nic_type is None:
1130 if hvp[constants.HV_VIF_TYPE]:
1131 vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1132 # ensure old instances don't change
1133 nic_type_str = vif_type_str
1134 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1135 nic_type_str = ", type=paravirtualized"
1137 # parameter 'model' is only valid with type 'ioemu'
1138 nic_type_str = ", model=%s, type=%s" % \
1139 (nic_type, constants.HT_HVM_VIF_IOEMU)
1140 for idx, nic in enumerate(instance.nics):
1141 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1142 ip = getattr(nic, "ip", None)
1144 nic_str += ", ip=%s" % ip
1145 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1146 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1147 if hvp[constants.HV_VIF_SCRIPT]:
1148 nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1149 vif_data.append("'%s'" % nic_str)
1150 self._WriteNICInfoFile(instance, idx, nic)
1152 config.write("vif = [%s]\n" % ",".join(vif_data))
1155 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1157 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1159 iso = "'file:%s,hdc:cdrom,r'" % iso_path
1160 disk_data.append(iso)
1162 config.write("disk = [%s]\n" % (",".join(disk_data)))
1163 # Add PCI passthrough
1165 pci_pass = hvp[constants.HV_PASSTHROUGH]
1167 pci_pass_arr = pci_pass.split(";")
1168 config.write("pci = %s\n" % pci_pass_arr)
1169 config.write("on_poweroff = 'destroy'\n")
1170 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1171 config.write("on_reboot = 'restart'\n")
1173 config.write("on_reboot = 'destroy'\n")
1174 config.write("on_crash = 'restart'\n")
1176 return config.getvalue()