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
41 XEND_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xend-config.sxp")
42 XL_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xen/xl.conf")
43 VIF_BRIDGE_SCRIPT = utils.PathJoin(pathutils.XEN_CONFIG_DIR,
45 _DOM0_NAME = "Domain-0"
46 _DISK_LETTERS = string.ascii_lowercase
49 constants.FD_LOOP: "file",
50 constants.FD_BLKTAP: "tap:aio",
54 def _CreateConfigCpus(cpu_mask):
55 """Create a CPU config string for Xen's config file.
58 # Convert the string CPU mask to a list of list of int's
59 cpu_list = utils.ParseMultiCpuMask(cpu_mask)
61 if len(cpu_list) == 1:
62 all_cpu_mapping = cpu_list[0]
63 if all_cpu_mapping == constants.CPU_PINNING_OFF:
64 # If CPU pinning has 1 entry that's "all", then remove the
65 # parameter from the config file
68 # If CPU pinning has one non-all entry, mapping all vCPUS (the entire
69 # VM) to one physical CPU, using format 'cpu = "C"'
70 return "cpu = \"%s\"" % ",".join(map(str, all_cpu_mapping))
74 if vcpu[0] == constants.CPU_PINNING_ALL_VAL:
75 cpu_map = constants.CPU_PINNING_ALL_XEN
77 cpu_map = ",".join(map(str, vcpu))
78 return "\"%s\"" % cpu_map
80 # build the result string in format 'cpus = [ "c", "c", "c" ]',
81 # where each c is a physical CPU number, a range, a list, or any
83 return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
86 def _RunInstanceList(fn, instance_list_errors):
87 """Helper function for L{_GetInstanceList} to retrieve the list of instances
91 @param fn: Function to query xen for the list of instances
92 @type instance_list_errors: list
93 @param instance_list_errors: Error list
99 logging.error("Retrieving the instance list from xen failed (%s): %s",
100 result.fail_reason, result.output)
101 instance_list_errors.append(result)
102 raise utils.RetryAgain()
104 # skip over the heading
105 return result.stdout.splitlines()
108 def _ParseInstanceList(lines, include_node):
109 """Parses the output of listing instances by xen.
112 @param lines: Result of retrieving the instance list from xen
113 @type include_node: boolean
114 @param include_node: If True, return information for Dom0
115 @return: list of tuple containing (name, id, memory, vcpus, state, time
121 # Iterate through all lines while ignoring header
122 for line in lines[1:]:
123 # The format of lines is:
124 # Name ID Mem(MiB) VCPUs State Time(s)
125 # Domain-0 0 3418 4 r----- 266.2
128 raise errors.HypervisorError("Can't parse instance list,"
131 data[1] = int(data[1])
132 data[2] = int(data[2])
133 data[3] = int(data[3])
134 data[5] = float(data[5])
135 except (TypeError, ValueError), err:
136 raise errors.HypervisorError("Can't parse instance list,"
137 " line: %s, error: %s" % (line, err))
139 # skip the Domain-0 (optional)
140 if include_node or data[0] != _DOM0_NAME:
146 def _GetInstanceList(fn, include_node, _timeout=5):
147 """Return the list of running instances.
149 See L{_RunInstanceList} and L{_ParseInstanceList} for parameter details.
152 instance_list_errors = []
154 lines = utils.Retry(_RunInstanceList, (0.3, 1.5, 1.0), _timeout,
155 args=(fn, instance_list_errors))
156 except utils.RetryTimeout:
157 if instance_list_errors:
158 instance_list_result = instance_list_errors.pop()
160 errmsg = ("listing instances failed, timeout exceeded (%s): %s" %
161 (instance_list_result.fail_reason, instance_list_result.output))
163 errmsg = "listing instances failed"
165 raise errors.HypervisorError(errmsg)
167 return _ParseInstanceList(lines, include_node)
170 def _ParseNodeInfo(info):
171 """Return information about the node.
173 @return: a dict with the following keys (memory values in MiB):
174 - memory_total: the total memory size on the node
175 - memory_free: the available memory on the node for instances
176 - nr_cpus: total number of CPUs
177 - nr_nodes: in a NUMA system, the number of domains
178 - nr_sockets: the number of physical CPU sockets in the node
179 - hv_version: the hypervisor version in the form (major, minor)
183 cores_per_socket = threads_per_core = nr_cpus = None
184 xen_major, xen_minor = None, None
188 for line in info.splitlines():
189 fields = line.split(":", 1)
194 (key, val) = map(lambda s: s.strip(), fields)
196 # Note: in Xen 3, memory has changed to total_memory
197 if key in ("memory", "total_memory"):
198 memory_total = int(val)
199 elif key == "free_memory":
200 memory_free = int(val)
201 elif key == "nr_cpus":
202 nr_cpus = result["cpu_total"] = int(val)
203 elif key == "nr_nodes":
204 result["cpu_nodes"] = int(val)
205 elif key == "cores_per_socket":
206 cores_per_socket = int(val)
207 elif key == "threads_per_core":
208 threads_per_core = int(val)
209 elif key == "xen_major":
211 elif key == "xen_minor":
214 if None not in [cores_per_socket, threads_per_core, nr_cpus]:
215 result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
217 if memory_free is not None:
218 result["memory_free"] = memory_free
220 if memory_total is not None:
221 result["memory_total"] = memory_total
223 if not (xen_major is None or xen_minor is None):
224 result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
229 def _MergeInstanceInfo(info, instance_list):
230 """Updates node information from L{_ParseNodeInfo} with instance info.
233 @param info: Result from L{_ParseNodeInfo}
234 @type instance_list: list of tuples
235 @param instance_list: list of instance information; one tuple per instance
241 for (name, _, mem, vcpus, _, _) in instance_list:
242 if name == _DOM0_NAME:
243 info["memory_dom0"] = mem
244 info["cpu_dom0"] = vcpus
246 # Include Dom0 in total memory usage
249 memory_free = info.get("memory_free")
250 memory_total = info.get("memory_total")
252 # Calculate memory used by hypervisor
253 if None not in [memory_total, memory_free, total_instmem]:
254 info["memory_hv"] = memory_total - memory_free - total_instmem
259 def _GetNodeInfo(info, instance_list):
260 """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
262 @type instance_list: list of tuples
263 @param instance_list: list of instance information; one tuple per instance
266 return _MergeInstanceInfo(_ParseNodeInfo(info), instance_list)
269 def _GetConfigFileDiskData(block_devices, blockdev_prefix,
270 _letters=_DISK_LETTERS):
271 """Get disk directives for Xen config file.
273 This method builds the xen config disk directive according to the
274 given disk_template and block_devices.
276 @param block_devices: list of tuples (cfdev, rldev):
277 - cfdev: dict containing ganeti config disk part
278 - rldev: ganeti.block.bdev.BlockDev object
279 @param blockdev_prefix: a string containing blockdevice prefix,
280 e.g. "sd" for /dev/sda
282 @return: string containing disk directive for xen instance config file
285 if len(block_devices) > len(_letters):
286 raise errors.HypervisorError("Too many disks")
290 for sd_suffix, (cfdev, dev_path) in zip(_letters, block_devices):
291 sd_name = blockdev_prefix + sd_suffix
293 if cfdev.mode == constants.DISK_RDWR:
298 if cfdev.dev_type == constants.LD_FILE:
299 driver = _FILE_DRIVER_MAP[cfdev.physical_id[0]]
303 disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode))
308 class XenHypervisor(hv_base.BaseHypervisor):
309 """Xen generic hypervisor interface
311 This is the Xen base class used for both Xen PVM and HVM. It contains
312 all the functionality that is identical for both.
316 REBOOT_RETRY_COUNT = 60
317 REBOOT_RETRY_INTERVAL = 10
318 _ROOT_DIR = pathutils.RUN_DIR + "/xen-hypervisor"
319 _NICS_DIR = _ROOT_DIR + "/nic" # contains NICs' info
320 _DIRS = [_ROOT_DIR, _NICS_DIR]
327 ANCILLARY_FILES_OPT = [
331 def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
332 hv_base.BaseHypervisor.__init__(self)
335 self._cfgdir = pathutils.XEN_CONFIG_DIR
337 self._cfgdir = _cfgdir
339 if _run_cmd_fn is None:
340 self._run_cmd_fn = utils.RunCmd
342 self._run_cmd_fn = _run_cmd_fn
346 def _GetCommand(self, hvparams):
347 """Returns Xen command to use.
349 @type hvparams: dict of strings
350 @param hvparams: hypervisor parameters
353 if self._cmd is None:
354 if hvparams is None or constants.HV_XEN_CMD not in hvparams:
355 raise errors.HypervisorError("Cannot determine xen command.")
357 cmd = hvparams[constants.HV_XEN_CMD]
361 if cmd not in constants.KNOWN_XEN_COMMANDS:
362 raise errors.ProgrammerError("Unknown Xen command '%s'" % cmd)
366 def _RunXen(self, args, hvparams):
367 """Wrapper around L{utils.process.RunCmd} to run Xen command.
369 @type hvparams: dict of strings
370 @param hvparams: dictionary of hypervisor params
371 @see: L{utils.process.RunCmd}
374 cmd = [self._GetCommand(hvparams)]
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_name, 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 dirs = [(dname, constants.RUN_DIRS_MODE)
398 for dname in cls._DIRS + [cls._InstanceNICDir(instance_name)]]
399 utils.EnsureDirs(dirs)
401 cfg_file = cls._InstanceNICFile(instance_name, idx)
405 netinfo = objects.Network.FromDict(nic.netinfo)
406 data.write("NETWORK_NAME=%s\n" % netinfo.name)
408 data.write("NETWORK_SUBNET=%s\n" % netinfo.network)
410 data.write("NETWORK_GATEWAY=%s\n" % netinfo.gateway)
412 data.write("NETWORK_SUBNET6=%s\n" % netinfo.network6)
414 data.write("NETWORK_GATEWAY6=%s\n" % netinfo.gateway6)
415 if netinfo.mac_prefix:
416 data.write("NETWORK_MAC_PREFIX=%s\n" % netinfo.mac_prefix)
418 data.write("NETWORK_TAGS=%s\n" % r"\ ".join(netinfo.tags))
420 data.write("MAC=%s\n" % nic.mac)
421 data.write("IP=%s\n" % nic.ip)
422 data.write("MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
423 data.write("LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
426 utils.WriteFile(cfg_file, data=data.getvalue())
427 except EnvironmentError, err:
428 raise errors.HypervisorError("Cannot write Xen instance configuration"
429 " file %s: %s" % (cfg_file, err))
432 def _InstanceNICDir(cls, instance_name):
433 """Returns the directory holding the tap device files for a given instance.
436 return utils.PathJoin(cls._NICS_DIR, instance_name)
439 def _InstanceNICFile(cls, instance_name, seq):
440 """Returns the name of the file containing the tap device for a given NIC
443 return utils.PathJoin(cls._InstanceNICDir(instance_name), str(seq))
446 def _GetConfig(cls, instance, startup_memory, block_devices):
447 """Build Xen configuration for an instance.
450 raise NotImplementedError
452 def _WriteConfigFile(self, instance_name, data):
453 """Write the Xen config file for the instance.
455 This version of the function just writes the config file from static data.
458 # just in case it exists
459 utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
461 cfg_file = self._ConfigFileName(instance_name)
463 utils.WriteFile(cfg_file, data=data)
464 except EnvironmentError, err:
465 raise errors.HypervisorError("Cannot write Xen instance configuration"
466 " file %s: %s" % (cfg_file, err))
468 def _ReadConfigFile(self, instance_name):
469 """Returns the contents of the instance config file.
472 filename = self._ConfigFileName(instance_name)
475 file_content = utils.ReadFile(filename)
476 except EnvironmentError, err:
477 raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
481 def _RemoveConfigFile(self, instance_name):
482 """Remove the xen configuration file.
485 utils.RemoveFile(self._ConfigFileName(instance_name))
487 shutil.rmtree(self._InstanceNICDir(instance_name))
489 if err.errno != errno.ENOENT:
492 def _StashConfigFile(self, instance_name):
493 """Move the Xen config file to the log directory and return its new path.
496 old_filename = self._ConfigFileName(instance_name)
498 (instance_name, utils.TimestampForFilename()))
499 new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
500 utils.RenameFile(old_filename, new_filename)
503 def _GetInstanceList(self, include_node, hvparams):
504 """Wrapper around module level L{_GetInstanceList}.
506 @type hvparams: dict of strings
507 @param hvparams: hypervisor parameters to be used on this node
510 return _GetInstanceList(lambda: self._RunXen(["list"], hvparams),
513 def ListInstances(self, hvparams=None):
514 """Get the list of running instances.
517 instance_list = self._GetInstanceList(False, hvparams)
518 names = [info[0] for info in instance_list]
521 def GetInstanceInfo(self, instance_name, hvparams=None):
522 """Get instance properties.
524 @type instance_name: string
525 @param instance_name: the instance name
526 @type hvparams: dict of strings
527 @param hvparams: the instance's hypervisor params
529 @return: tuple (name, id, memory, vcpus, stat, times)
532 instance_list = self._GetInstanceList(instance_name == _DOM0_NAME, hvparams)
534 for data in instance_list:
535 if data[0] == instance_name:
540 def GetAllInstancesInfo(self, hvparams=None):
541 """Get properties of all instances.
543 @type hvparams: dict of strings
544 @param hvparams: hypervisor parameters
545 @return: list of tuples (name, id, memory, vcpus, stat, times)
548 return self._GetInstanceList(False, hvparams)
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,
569 hvparams=instance.hvparams)
571 self._MakeConfigFile(instance, startup_memory, block_devices)
576 cmd.append(self._ConfigFileName(instance.name))
578 result = self._RunXen(cmd, instance.hvparams)
580 # Move the Xen configuration file to the log directory to avoid
581 # leaving a stale config file behind.
582 stashed_config = self._StashConfigFile(instance.name)
583 raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved"
584 " config file to %s" %
585 (instance.name, result.fail_reason,
586 result.output, stashed_config))
588 def StopInstance(self, instance, force=False, retry=False, name=None):
595 return self._StopInstance(name, force, instance.hvparams)
597 def _StopInstance(self, name, force, hvparams):
601 @param name: name of the instance to be shutdown
603 @param force: flag specifying whether shutdown should be forced
604 @type hvparams: dict of string
605 @param hvparams: hypervisor parameters of the instance
613 result = self._RunXen([action, name], hvparams)
615 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
616 (name, result.fail_reason, result.output))
618 # Remove configuration file if stopping/starting instance was successful
619 self._RemoveConfigFile(name)
621 def RebootInstance(self, instance):
622 """Reboot an instance.
625 ini_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
628 raise errors.HypervisorError("Failed to reboot instance %s,"
629 " not running" % instance.name)
631 result = self._RunXen(["reboot", instance.name], instance.hvparams)
633 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
634 (instance.name, result.fail_reason,
637 def _CheckInstance():
638 new_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
640 # check if the domain ID has changed or the run time has decreased
641 if (new_info is not None and
642 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
645 raise utils.RetryAgain()
648 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
649 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
650 except utils.RetryTimeout:
651 raise errors.HypervisorError("Failed to reboot instance %s: instance"
652 " did not reboot in the expected interval" %
655 def BalloonInstanceMemory(self, instance, mem):
656 """Balloon an instance memory to a certain value.
658 @type instance: L{objects.Instance}
659 @param instance: instance to be accepted
661 @param mem: actual memory size to use for instance runtime
664 result = self._RunXen(["mem-set", instance.name, mem], instance.hvparams)
666 raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
667 (instance.name, result.fail_reason,
670 # Update configuration file
671 cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
672 cmd.append(self._ConfigFileName(instance.name))
674 result = utils.RunCmd(cmd)
676 raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
677 (instance.name, result.fail_reason,
680 def GetNodeInfo(self, hvparams=None):
681 """Return information about the node.
683 @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
686 result = self._RunXen(["info"], hvparams)
688 logging.error("Can't retrieve xen hypervisor information (%s): %s",
689 result.fail_reason, result.output)
692 instance_list = self._GetInstanceList(True, hvparams)
693 return _GetNodeInfo(result.stdout, instance_list)
695 def GetInstanceConsole(self, instance, primary_node, hvparams, beparams):
696 """Return a command for connecting to the console of an instance.
699 xen_cmd = self._GetCommand(hvparams)
700 return objects.InstanceConsole(instance=instance.name,
701 kind=constants.CONS_SSH,
702 host=primary_node.name,
703 user=constants.SSH_CONSOLE_USER,
704 command=[pathutils.XEN_CONSOLE_WRAPPER,
705 xen_cmd, instance.name])
707 def Verify(self, hvparams=None):
708 """Verify the hypervisor.
710 For Xen, this verifies that the xend process is running.
712 @type hvparams: dict of strings
713 @param hvparams: hypervisor parameters to be verified against
715 @return: Problem description if something is wrong, C{None} otherwise
719 return "Could not verify the hypervisor, because no hvparams were" \
722 if constants.HV_XEN_CMD in hvparams:
723 xen_cmd = hvparams[constants.HV_XEN_CMD]
725 self._CheckToolstack(xen_cmd)
726 except errors.HypervisorError:
727 return "The configured xen toolstack '%s' is not available on this" \
730 result = self._RunXen(["info"], hvparams)
732 return "Retrieving information from xen failed: %s, %s" % \
733 (result.fail_reason, result.output)
737 def MigrationInfo(self, instance):
738 """Get instance information to perform a migration.
740 @type instance: L{objects.Instance}
741 @param instance: instance to be migrated
743 @return: content of the xen config file
746 return self._ReadConfigFile(instance.name)
748 def AcceptInstance(self, instance, info, target):
749 """Prepare to accept an instance.
751 @type instance: L{objects.Instance}
752 @param instance: instance to be accepted
754 @param info: content of the xen config file on the source node
756 @param target: target host (usually ip), on this node
761 def FinalizeMigrationDst(self, instance, info, success):
762 """Finalize an instance migration.
764 After a successful migration we write the xen config file.
765 We do nothing on a failure, as we did not change anything at accept time.
767 @type instance: L{objects.Instance}
768 @param instance: instance whose migration is being finalized
770 @param info: content of the xen config file on the source node
771 @type success: boolean
772 @param success: whether the migration was a success or a failure
776 self._WriteConfigFile(instance.name, info)
778 def MigrateInstance(self, cluster_name, instance, target, live):
779 """Migrate an instance to a target node.
781 The migration will not be attempted if the instance is not
784 @type instance: L{objects.Instance}
785 @param instance: the instance to be migrated
787 @param target: ip address of the target node
789 @param live: perform a live migration
792 port = instance.hvparams[constants.HV_MIGRATION_PORT]
794 return self._MigrateInstance(cluster_name, instance.name, target, port,
795 live, instance.hvparams)
797 def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
798 hvparams, _ping_fn=netutils.TcpPing):
799 """Migrate an instance to a target node.
801 @see: L{MigrateInstance} for details
805 raise errors.HypervisorError("No hvparams provided.")
807 if self.GetInstanceInfo(instance_name, hvparams=hvparams) is None:
808 raise errors.HypervisorError("Instance not running, cannot migrate")
810 cmd = self._GetCommand(hvparams)
812 if (cmd == constants.XEN_CMD_XM and
813 not _ping_fn(target, port, live_port_needed=True)):
814 raise errors.HypervisorError("Remote host %s not listening on port"
815 " %s, cannot migrate" % (target, port))
819 if cmd == constants.XEN_CMD_XM:
820 args.extend(["-p", "%d" % port])
824 elif cmd == constants.XEN_CMD_XL:
826 "-s", constants.XL_SSH_CMD % cluster_name,
827 "-C", self._ConfigFileName(instance_name),
831 raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
833 args.extend([instance_name, target])
835 result = self._RunXen(args, hvparams)
837 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
838 (instance_name, result.output))
840 def FinalizeMigrationSource(self, instance, success, live):
841 """Finalize the instance migration on the source node.
843 @type instance: L{objects.Instance}
844 @param instance: the instance that was migrated
846 @param success: whether the migration succeeded or not
848 @param live: whether the user requested a live migration or not
851 # pylint: disable=W0613
853 # remove old xen file after migration succeeded
855 self._RemoveConfigFile(instance.name)
856 except EnvironmentError:
857 logging.exception("Failure while removing instance config file")
859 def GetMigrationStatus(self, instance):
860 """Get the migration status
862 As MigrateInstance for Xen is still blocking, if this method is called it
863 means that MigrateInstance has completed successfully. So we can safely
864 assume that the migration was successful and notify this fact to the client.
866 @type instance: L{objects.Instance}
867 @param instance: the instance that is being migrated
868 @rtype: L{objects.MigrationStatus}
869 @return: the status of the current migration (one of
870 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
871 progress info that can be retrieved from the hypervisor
874 return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
876 def PowercycleNode(self, hvparams=None):
877 """Xen-specific powercycle.
879 This first does a Linux reboot (which triggers automatically a Xen
880 reboot), and if that fails it tries to do a Xen reboot. The reason
881 we don't try a Xen reboot first is that the xen reboot launches an
882 external command which connects to the Xen hypervisor, and that
883 won't work in case the root filesystem is broken and/or the xend
884 daemon is not working.
886 @type hvparams: dict of strings
887 @param hvparams: hypervisor params to be used on this node
891 self.LinuxPowercycle()
893 xen_cmd = self._GetCommand(hvparams)
894 utils.RunCmd([xen_cmd, "debug", "R"])
896 def _CheckToolstack(self, xen_cmd):
897 """Check whether the given toolstack is available on the node.
899 @type xen_cmd: string
900 @param xen_cmd: xen command (e.g. 'xm' or 'xl')
903 binary_found = self._CheckToolstackBinary(xen_cmd)
905 raise errors.HypervisorError("No '%s' binary found on node." % xen_cmd)
906 elif xen_cmd == constants.XEN_CMD_XL:
907 if not self._CheckToolstackXlConfigured():
908 raise errors.HypervisorError("Toolstack '%s' is not enabled on this"
911 def _CheckToolstackBinary(self, xen_cmd):
912 """Checks whether the xen command's binary is found on the machine.
915 if xen_cmd not in constants.KNOWN_XEN_COMMANDS:
916 raise errors.HypervisorError("Unknown xen command '%s'." % xen_cmd)
917 result = self._run_cmd_fn(["which", xen_cmd])
918 return not result.failed
920 def _CheckToolstackXlConfigured(self):
921 """Checks whether xl is enabled on an xl-capable node.
924 @returns: C{True} if 'xl' is enabled, C{False} otherwise
927 result = self._run_cmd_fn([constants.XEN_CMD_XL, "help"])
928 if not result.failed:
931 if "toolstack" in result.stderr:
933 # xl fails for some other reason than the toolstack
935 raise errors.HypervisorError("Cannot run xen ('%s'). Error: %s."
936 % (constants.XEN_CMD_XL, result.stderr))
939 class XenPvmHypervisor(XenHypervisor):
940 """Xen PVM hypervisor interface"""
943 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
944 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
945 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
946 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
947 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
948 constants.HV_ROOT_PATH: hv_base.NO_CHECK,
949 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
950 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
951 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
952 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
953 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
954 constants.HV_REBOOT_BEHAVIOR:
955 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
956 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
957 constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
958 constants.HV_CPU_WEIGHT:
959 (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
960 constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
961 constants.HV_XEN_CMD:
962 hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
965 def _GetConfig(self, instance, startup_memory, block_devices):
966 """Write the Xen config file for the instance.
969 hvp = instance.hvparams
971 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
973 # if bootloader is True, use bootloader instead of kernel and ramdisk
975 if hvp[constants.HV_USE_BOOTLOADER]:
976 # bootloader handling
977 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
979 config.write("bootloader = '%s'\n" % bootloader_path)
981 raise errors.HypervisorError("Bootloader enabled, but missing"
984 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
986 config.write("bootargs = '%s'\n" % bootloader_args)
989 kpath = hvp[constants.HV_KERNEL_PATH]
990 config.write("kernel = '%s'\n" % kpath)
993 initrd_path = hvp[constants.HV_INITRD_PATH]
995 config.write("ramdisk = '%s'\n" % initrd_path)
997 # rest of the settings
998 config.write("memory = %d\n" % startup_memory)
999 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1000 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1001 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1003 config.write("%s\n" % cpu_pinning)
1004 cpu_cap = hvp[constants.HV_CPU_CAP]
1006 config.write("cpu_cap=%d\n" % cpu_cap)
1007 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1009 config.write("cpu_weight=%d\n" % cpu_weight)
1011 config.write("name = '%s'\n" % instance.name)
1014 for idx, nic in enumerate(instance.nics):
1015 nic_str = "mac=%s" % (nic.mac)
1016 ip = getattr(nic, "ip", None)
1018 nic_str += ", ip=%s" % ip
1019 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1020 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1021 if hvp[constants.HV_VIF_SCRIPT]:
1022 nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1023 vif_data.append("'%s'" % nic_str)
1024 self._WriteNICInfoFile(instance.name, idx, nic)
1027 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1029 config.write("vif = [%s]\n" % ",".join(vif_data))
1030 config.write("disk = [%s]\n" % ",".join(disk_data))
1032 if hvp[constants.HV_ROOT_PATH]:
1033 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
1034 config.write("on_poweroff = 'destroy'\n")
1035 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1036 config.write("on_reboot = 'restart'\n")
1038 config.write("on_reboot = 'destroy'\n")
1039 config.write("on_crash = 'restart'\n")
1040 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
1042 return config.getvalue()
1045 class XenHvmHypervisor(XenHypervisor):
1046 """Xen HVM hypervisor interface"""
1048 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
1049 pathutils.VNC_PASSWORD_FILE,
1051 ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
1052 pathutils.VNC_PASSWORD_FILE,
1056 constants.HV_ACPI: hv_base.NO_CHECK,
1057 constants.HV_BOOT_ORDER: (True, ) +
1058 (lambda x: x and len(x.strip("acdn")) == 0,
1059 "Invalid boot order specified, must be one or more of [acdn]",
1061 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
1062 constants.HV_DISK_TYPE:
1063 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
1064 constants.HV_NIC_TYPE:
1065 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
1066 constants.HV_PAE: hv_base.NO_CHECK,
1067 constants.HV_VNC_BIND_ADDRESS:
1068 (False, netutils.IP4Address.IsValid,
1069 "VNC bind address is not a valid IP address", None, None),
1070 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
1071 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
1072 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
1073 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
1074 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
1075 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
1076 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
1077 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
1078 # Add PCI passthrough
1079 constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
1080 constants.HV_REBOOT_BEHAVIOR:
1081 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
1082 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
1083 constants.HV_CPU_CAP: hv_base.NO_CHECK,
1084 constants.HV_CPU_WEIGHT:
1085 (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
1086 constants.HV_VIF_TYPE:
1087 hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
1088 constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
1089 constants.HV_VIRIDIAN: hv_base.NO_CHECK,
1090 constants.HV_XEN_CMD:
1091 hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
1094 def _GetConfig(self, instance, startup_memory, block_devices):
1095 """Create a Xen 3.1 HVM config file.
1098 hvp = instance.hvparams
1103 kpath = hvp[constants.HV_KERNEL_PATH]
1104 config.write("kernel = '%s'\n" % kpath)
1106 config.write("builder = 'hvm'\n")
1107 config.write("memory = %d\n" % startup_memory)
1108 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1109 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1110 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1112 config.write("%s\n" % cpu_pinning)
1113 cpu_cap = hvp[constants.HV_CPU_CAP]
1115 config.write("cpu_cap=%d\n" % cpu_cap)
1116 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1118 config.write("cpu_weight=%d\n" % cpu_weight)
1120 config.write("name = '%s'\n" % instance.name)
1121 if hvp[constants.HV_PAE]:
1122 config.write("pae = 1\n")
1124 config.write("pae = 0\n")
1125 if hvp[constants.HV_ACPI]:
1126 config.write("acpi = 1\n")
1128 config.write("acpi = 0\n")
1129 if hvp[constants.HV_VIRIDIAN]:
1130 config.write("viridian = 1\n")
1132 config.write("viridian = 0\n")
1134 config.write("apic = 1\n")
1135 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1136 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1137 config.write("sdl = 0\n")
1138 config.write("usb = 1\n")
1139 config.write("usbdevice = 'tablet'\n")
1140 config.write("vnc = 1\n")
1141 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1142 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1144 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1146 if instance.network_port > constants.VNC_BASE_PORT:
1147 display = instance.network_port - constants.VNC_BASE_PORT
1148 config.write("vncdisplay = %s\n" % display)
1149 config.write("vncunused = 0\n")
1151 config.write("# vncdisplay = 1\n")
1152 config.write("vncunused = 1\n")
1154 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1156 password = utils.ReadFile(vnc_pwd_file)
1157 except EnvironmentError, err:
1158 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1159 (vnc_pwd_file, err))
1161 config.write("vncpasswd = '%s'\n" % password.rstrip())
1163 config.write("serial = 'pty'\n")
1164 if hvp[constants.HV_USE_LOCALTIME]:
1165 config.write("localtime = 1\n")
1168 # Note: what is called 'nic_type' here, is used as value for the xen nic
1169 # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1170 # the 'vif_type' to avoid a clash of notation.
1171 nic_type = hvp[constants.HV_NIC_TYPE]
1173 if nic_type is None:
1175 if hvp[constants.HV_VIF_TYPE]:
1176 vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1177 # ensure old instances don't change
1178 nic_type_str = vif_type_str
1179 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1180 nic_type_str = ", type=paravirtualized"
1182 # parameter 'model' is only valid with type 'ioemu'
1183 nic_type_str = ", model=%s, type=%s" % \
1184 (nic_type, constants.HT_HVM_VIF_IOEMU)
1185 for idx, nic in enumerate(instance.nics):
1186 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1187 ip = getattr(nic, "ip", None)
1189 nic_str += ", ip=%s" % ip
1190 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1191 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1192 if hvp[constants.HV_VIF_SCRIPT]:
1193 nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1194 vif_data.append("'%s'" % nic_str)
1195 self._WriteNICInfoFile(instance.name, idx, nic)
1197 config.write("vif = [%s]\n" % ",".join(vif_data))
1200 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1202 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1204 iso = "'file:%s,hdc:cdrom,r'" % iso_path
1205 disk_data.append(iso)
1207 config.write("disk = [%s]\n" % (",".join(disk_data)))
1208 # Add PCI passthrough
1210 pci_pass = hvp[constants.HV_PASSTHROUGH]
1212 pci_pass_arr = pci_pass.split(";")
1213 config.write("pci = %s\n" % pci_pass_arr)
1214 config.write("on_poweroff = 'destroy'\n")
1215 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1216 config.write("on_reboot = 'restart'\n")
1218 config.write("on_reboot = 'destroy'\n")
1219 config.write("on_crash = 'restart'\n")
1221 return config.getvalue()