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_name, 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 dirs = [(dname, constants.RUN_DIRS_MODE)
406 for dname in cls._DIRS + [cls._InstanceNICDir(instance_name)]]
407 utils.EnsureDirs(dirs)
409 cfg_file = cls._InstanceNICFile(instance_name, idx)
413 netinfo = objects.Network.FromDict(nic.netinfo)
414 data.write("NETWORK_NAME=%s\n" % netinfo.name)
416 data.write("NETWORK_SUBNET=%s\n" % netinfo.network)
418 data.write("NETWORK_GATEWAY=%s\n" % netinfo.gateway)
420 data.write("NETWORK_SUBNET6=%s\n" % netinfo.network6)
422 data.write("NETWORK_GATEWAY6=%s\n" % netinfo.gateway6)
423 if netinfo.mac_prefix:
424 data.write("NETWORK_MAC_PREFIX=%s\n" % netinfo.mac_prefix)
426 data.write("NETWORK_TAGS=%s\n" % "\ ".join(netinfo.tags))
428 data.write("MAC=%s\n" % nic.mac)
429 data.write("IP=%s\n" % nic.ip)
430 data.write("MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
431 data.write("LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
434 utils.WriteFile(cfg_file, data=data.getvalue())
435 except EnvironmentError, err:
436 raise errors.HypervisorError("Cannot write Xen instance configuration"
437 " file %s: %s" % (cfg_file, err))
440 def _InstanceNICDir(cls, instance_name):
441 """Returns the directory holding the tap device files for a given instance.
444 return utils.PathJoin(cls._NICS_DIR, instance_name)
447 def _InstanceNICFile(cls, instance_name, seq):
448 """Returns the name of the file containing the tap device for a given NIC
451 return utils.PathJoin(cls._InstanceNICDir(instance_name), str(seq))
454 def _GetConfig(cls, instance, startup_memory, block_devices):
455 """Build Xen configuration for an instance.
458 raise NotImplementedError
460 def _WriteConfigFile(self, instance_name, data):
461 """Write the Xen config file for the instance.
463 This version of the function just writes the config file from static data.
466 # just in case it exists
467 utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
469 cfg_file = self._ConfigFileName(instance_name)
471 utils.WriteFile(cfg_file, data=data)
472 except EnvironmentError, err:
473 raise errors.HypervisorError("Cannot write Xen instance configuration"
474 " file %s: %s" % (cfg_file, err))
476 def _ReadConfigFile(self, instance_name):
477 """Returns the contents of the instance config file.
480 filename = self._ConfigFileName(instance_name)
483 file_content = utils.ReadFile(filename)
484 except EnvironmentError, err:
485 raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
489 def _RemoveConfigFile(self, instance_name):
490 """Remove the xen configuration file.
493 utils.RemoveFile(self._ConfigFileName(instance_name))
495 shutil.rmtree(self._InstanceNICDir(instance_name))
497 if err.errno != errno.ENOENT:
500 def _StashConfigFile(self, instance_name):
501 """Move the Xen config file to the log directory and return its new path.
504 old_filename = self._ConfigFileName(instance_name)
506 (instance_name, utils.TimestampForFilename()))
507 new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
508 utils.RenameFile(old_filename, new_filename)
511 def _GetXmList(self, include_node):
512 """Wrapper around module level L{_GetXmList}.
515 return _GetXmList(lambda: self._RunXen(["list"]), include_node)
517 def ListInstances(self):
518 """Get the list of running instances.
521 xm_list = self._GetXmList(False)
522 names = [info[0] for info in xm_list]
525 def GetInstanceInfo(self, instance_name):
526 """Get instance properties.
528 @param instance_name: the instance name
530 @return: tuple (name, id, memory, vcpus, stat, times)
533 xm_list = self._GetXmList(instance_name == _DOM0_NAME)
536 if data[0] == instance_name:
541 def GetAllInstancesInfo(self):
542 """Get properties of all instances.
544 @return: list of tuples (name, id, memory, vcpus, stat, times)
547 xm_list = self._GetXmList(False)
550 def _MakeConfigFile(self, instance, startup_memory, block_devices):
551 """Gather configuration details and write to disk.
553 See L{_GetConfig} for arguments.
557 buf.write("# Automatically generated by Ganeti. Do not edit!\n")
559 buf.write(self._GetConfig(instance, startup_memory, block_devices))
562 self._WriteConfigFile(instance.name, buf.getvalue())
564 def StartInstance(self, instance, block_devices, startup_paused):
565 """Start an instance.
568 startup_memory = self._InstanceStartupMemory(instance)
570 self._MakeConfigFile(instance, startup_memory, block_devices)
575 cmd.append(self._ConfigFileName(instance.name))
577 result = self._RunXen(cmd)
579 # Move the Xen configuration file to the log directory to avoid
580 # leaving a stale config file behind.
581 stashed_config = self._StashConfigFile(instance.name)
582 raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved"
583 " config file to %s" %
584 (instance.name, result.fail_reason,
585 result.output, stashed_config))
587 def StopInstance(self, instance, force=False, retry=False, name=None,
591 A soft shutdown can be interrupted. A hard shutdown tries forever.
594 assert(timeout is None or force is not None)
599 return self._StopInstance(name, force, timeout)
601 def _ShutdownInstance(self, name, timeout):
602 """Shutdown an instance if the instance is running.
604 The '-w' flag waits for shutdown to complete which avoids the need
605 to poll in the case where we want to destroy the domain
606 immediately after shutdown.
609 @param name: name of the instance to stop
610 @type timeout: int or None
611 @param timeout: a timeout after which the shutdown command should be killed,
612 or None for no timeout
615 instance_info = self.GetInstanceInfo(name)
617 if instance_info is None or _IsInstanceShutdown(instance_info[4]):
618 logging.info("Failed to shutdown instance %s, not running", name)
621 return self._RunXen(["shutdown", "-w", name], timeout)
623 def _DestroyInstance(self, name):
624 """Destroy an instance if the instance if the instance exists.
627 @param name: name of the instance to destroy
630 instance_info = self.GetInstanceInfo(name)
632 if instance_info is None:
633 logging.info("Failed to destroy instance %s, does not exist", name)
636 return self._RunXen(["destroy", name])
638 def _StopInstance(self, name, force, timeout):
642 @param name: name of the instance to destroy
645 @param force: whether to do a "hard" stop (destroy)
647 @type timeout: int or None
648 @param timeout: a timeout after which the shutdown command should be killed,
649 or None for no timeout
653 result = self._DestroyInstance(name)
655 self._ShutdownInstance(name, timeout)
656 result = self._DestroyInstance(name)
658 if result is not None and result.failed and \
659 self.GetInstanceInfo(name) is not None:
660 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
661 (name, result.fail_reason, result.output))
663 # Remove configuration file if stopping/starting instance was successful
664 self._RemoveConfigFile(name)
666 def RebootInstance(self, instance):
667 """Reboot an instance.
670 ini_info = self.GetInstanceInfo(instance.name)
673 raise errors.HypervisorError("Failed to reboot instance %s,"
674 " not running" % instance.name)
676 result = self._RunXen(["reboot", instance.name])
678 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
679 (instance.name, result.fail_reason,
682 def _CheckInstance():
683 new_info = self.GetInstanceInfo(instance.name)
685 # check if the domain ID has changed or the run time has decreased
686 if (new_info is not None and
687 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
690 raise utils.RetryAgain()
693 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
694 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
695 except utils.RetryTimeout:
696 raise errors.HypervisorError("Failed to reboot instance %s: instance"
697 " did not reboot in the expected interval" %
700 def BalloonInstanceMemory(self, instance, mem):
701 """Balloon an instance memory to a certain value.
703 @type instance: L{objects.Instance}
704 @param instance: instance to be accepted
706 @param mem: actual memory size to use for instance runtime
709 result = self._RunXen(["mem-set", instance.name, mem])
711 raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
712 (instance.name, result.fail_reason,
715 # Update configuration file
716 cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
717 cmd.append(self._ConfigFileName(instance.name))
719 result = utils.RunCmd(cmd)
721 raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
722 (instance.name, result.fail_reason,
725 def GetNodeInfo(self):
726 """Return information about the node.
728 @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
731 result = self._RunXen(["info"])
733 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
737 return _GetNodeInfo(result.stdout, self._GetXmList)
740 def GetInstanceConsole(cls, instance, hvparams, beparams):
741 """Return a command for connecting to the console of an instance.
744 return objects.InstanceConsole(instance=instance.name,
745 kind=constants.CONS_SSH,
746 host=instance.primary_node,
747 user=constants.SSH_CONSOLE_USER,
748 command=[pathutils.XEN_CONSOLE_WRAPPER,
749 constants.XEN_CMD, instance.name])
752 """Verify the hypervisor.
754 For Xen, this verifies that the xend process is running.
756 @return: Problem description if something is wrong, C{None} otherwise
759 result = self._RunXen(["info"])
761 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
765 def MigrationInfo(self, instance):
766 """Get instance information to perform a migration.
768 @type instance: L{objects.Instance}
769 @param instance: instance to be migrated
771 @return: content of the xen config file
774 return self._ReadConfigFile(instance.name)
776 def AcceptInstance(self, instance, info, target):
777 """Prepare to accept an instance.
779 @type instance: L{objects.Instance}
780 @param instance: instance to be accepted
782 @param info: content of the xen config file on the source node
784 @param target: target host (usually ip), on this node
789 def FinalizeMigrationDst(self, instance, info, success):
790 """Finalize an instance migration.
792 After a successful migration we write the xen config file.
793 We do nothing on a failure, as we did not change anything at accept time.
795 @type instance: L{objects.Instance}
796 @param instance: instance whose migration is being finalized
798 @param info: content of the xen config file on the source node
799 @type success: boolean
800 @param success: whether the migration was a success or a failure
804 self._WriteConfigFile(instance.name, info)
806 def MigrateInstance(self, instance, target, live):
807 """Migrate an instance to a target node.
809 The migration will not be attempted if the instance is not
812 @type instance: L{objects.Instance}
813 @param instance: the instance to be migrated
815 @param target: ip address of the target node
817 @param live: perform a live migration
820 port = instance.hvparams[constants.HV_MIGRATION_PORT]
822 # TODO: Pass cluster name via RPC
823 cluster_name = ssconf.SimpleStore().GetClusterName()
825 return self._MigrateInstance(cluster_name, instance.name, target, port,
828 def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
829 _ping_fn=netutils.TcpPing):
830 """Migrate an instance to a target node.
832 @see: L{MigrateInstance} for details
835 if self.GetInstanceInfo(instance_name) is None:
836 raise errors.HypervisorError("Instance not running, cannot migrate")
838 cmd = self._GetCommand()
840 if (cmd == constants.XEN_CMD_XM and
841 not _ping_fn(target, port, live_port_needed=True)):
842 raise errors.HypervisorError("Remote host %s not listening on port"
843 " %s, cannot migrate" % (target, port))
847 if cmd == constants.XEN_CMD_XM:
848 args.extend(["-p", "%d" % port])
852 elif cmd == constants.XEN_CMD_XL:
854 "-s", constants.XL_SSH_CMD % cluster_name,
855 "-C", self._ConfigFileName(instance_name),
859 raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
861 args.extend([instance_name, target])
863 result = self._RunXen(args)
865 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
866 (instance_name, result.output))
868 def FinalizeMigrationSource(self, instance, success, live):
869 """Finalize the instance migration on the source node.
871 @type instance: L{objects.Instance}
872 @param instance: the instance that was migrated
874 @param success: whether the migration succeeded or not
876 @param live: whether the user requested a live migration or not
879 # pylint: disable=W0613
881 # remove old xen file after migration succeeded
883 self._RemoveConfigFile(instance.name)
884 except EnvironmentError:
885 logging.exception("Failure while removing instance config file")
887 def GetMigrationStatus(self, instance):
888 """Get the migration status
890 As MigrateInstance for Xen is still blocking, if this method is called it
891 means that MigrateInstance has completed successfully. So we can safely
892 assume that the migration was successful and notify this fact to the client.
894 @type instance: L{objects.Instance}
895 @param instance: the instance that is being migrated
896 @rtype: L{objects.MigrationStatus}
897 @return: the status of the current migration (one of
898 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
899 progress info that can be retrieved from the hypervisor
902 return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
905 def PowercycleNode(cls):
906 """Xen-specific powercycle.
908 This first does a Linux reboot (which triggers automatically a Xen
909 reboot), and if that fails it tries to do a Xen reboot. The reason
910 we don't try a Xen reboot first is that the xen reboot launches an
911 external command which connects to the Xen hypervisor, and that
912 won't work in case the root filesystem is broken and/or the xend
913 daemon is not working.
917 cls.LinuxPowercycle()
919 utils.RunCmd([constants.XEN_CMD, "debug", "R"])
921 def HotplugSupported(self, instance, action, dev_type):
922 """Whether hotplug is supported.
925 raise errors.HypervisorError("Hotplug not supported by the xen hypervisor")
928 class XenPvmHypervisor(XenHypervisor):
929 """Xen PVM hypervisor interface"""
932 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
933 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
934 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
935 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
936 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
937 constants.HV_ROOT_PATH: hv_base.NO_CHECK,
938 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
939 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
940 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
941 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
942 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
943 constants.HV_REBOOT_BEHAVIOR:
944 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
945 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
946 constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
947 constants.HV_CPU_WEIGHT:
948 (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
949 constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
952 def _GetConfig(self, instance, startup_memory, block_devices):
953 """Write the Xen config file for the instance.
956 hvp = instance.hvparams
958 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
960 # if bootloader is True, use bootloader instead of kernel and ramdisk
962 if hvp[constants.HV_USE_BOOTLOADER]:
963 # bootloader handling
964 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
966 config.write("bootloader = '%s'\n" % bootloader_path)
968 raise errors.HypervisorError("Bootloader enabled, but missing"
971 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
973 config.write("bootargs = '%s'\n" % bootloader_args)
976 kpath = hvp[constants.HV_KERNEL_PATH]
977 config.write("kernel = '%s'\n" % kpath)
980 initrd_path = hvp[constants.HV_INITRD_PATH]
982 config.write("ramdisk = '%s'\n" % initrd_path)
984 # rest of the settings
985 config.write("memory = %d\n" % startup_memory)
986 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
987 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
988 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
990 config.write("%s\n" % cpu_pinning)
991 cpu_cap = hvp[constants.HV_CPU_CAP]
993 config.write("cpu_cap=%d\n" % cpu_cap)
994 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
996 config.write("cpu_weight=%d\n" % cpu_weight)
998 config.write("name = '%s'\n" % instance.name)
1001 for idx, nic in enumerate(instance.nics):
1002 nic_str = "mac=%s" % (nic.mac)
1003 ip = getattr(nic, "ip", None)
1005 nic_str += ", ip=%s" % ip
1006 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1007 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1008 if hvp[constants.HV_VIF_SCRIPT]:
1009 nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1010 vif_data.append("'%s'" % nic_str)
1011 self._WriteNICInfoFile(instance.name, idx, nic)
1014 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1016 config.write("vif = [%s]\n" % ",".join(vif_data))
1017 config.write("disk = [%s]\n" % ",".join(disk_data))
1019 if hvp[constants.HV_ROOT_PATH]:
1020 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
1021 config.write("on_poweroff = 'destroy'\n")
1022 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1023 config.write("on_reboot = 'restart'\n")
1025 config.write("on_reboot = 'destroy'\n")
1026 config.write("on_crash = 'restart'\n")
1027 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
1029 return config.getvalue()
1032 class XenHvmHypervisor(XenHypervisor):
1033 """Xen HVM hypervisor interface"""
1035 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
1036 pathutils.VNC_PASSWORD_FILE,
1038 ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
1039 pathutils.VNC_PASSWORD_FILE,
1043 constants.HV_ACPI: hv_base.NO_CHECK,
1044 constants.HV_BOOT_ORDER: (True, ) +
1045 (lambda x: x and len(x.strip("acdn")) == 0,
1046 "Invalid boot order specified, must be one or more of [acdn]",
1048 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
1049 constants.HV_DISK_TYPE:
1050 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
1051 constants.HV_NIC_TYPE:
1052 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
1053 constants.HV_PAE: hv_base.NO_CHECK,
1054 constants.HV_VNC_BIND_ADDRESS:
1055 (False, netutils.IP4Address.IsValid,
1056 "VNC bind address is not a valid IP address", None, None),
1057 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
1058 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
1059 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
1060 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
1061 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
1062 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
1063 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
1064 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
1065 # Add PCI passthrough
1066 constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
1067 constants.HV_REBOOT_BEHAVIOR:
1068 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
1069 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
1070 constants.HV_CPU_CAP: hv_base.NO_CHECK,
1071 constants.HV_CPU_WEIGHT:
1072 (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
1073 constants.HV_VIF_TYPE:
1074 hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
1075 constants.HV_VIRIDIAN: hv_base.NO_CHECK,
1076 constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
1079 def _GetConfig(self, instance, startup_memory, block_devices):
1080 """Create a Xen 3.1 HVM config file.
1083 hvp = instance.hvparams
1088 kpath = hvp[constants.HV_KERNEL_PATH]
1089 config.write("kernel = '%s'\n" % kpath)
1091 config.write("builder = 'hvm'\n")
1092 config.write("memory = %d\n" % startup_memory)
1093 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1094 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1095 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1097 config.write("%s\n" % cpu_pinning)
1098 cpu_cap = hvp[constants.HV_CPU_CAP]
1100 config.write("cpu_cap=%d\n" % cpu_cap)
1101 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1103 config.write("cpu_weight=%d\n" % cpu_weight)
1105 config.write("name = '%s'\n" % instance.name)
1106 if hvp[constants.HV_PAE]:
1107 config.write("pae = 1\n")
1109 config.write("pae = 0\n")
1110 if hvp[constants.HV_ACPI]:
1111 config.write("acpi = 1\n")
1113 config.write("acpi = 0\n")
1114 if hvp[constants.HV_VIRIDIAN]:
1115 config.write("viridian = 1\n")
1117 config.write("viridian = 0\n")
1119 config.write("apic = 1\n")
1120 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1121 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1122 config.write("sdl = 0\n")
1123 config.write("usb = 1\n")
1124 config.write("usbdevice = 'tablet'\n")
1125 config.write("vnc = 1\n")
1126 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1127 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1129 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1131 if instance.network_port > constants.VNC_BASE_PORT:
1132 display = instance.network_port - constants.VNC_BASE_PORT
1133 config.write("vncdisplay = %s\n" % display)
1134 config.write("vncunused = 0\n")
1136 config.write("# vncdisplay = 1\n")
1137 config.write("vncunused = 1\n")
1139 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1141 password = utils.ReadFile(vnc_pwd_file)
1142 except EnvironmentError, err:
1143 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1144 (vnc_pwd_file, err))
1146 config.write("vncpasswd = '%s'\n" % password.rstrip())
1148 config.write("serial = 'pty'\n")
1149 if hvp[constants.HV_USE_LOCALTIME]:
1150 config.write("localtime = 1\n")
1153 # Note: what is called 'nic_type' here, is used as value for the xen nic
1154 # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1155 # the 'vif_type' to avoid a clash of notation.
1156 nic_type = hvp[constants.HV_NIC_TYPE]
1158 if nic_type is None:
1160 if hvp[constants.HV_VIF_TYPE]:
1161 vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1162 # ensure old instances don't change
1163 nic_type_str = vif_type_str
1164 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1165 nic_type_str = ", type=paravirtualized"
1167 # parameter 'model' is only valid with type 'ioemu'
1168 nic_type_str = ", model=%s, type=%s" % \
1169 (nic_type, constants.HT_HVM_VIF_IOEMU)
1170 for idx, nic in enumerate(instance.nics):
1171 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1172 ip = getattr(nic, "ip", None)
1174 nic_str += ", ip=%s" % ip
1175 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1176 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1177 if hvp[constants.HV_VIF_SCRIPT]:
1178 nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1179 vif_data.append("'%s'" % nic_str)
1180 self._WriteNICInfoFile(instance.name, idx, nic)
1182 config.write("vif = [%s]\n" % ",".join(vif_data))
1185 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1187 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1189 iso = "'file:%s,hdc:cdrom,r'" % iso_path
1190 disk_data.append(iso)
1192 config.write("disk = [%s]\n" % (",".join(disk_data)))
1193 # Add PCI passthrough
1195 pci_pass = hvp[constants.HV_PASSTHROUGH]
1197 pci_pass_arr = pci_pass.split(";")
1198 config.write("pci = %s\n" % pci_pass_arr)
1199 config.write("on_poweroff = 'destroy'\n")
1200 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1201 config.write("on_reboot = 'restart'\n")
1203 config.write("on_reboot = 'destroy'\n")
1204 config.write("on_crash = 'restart'\n")
1206 return config.getvalue()