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, timeout=None):
369 """Wrapper around L{utils.process.RunCmd} to run Xen command.
371 If a timeout (in seconds) is specified, the command will be terminated after
372 that number of seconds.
374 @see: L{utils.process.RunCmd}
379 if timeout is not None:
380 cmd.extend(["timeout", str(timeout)])
382 cmd.extend([self._GetCommand()])
385 return self._run_cmd_fn(cmd)
387 def _ConfigFileName(self, instance_name):
388 """Get the config file name for an instance.
390 @param instance_name: instance name
391 @type instance_name: str
392 @return: fully qualified path to instance config file
396 return utils.PathJoin(self._cfgdir, instance_name)
399 def _WriteNICInfoFile(cls, instance, idx, nic):
400 """Write the Xen config file for the instance.
402 This version of the function just writes the config file from static data.
405 instance_name = instance.name
406 dirs = [(dname, constants.RUN_DIRS_MODE)
407 for dname in cls._DIRS + [cls._InstanceNICDir(instance_name)]]
408 utils.EnsureDirs(dirs)
410 cfg_file = cls._InstanceNICFile(instance_name, idx)
413 data.write("TAGS=%s\n" % "\ ".join(instance.GetTags()))
415 netinfo = objects.Network.FromDict(nic.netinfo)
416 for k, v in netinfo.HooksDict().iteritems():
417 data.write("%s=%s\n" % (k, v))
419 data.write("MAC=%s\n" % nic.mac)
420 data.write("IP=%s\n" % nic.ip)
421 data.write("MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
422 data.write("LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
425 utils.WriteFile(cfg_file, data=data.getvalue())
426 except EnvironmentError, err:
427 raise errors.HypervisorError("Cannot write Xen instance configuration"
428 " file %s: %s" % (cfg_file, err))
431 def _InstanceNICDir(cls, instance_name):
432 """Returns the directory holding the tap device files for a given instance.
435 return utils.PathJoin(cls._NICS_DIR, instance_name)
438 def _InstanceNICFile(cls, instance_name, seq):
439 """Returns the name of the file containing the tap device for a given NIC
442 return utils.PathJoin(cls._InstanceNICDir(instance_name), str(seq))
445 def _GetConfig(cls, instance, startup_memory, block_devices):
446 """Build Xen configuration for an instance.
449 raise NotImplementedError
451 def _WriteConfigFile(self, instance_name, data):
452 """Write the Xen config file for the instance.
454 This version of the function just writes the config file from static data.
457 # just in case it exists
458 utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
460 cfg_file = self._ConfigFileName(instance_name)
462 utils.WriteFile(cfg_file, data=data)
463 except EnvironmentError, err:
464 raise errors.HypervisorError("Cannot write Xen instance configuration"
465 " file %s: %s" % (cfg_file, err))
467 def _ReadConfigFile(self, instance_name):
468 """Returns the contents of the instance config file.
471 filename = self._ConfigFileName(instance_name)
474 file_content = utils.ReadFile(filename)
475 except EnvironmentError, err:
476 raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
480 def _RemoveConfigFile(self, instance_name):
481 """Remove the xen configuration file.
484 utils.RemoveFile(self._ConfigFileName(instance_name))
486 shutil.rmtree(self._InstanceNICDir(instance_name))
488 if err.errno != errno.ENOENT:
491 def _StashConfigFile(self, instance_name):
492 """Move the Xen config file to the log directory and return its new path.
495 old_filename = self._ConfigFileName(instance_name)
497 (instance_name, utils.TimestampForFilename()))
498 new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
499 utils.RenameFile(old_filename, new_filename)
502 def _GetXmList(self, include_node):
503 """Wrapper around module level L{_GetXmList}.
506 return _GetXmList(lambda: self._RunXen(["list"]), include_node)
508 def ListInstances(self):
509 """Get the list of running instances.
512 xm_list = self._GetXmList(False)
513 names = [info[0] for info in xm_list]
516 def GetInstanceInfo(self, instance_name):
517 """Get instance properties.
519 @param instance_name: the instance name
521 @return: tuple (name, id, memory, vcpus, stat, times)
524 xm_list = self._GetXmList(instance_name == _DOM0_NAME)
527 if data[0] == instance_name:
532 def GetAllInstancesInfo(self):
533 """Get properties of all instances.
535 @return: list of tuples (name, id, memory, vcpus, stat, times)
538 xm_list = self._GetXmList(False)
541 def _MakeConfigFile(self, instance, startup_memory, block_devices):
542 """Gather configuration details and write to disk.
544 See L{_GetConfig} for arguments.
548 buf.write("# Automatically generated by Ganeti. Do not edit!\n")
550 buf.write(self._GetConfig(instance, startup_memory, block_devices))
553 self._WriteConfigFile(instance.name, buf.getvalue())
555 def StartInstance(self, instance, block_devices, startup_paused):
556 """Start an instance.
559 startup_memory = self._InstanceStartupMemory(instance)
561 self._MakeConfigFile(instance, startup_memory, block_devices)
566 cmd.append(self._ConfigFileName(instance.name))
568 result = self._RunXen(cmd)
570 # Move the Xen configuration file to the log directory to avoid
571 # leaving a stale config file behind.
572 stashed_config = self._StashConfigFile(instance.name)
573 raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved"
574 " config file to %s" %
575 (instance.name, result.fail_reason,
576 result.output, stashed_config))
578 def StopInstance(self, instance, force=False, retry=False, name=None,
582 A soft shutdown can be interrupted. A hard shutdown tries forever.
585 assert(timeout is None or force is not None)
590 return self._StopInstance(name, force, timeout)
592 def _ShutdownInstance(self, name, timeout):
593 """Shutdown an instance if the instance is running.
595 The '-w' flag waits for shutdown to complete which avoids the need
596 to poll in the case where we want to destroy the domain
597 immediately after shutdown.
600 @param name: name of the instance to stop
601 @type timeout: int or None
602 @param timeout: a timeout after which the shutdown command should be killed,
603 or None for no timeout
606 instance_info = self.GetInstanceInfo(name)
608 if instance_info is None or _IsInstanceShutdown(instance_info[4]):
609 logging.info("Failed to shutdown instance %s, not running", name)
612 return self._RunXen(["shutdown", "-w", name], timeout)
614 def _DestroyInstance(self, name):
615 """Destroy an instance if the instance if the instance exists.
618 @param name: name of the instance to destroy
621 instance_info = self.GetInstanceInfo(name)
623 if instance_info is None:
624 logging.info("Failed to destroy instance %s, does not exist", name)
627 return self._RunXen(["destroy", name])
629 def _StopInstance(self, name, force, timeout):
633 @param name: name of the instance to destroy
636 @param force: whether to do a "hard" stop (destroy)
638 @type timeout: int or None
639 @param timeout: a timeout after which the shutdown command should be killed,
640 or None for no timeout
644 result = self._DestroyInstance(name)
646 self._ShutdownInstance(name, timeout)
647 result = self._DestroyInstance(name)
649 if result is not None and result.failed and \
650 self.GetInstanceInfo(name) is not None:
651 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
652 (name, result.fail_reason, result.output))
654 # Remove configuration file if stopping/starting instance was successful
655 self._RemoveConfigFile(name)
657 def RebootInstance(self, instance):
658 """Reboot an instance.
661 ini_info = self.GetInstanceInfo(instance.name)
664 raise errors.HypervisorError("Failed to reboot instance %s,"
665 " not running" % instance.name)
667 result = self._RunXen(["reboot", instance.name])
669 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
670 (instance.name, result.fail_reason,
673 def _CheckInstance():
674 new_info = self.GetInstanceInfo(instance.name)
676 # check if the domain ID has changed or the run time has decreased
677 if (new_info is not None and
678 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
681 raise utils.RetryAgain()
684 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
685 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
686 except utils.RetryTimeout:
687 raise errors.HypervisorError("Failed to reboot instance %s: instance"
688 " did not reboot in the expected interval" %
691 def BalloonInstanceMemory(self, instance, mem):
692 """Balloon an instance memory to a certain value.
694 @type instance: L{objects.Instance}
695 @param instance: instance to be accepted
697 @param mem: actual memory size to use for instance runtime
700 result = self._RunXen(["mem-set", instance.name, mem])
702 raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
703 (instance.name, result.fail_reason,
706 # Update configuration file
707 cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
708 cmd.append(self._ConfigFileName(instance.name))
710 result = utils.RunCmd(cmd)
712 raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
713 (instance.name, result.fail_reason,
716 def GetNodeInfo(self):
717 """Return information about the node.
719 @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
722 result = self._RunXen(["info"])
724 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
728 return _GetNodeInfo(result.stdout, self._GetXmList)
731 def GetInstanceConsole(cls, instance, hvparams, beparams):
732 """Return a command for connecting to the console of an instance.
735 return objects.InstanceConsole(instance=instance.name,
736 kind=constants.CONS_SSH,
737 host=instance.primary_node,
738 user=constants.SSH_CONSOLE_USER,
739 command=[pathutils.XEN_CONSOLE_WRAPPER,
740 constants.XEN_CMD, instance.name])
743 """Verify the hypervisor.
745 For Xen, this verifies that the xend process is running.
747 @return: Problem description if something is wrong, C{None} otherwise
750 result = self._RunXen(["info"])
752 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
756 def MigrationInfo(self, instance):
757 """Get instance information to perform a migration.
759 @type instance: L{objects.Instance}
760 @param instance: instance to be migrated
762 @return: content of the xen config file
765 return self._ReadConfigFile(instance.name)
767 def AcceptInstance(self, instance, info, target):
768 """Prepare to accept an instance.
770 @type instance: L{objects.Instance}
771 @param instance: instance to be accepted
773 @param info: content of the xen config file on the source node
775 @param target: target host (usually ip), on this node
780 def FinalizeMigrationDst(self, instance, info, success):
781 """Finalize an instance migration.
783 After a successful migration we write the xen config file.
784 We do nothing on a failure, as we did not change anything at accept time.
786 @type instance: L{objects.Instance}
787 @param instance: instance whose migration is being finalized
789 @param info: content of the xen config file on the source node
790 @type success: boolean
791 @param success: whether the migration was a success or a failure
795 self._WriteConfigFile(instance.name, info)
797 def MigrateInstance(self, instance, target, live):
798 """Migrate an instance to a target node.
800 The migration will not be attempted if the instance is not
803 @type instance: L{objects.Instance}
804 @param instance: the instance to be migrated
806 @param target: ip address of the target node
808 @param live: perform a live migration
811 port = instance.hvparams[constants.HV_MIGRATION_PORT]
813 # TODO: Pass cluster name via RPC
814 cluster_name = ssconf.SimpleStore().GetClusterName()
816 return self._MigrateInstance(cluster_name, instance.name, target, port,
819 def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
820 _ping_fn=netutils.TcpPing):
821 """Migrate an instance to a target node.
823 @see: L{MigrateInstance} for details
826 if self.GetInstanceInfo(instance_name) is None:
827 raise errors.HypervisorError("Instance not running, cannot migrate")
829 cmd = self._GetCommand()
831 if (cmd == constants.XEN_CMD_XM and
832 not _ping_fn(target, port, live_port_needed=True)):
833 raise errors.HypervisorError("Remote host %s not listening on port"
834 " %s, cannot migrate" % (target, port))
838 if cmd == constants.XEN_CMD_XM:
839 args.extend(["-p", "%d" % port])
843 elif cmd == constants.XEN_CMD_XL:
845 "-s", constants.XL_SSH_CMD % cluster_name,
846 "-C", self._ConfigFileName(instance_name),
850 raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
852 args.extend([instance_name, target])
854 result = self._RunXen(args)
856 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
857 (instance_name, result.output))
859 def FinalizeMigrationSource(self, instance, success, live):
860 """Finalize the instance migration on the source node.
862 @type instance: L{objects.Instance}
863 @param instance: the instance that was migrated
865 @param success: whether the migration succeeded or not
867 @param live: whether the user requested a live migration or not
870 # pylint: disable=W0613
872 # remove old xen file after migration succeeded
874 self._RemoveConfigFile(instance.name)
875 except EnvironmentError:
876 logging.exception("Failure while removing instance config file")
878 def GetMigrationStatus(self, instance):
879 """Get the migration status
881 As MigrateInstance for Xen is still blocking, if this method is called it
882 means that MigrateInstance has completed successfully. So we can safely
883 assume that the migration was successful and notify this fact to the client.
885 @type instance: L{objects.Instance}
886 @param instance: the instance that is being migrated
887 @rtype: L{objects.MigrationStatus}
888 @return: the status of the current migration (one of
889 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
890 progress info that can be retrieved from the hypervisor
893 return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
896 def PowercycleNode(cls):
897 """Xen-specific powercycle.
899 This first does a Linux reboot (which triggers automatically a Xen
900 reboot), and if that fails it tries to do a Xen reboot. The reason
901 we don't try a Xen reboot first is that the xen reboot launches an
902 external command which connects to the Xen hypervisor, and that
903 won't work in case the root filesystem is broken and/or the xend
904 daemon is not working.
908 cls.LinuxPowercycle()
910 utils.RunCmd([constants.XEN_CMD, "debug", "R"])
913 class XenPvmHypervisor(XenHypervisor):
914 """Xen PVM hypervisor interface"""
917 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
918 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
919 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
920 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
921 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
922 constants.HV_ROOT_PATH: hv_base.NO_CHECK,
923 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
924 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
925 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
926 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
927 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
928 constants.HV_REBOOT_BEHAVIOR:
929 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
930 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
931 constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
932 constants.HV_CPU_WEIGHT:
933 (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
934 constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
937 def _GetConfig(self, instance, startup_memory, block_devices):
938 """Write the Xen config file for the instance.
941 hvp = instance.hvparams
943 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
945 # if bootloader is True, use bootloader instead of kernel and ramdisk
947 if hvp[constants.HV_USE_BOOTLOADER]:
948 # bootloader handling
949 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
951 config.write("bootloader = '%s'\n" % bootloader_path)
953 raise errors.HypervisorError("Bootloader enabled, but missing"
956 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
958 config.write("bootargs = '%s'\n" % bootloader_args)
961 kpath = hvp[constants.HV_KERNEL_PATH]
962 config.write("kernel = '%s'\n" % kpath)
965 initrd_path = hvp[constants.HV_INITRD_PATH]
967 config.write("ramdisk = '%s'\n" % initrd_path)
969 # rest of the settings
970 config.write("memory = %d\n" % startup_memory)
971 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
972 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
973 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
975 config.write("%s\n" % cpu_pinning)
976 cpu_cap = hvp[constants.HV_CPU_CAP]
978 config.write("cpu_cap=%d\n" % cpu_cap)
979 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
981 config.write("cpu_weight=%d\n" % cpu_weight)
983 config.write("name = '%s'\n" % instance.name)
986 for idx, nic in enumerate(instance.nics):
987 nic_str = "mac=%s" % (nic.mac)
988 ip = getattr(nic, "ip", None)
990 nic_str += ", ip=%s" % ip
991 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
992 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
993 if hvp[constants.HV_VIF_SCRIPT]:
994 nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
995 vif_data.append("'%s'" % nic_str)
996 self._WriteNICInfoFile(instance, idx, nic)
999 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1001 config.write("vif = [%s]\n" % ",".join(vif_data))
1002 config.write("disk = [%s]\n" % ",".join(disk_data))
1004 if hvp[constants.HV_ROOT_PATH]:
1005 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
1006 config.write("on_poweroff = 'destroy'\n")
1007 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1008 config.write("on_reboot = 'restart'\n")
1010 config.write("on_reboot = 'destroy'\n")
1011 config.write("on_crash = 'restart'\n")
1012 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
1014 return config.getvalue()
1017 class XenHvmHypervisor(XenHypervisor):
1018 """Xen HVM hypervisor interface"""
1020 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
1021 pathutils.VNC_PASSWORD_FILE,
1023 ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
1024 pathutils.VNC_PASSWORD_FILE,
1028 constants.HV_ACPI: hv_base.NO_CHECK,
1029 constants.HV_BOOT_ORDER: (True, ) +
1030 (lambda x: x and len(x.strip("acdn")) == 0,
1031 "Invalid boot order specified, must be one or more of [acdn]",
1033 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
1034 constants.HV_DISK_TYPE:
1035 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
1036 constants.HV_NIC_TYPE:
1037 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
1038 constants.HV_PAE: hv_base.NO_CHECK,
1039 constants.HV_VNC_BIND_ADDRESS:
1040 (False, netutils.IP4Address.IsValid,
1041 "VNC bind address is not a valid IP address", None, None),
1042 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
1043 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
1044 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
1045 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
1046 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
1047 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
1048 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
1049 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
1050 # Add PCI passthrough
1051 constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
1052 constants.HV_REBOOT_BEHAVIOR:
1053 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
1054 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
1055 constants.HV_CPU_CAP: hv_base.NO_CHECK,
1056 constants.HV_CPU_WEIGHT:
1057 (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
1058 constants.HV_VIF_TYPE:
1059 hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
1060 constants.HV_VIRIDIAN: hv_base.NO_CHECK,
1061 constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
1064 def _GetConfig(self, instance, startup_memory, block_devices):
1065 """Create a Xen 3.1 HVM config file.
1068 hvp = instance.hvparams
1073 kpath = hvp[constants.HV_KERNEL_PATH]
1074 config.write("kernel = '%s'\n" % kpath)
1076 config.write("builder = 'hvm'\n")
1077 config.write("memory = %d\n" % startup_memory)
1078 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1079 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1080 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1082 config.write("%s\n" % cpu_pinning)
1083 cpu_cap = hvp[constants.HV_CPU_CAP]
1085 config.write("cpu_cap=%d\n" % cpu_cap)
1086 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1088 config.write("cpu_weight=%d\n" % cpu_weight)
1090 config.write("name = '%s'\n" % instance.name)
1091 if hvp[constants.HV_PAE]:
1092 config.write("pae = 1\n")
1094 config.write("pae = 0\n")
1095 if hvp[constants.HV_ACPI]:
1096 config.write("acpi = 1\n")
1098 config.write("acpi = 0\n")
1099 if hvp[constants.HV_VIRIDIAN]:
1100 config.write("viridian = 1\n")
1102 config.write("viridian = 0\n")
1104 config.write("apic = 1\n")
1105 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1106 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1107 config.write("sdl = 0\n")
1108 config.write("usb = 1\n")
1109 config.write("usbdevice = 'tablet'\n")
1110 config.write("vnc = 1\n")
1111 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1112 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1114 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1116 if instance.network_port > constants.VNC_BASE_PORT:
1117 display = instance.network_port - constants.VNC_BASE_PORT
1118 config.write("vncdisplay = %s\n" % display)
1119 config.write("vncunused = 0\n")
1121 config.write("# vncdisplay = 1\n")
1122 config.write("vncunused = 1\n")
1124 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1126 password = utils.ReadFile(vnc_pwd_file)
1127 except EnvironmentError, err:
1128 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1129 (vnc_pwd_file, err))
1131 config.write("vncpasswd = '%s'\n" % password.rstrip())
1133 config.write("serial = 'pty'\n")
1134 if hvp[constants.HV_USE_LOCALTIME]:
1135 config.write("localtime = 1\n")
1138 # Note: what is called 'nic_type' here, is used as value for the xen nic
1139 # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1140 # the 'vif_type' to avoid a clash of notation.
1141 nic_type = hvp[constants.HV_NIC_TYPE]
1143 if nic_type is None:
1145 if hvp[constants.HV_VIF_TYPE]:
1146 vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1147 # ensure old instances don't change
1148 nic_type_str = vif_type_str
1149 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1150 nic_type_str = ", type=paravirtualized"
1152 # parameter 'model' is only valid with type 'ioemu'
1153 nic_type_str = ", model=%s, type=%s" % \
1154 (nic_type, constants.HT_HVM_VIF_IOEMU)
1155 for idx, nic in enumerate(instance.nics):
1156 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1157 ip = getattr(nic, "ip", None)
1159 nic_str += ", ip=%s" % ip
1160 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1161 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1162 if hvp[constants.HV_VIF_SCRIPT]:
1163 nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1164 vif_data.append("'%s'" % nic_str)
1165 self._WriteNICInfoFile(instance, idx, nic)
1167 config.write("vif = [%s]\n" % ",".join(vif_data))
1170 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1172 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1174 iso = "'file:%s,hdc:cdrom,r'" % iso_path
1175 disk_data.append(iso)
1177 config.write("disk = [%s]\n" % (",".join(disk_data)))
1178 # Add PCI passthrough
1180 pci_pass = hvp[constants.HV_PASSTHROUGH]
1182 pci_pass_arr = pci_pass.split(";")
1183 config.write("pci = %s\n" % pci_pass_arr)
1184 config.write("on_poweroff = 'destroy'\n")
1185 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1186 config.write("on_reboot = 'restart'\n")
1188 config.write("on_reboot = 'destroy'\n")
1189 config.write("on_crash = 'restart'\n")
1191 return config.getvalue()