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 in [constants.DT_FILE, constants.DT_SHARED_FILE]:
299 driver = _FILE_DRIVER_MAP[cfdev.logical_id[0]]
303 disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode))
308 def _QuoteCpuidField(data):
309 """Add quotes around the CPUID field only if necessary.
311 Xen CPUID fields come in two shapes: LIBXL strings, which need quotes around
312 them, and lists of XEND strings, which don't.
314 @param data: Either type of parameter.
315 @return: The quoted version thereof.
318 return "'%s'" % data if data.startswith("host") else data
321 class XenHypervisor(hv_base.BaseHypervisor):
322 """Xen generic hypervisor interface
324 This is the Xen base class used for both Xen PVM and HVM. It contains
325 all the functionality that is identical for both.
329 REBOOT_RETRY_COUNT = 60
330 REBOOT_RETRY_INTERVAL = 10
331 _ROOT_DIR = pathutils.RUN_DIR + "/xen-hypervisor"
332 _NICS_DIR = _ROOT_DIR + "/nic" # contains NICs' info
333 _DIRS = [_ROOT_DIR, _NICS_DIR]
340 ANCILLARY_FILES_OPT = [
344 def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
345 hv_base.BaseHypervisor.__init__(self)
348 self._cfgdir = pathutils.XEN_CONFIG_DIR
350 self._cfgdir = _cfgdir
352 if _run_cmd_fn is None:
353 self._run_cmd_fn = utils.RunCmd
355 self._run_cmd_fn = _run_cmd_fn
359 def _GetCommand(self, hvparams):
360 """Returns Xen command to use.
362 @type hvparams: dict of strings
363 @param hvparams: hypervisor parameters
366 if self._cmd is None:
367 if hvparams is None or constants.HV_XEN_CMD not in hvparams:
368 raise errors.HypervisorError("Cannot determine xen command.")
370 cmd = hvparams[constants.HV_XEN_CMD]
374 if cmd not in constants.KNOWN_XEN_COMMANDS:
375 raise errors.ProgrammerError("Unknown Xen command '%s'" % cmd)
379 def _RunXen(self, args, hvparams):
380 """Wrapper around L{utils.process.RunCmd} to run Xen command.
382 @type hvparams: dict of strings
383 @param hvparams: dictionary of hypervisor params
384 @see: L{utils.process.RunCmd}
387 cmd = [self._GetCommand(hvparams)]
390 return self._run_cmd_fn(cmd)
392 def _ConfigFileName(self, instance_name):
393 """Get the config file name for an instance.
395 @param instance_name: instance name
396 @type instance_name: str
397 @return: fully qualified path to instance config file
401 return utils.PathJoin(self._cfgdir, instance_name)
404 def _WriteNICInfoFile(cls, instance_name, idx, nic):
405 """Write the Xen config file for the instance.
407 This version of the function just writes the config file from static data.
410 dirs = [(dname, constants.RUN_DIRS_MODE)
411 for dname in cls._DIRS + [cls._InstanceNICDir(instance_name)]]
412 utils.EnsureDirs(dirs)
414 cfg_file = cls._InstanceNICFile(instance_name, idx)
418 netinfo = objects.Network.FromDict(nic.netinfo)
419 data.write("NETWORK_NAME=%s\n" % netinfo.name)
421 data.write("NETWORK_SUBNET=%s\n" % netinfo.network)
423 data.write("NETWORK_GATEWAY=%s\n" % netinfo.gateway)
425 data.write("NETWORK_SUBNET6=%s\n" % netinfo.network6)
427 data.write("NETWORK_GATEWAY6=%s\n" % netinfo.gateway6)
428 if netinfo.mac_prefix:
429 data.write("NETWORK_MAC_PREFIX=%s\n" % netinfo.mac_prefix)
431 data.write("NETWORK_TAGS=%s\n" % r"\ ".join(netinfo.tags))
433 data.write("MAC=%s\n" % nic.mac)
434 data.write("IP=%s\n" % nic.ip)
435 data.write("MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
436 data.write("LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
439 utils.WriteFile(cfg_file, data=data.getvalue())
440 except EnvironmentError, err:
441 raise errors.HypervisorError("Cannot write Xen instance configuration"
442 " file %s: %s" % (cfg_file, err))
445 def _InstanceNICDir(cls, instance_name):
446 """Returns the directory holding the tap device files for a given instance.
449 return utils.PathJoin(cls._NICS_DIR, instance_name)
452 def _InstanceNICFile(cls, instance_name, seq):
453 """Returns the name of the file containing the tap device for a given NIC
456 return utils.PathJoin(cls._InstanceNICDir(instance_name), str(seq))
459 def _GetConfig(cls, instance, startup_memory, block_devices):
460 """Build Xen configuration for an instance.
463 raise NotImplementedError
465 def _WriteConfigFile(self, instance_name, data):
466 """Write the Xen config file for the instance.
468 This version of the function just writes the config file from static data.
471 # just in case it exists
472 utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
474 cfg_file = self._ConfigFileName(instance_name)
476 utils.WriteFile(cfg_file, data=data)
477 except EnvironmentError, err:
478 raise errors.HypervisorError("Cannot write Xen instance configuration"
479 " file %s: %s" % (cfg_file, err))
481 def _ReadConfigFile(self, instance_name):
482 """Returns the contents of the instance config file.
485 filename = self._ConfigFileName(instance_name)
488 file_content = utils.ReadFile(filename)
489 except EnvironmentError, err:
490 raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
494 def _RemoveConfigFile(self, instance_name):
495 """Remove the xen configuration file.
498 utils.RemoveFile(self._ConfigFileName(instance_name))
500 shutil.rmtree(self._InstanceNICDir(instance_name))
502 if err.errno != errno.ENOENT:
505 def _StashConfigFile(self, instance_name):
506 """Move the Xen config file to the log directory and return its new path.
509 old_filename = self._ConfigFileName(instance_name)
511 (instance_name, utils.TimestampForFilename()))
512 new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
513 utils.RenameFile(old_filename, new_filename)
516 def _GetInstanceList(self, include_node, hvparams):
517 """Wrapper around module level L{_GetInstanceList}.
519 @type hvparams: dict of strings
520 @param hvparams: hypervisor parameters to be used on this node
523 return _GetInstanceList(lambda: self._RunXen(["list"], hvparams),
526 def ListInstances(self, hvparams=None):
527 """Get the list of running instances.
530 instance_list = self._GetInstanceList(False, hvparams)
531 names = [info[0] for info in instance_list]
534 def GetInstanceInfo(self, instance_name, hvparams=None):
535 """Get instance properties.
537 @type instance_name: string
538 @param instance_name: the instance name
539 @type hvparams: dict of strings
540 @param hvparams: the instance's hypervisor params
542 @return: tuple (name, id, memory, vcpus, stat, times)
545 instance_list = self._GetInstanceList(instance_name == _DOM0_NAME, hvparams)
547 for data in instance_list:
548 if data[0] == instance_name:
553 def GetAllInstancesInfo(self, hvparams=None):
554 """Get properties of all instances.
556 @type hvparams: dict of strings
557 @param hvparams: hypervisor parameters
558 @return: list of tuples (name, id, memory, vcpus, stat, times)
561 return self._GetInstanceList(False, hvparams)
563 def _MakeConfigFile(self, instance, startup_memory, block_devices):
564 """Gather configuration details and write to disk.
566 See L{_GetConfig} for arguments.
570 buf.write("# Automatically generated by Ganeti. Do not edit!\n")
572 buf.write(self._GetConfig(instance, startup_memory, block_devices))
575 self._WriteConfigFile(instance.name, buf.getvalue())
577 def StartInstance(self, instance, block_devices, startup_paused):
578 """Start an instance.
581 startup_memory = self._InstanceStartupMemory(instance,
582 hvparams=instance.hvparams)
584 self._MakeConfigFile(instance, startup_memory, block_devices)
589 cmd.append(self._ConfigFileName(instance.name))
591 result = self._RunXen(cmd, instance.hvparams)
593 # Move the Xen configuration file to the log directory to avoid
594 # leaving a stale config file behind.
595 stashed_config = self._StashConfigFile(instance.name)
596 raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved"
597 " config file to %s" %
598 (instance.name, result.fail_reason,
599 result.output, stashed_config))
601 def StopInstance(self, instance, force=False, retry=False, name=None):
608 return self._StopInstance(name, force, instance.hvparams)
610 def _StopInstance(self, name, force, hvparams):
614 @param name: name of the instance to be shutdown
616 @param force: flag specifying whether shutdown should be forced
617 @type hvparams: dict of string
618 @param hvparams: hypervisor parameters of the instance
626 result = self._RunXen([action, name], hvparams)
628 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
629 (name, result.fail_reason, result.output))
631 # Remove configuration file if stopping/starting instance was successful
632 self._RemoveConfigFile(name)
634 def RebootInstance(self, instance):
635 """Reboot an instance.
638 ini_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
641 raise errors.HypervisorError("Failed to reboot instance %s,"
642 " not running" % instance.name)
644 result = self._RunXen(["reboot", instance.name], instance.hvparams)
646 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
647 (instance.name, result.fail_reason,
650 def _CheckInstance():
651 new_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
653 # check if the domain ID has changed or the run time has decreased
654 if (new_info is not None and
655 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
658 raise utils.RetryAgain()
661 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
662 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
663 except utils.RetryTimeout:
664 raise errors.HypervisorError("Failed to reboot instance %s: instance"
665 " did not reboot in the expected interval" %
668 def BalloonInstanceMemory(self, instance, mem):
669 """Balloon an instance memory to a certain value.
671 @type instance: L{objects.Instance}
672 @param instance: instance to be accepted
674 @param mem: actual memory size to use for instance runtime
677 result = self._RunXen(["mem-set", instance.name, mem], instance.hvparams)
679 raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
680 (instance.name, result.fail_reason,
683 # Update configuration file
684 cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
685 cmd.append(self._ConfigFileName(instance.name))
687 result = utils.RunCmd(cmd)
689 raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
690 (instance.name, result.fail_reason,
693 def GetNodeInfo(self, hvparams=None):
694 """Return information about the node.
696 @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
699 result = self._RunXen(["info"], hvparams)
701 logging.error("Can't retrieve xen hypervisor information (%s): %s",
702 result.fail_reason, result.output)
705 instance_list = self._GetInstanceList(True, hvparams)
706 return _GetNodeInfo(result.stdout, instance_list)
708 def GetInstanceConsole(self, instance, primary_node, hvparams, beparams):
709 """Return a command for connecting to the console of an instance.
712 xen_cmd = self._GetCommand(hvparams)
713 return objects.InstanceConsole(instance=instance.name,
714 kind=constants.CONS_SSH,
715 host=primary_node.name,
716 user=constants.SSH_CONSOLE_USER,
717 command=[pathutils.XEN_CONSOLE_WRAPPER,
718 xen_cmd, instance.name])
720 def Verify(self, hvparams=None):
721 """Verify the hypervisor.
723 For Xen, this verifies that the xend process is running.
725 @type hvparams: dict of strings
726 @param hvparams: hypervisor parameters to be verified against
728 @return: Problem description if something is wrong, C{None} otherwise
732 return "Could not verify the hypervisor, because no hvparams were" \
735 if constants.HV_XEN_CMD in hvparams:
736 xen_cmd = hvparams[constants.HV_XEN_CMD]
738 self._CheckToolstack(xen_cmd)
739 except errors.HypervisorError:
740 return "The configured xen toolstack '%s' is not available on this" \
743 result = self._RunXen(["info"], hvparams)
745 return "Retrieving information from xen failed: %s, %s" % \
746 (result.fail_reason, result.output)
750 def MigrationInfo(self, instance):
751 """Get instance information to perform a migration.
753 @type instance: L{objects.Instance}
754 @param instance: instance to be migrated
756 @return: content of the xen config file
759 return self._ReadConfigFile(instance.name)
761 def AcceptInstance(self, instance, info, target):
762 """Prepare to accept an instance.
764 @type instance: L{objects.Instance}
765 @param instance: instance to be accepted
767 @param info: content of the xen config file on the source node
769 @param target: target host (usually ip), on this node
774 def FinalizeMigrationDst(self, instance, info, success):
775 """Finalize an instance migration.
777 After a successful migration we write the xen config file.
778 We do nothing on a failure, as we did not change anything at accept time.
780 @type instance: L{objects.Instance}
781 @param instance: instance whose migration is being finalized
783 @param info: content of the xen config file on the source node
784 @type success: boolean
785 @param success: whether the migration was a success or a failure
789 self._WriteConfigFile(instance.name, info)
791 def MigrateInstance(self, cluster_name, instance, target, live):
792 """Migrate an instance to a target node.
794 The migration will not be attempted if the instance is not
797 @type instance: L{objects.Instance}
798 @param instance: the instance to be migrated
800 @param target: ip address of the target node
802 @param live: perform a live migration
805 port = instance.hvparams[constants.HV_MIGRATION_PORT]
807 return self._MigrateInstance(cluster_name, instance.name, target, port,
808 live, instance.hvparams)
810 def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
811 hvparams, _ping_fn=netutils.TcpPing):
812 """Migrate an instance to a target node.
814 @see: L{MigrateInstance} for details
818 raise errors.HypervisorError("No hvparams provided.")
820 if self.GetInstanceInfo(instance_name, hvparams=hvparams) is None:
821 raise errors.HypervisorError("Instance not running, cannot migrate")
823 cmd = self._GetCommand(hvparams)
825 if (cmd == constants.XEN_CMD_XM and
826 not _ping_fn(target, port, live_port_needed=True)):
827 raise errors.HypervisorError("Remote host %s not listening on port"
828 " %s, cannot migrate" % (target, port))
832 if cmd == constants.XEN_CMD_XM:
833 args.extend(["-p", "%d" % port])
837 elif cmd == constants.XEN_CMD_XL:
839 "-s", constants.XL_SSH_CMD % cluster_name,
840 "-C", self._ConfigFileName(instance_name),
844 raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
846 args.extend([instance_name, target])
848 result = self._RunXen(args, hvparams)
850 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
851 (instance_name, result.output))
853 def FinalizeMigrationSource(self, instance, success, live):
854 """Finalize the instance migration on the source node.
856 @type instance: L{objects.Instance}
857 @param instance: the instance that was migrated
859 @param success: whether the migration succeeded or not
861 @param live: whether the user requested a live migration or not
864 # pylint: disable=W0613
866 # remove old xen file after migration succeeded
868 self._RemoveConfigFile(instance.name)
869 except EnvironmentError:
870 logging.exception("Failure while removing instance config file")
872 def GetMigrationStatus(self, instance):
873 """Get the migration status
875 As MigrateInstance for Xen is still blocking, if this method is called it
876 means that MigrateInstance has completed successfully. So we can safely
877 assume that the migration was successful and notify this fact to the client.
879 @type instance: L{objects.Instance}
880 @param instance: the instance that is being migrated
881 @rtype: L{objects.MigrationStatus}
882 @return: the status of the current migration (one of
883 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
884 progress info that can be retrieved from the hypervisor
887 return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
889 def PowercycleNode(self, hvparams=None):
890 """Xen-specific powercycle.
892 This first does a Linux reboot (which triggers automatically a Xen
893 reboot), and if that fails it tries to do a Xen reboot. The reason
894 we don't try a Xen reboot first is that the xen reboot launches an
895 external command which connects to the Xen hypervisor, and that
896 won't work in case the root filesystem is broken and/or the xend
897 daemon is not working.
899 @type hvparams: dict of strings
900 @param hvparams: hypervisor params to be used on this node
904 self.LinuxPowercycle()
906 xen_cmd = self._GetCommand(hvparams)
907 utils.RunCmd([xen_cmd, "debug", "R"])
909 def _CheckToolstack(self, xen_cmd):
910 """Check whether the given toolstack is available on the node.
912 @type xen_cmd: string
913 @param xen_cmd: xen command (e.g. 'xm' or 'xl')
916 binary_found = self._CheckToolstackBinary(xen_cmd)
918 raise errors.HypervisorError("No '%s' binary found on node." % xen_cmd)
919 elif xen_cmd == constants.XEN_CMD_XL:
920 if not self._CheckToolstackXlConfigured():
921 raise errors.HypervisorError("Toolstack '%s' is not enabled on this"
924 def _CheckToolstackBinary(self, xen_cmd):
925 """Checks whether the xen command's binary is found on the machine.
928 if xen_cmd not in constants.KNOWN_XEN_COMMANDS:
929 raise errors.HypervisorError("Unknown xen command '%s'." % xen_cmd)
930 result = self._run_cmd_fn(["which", xen_cmd])
931 return not result.failed
933 def _CheckToolstackXlConfigured(self):
934 """Checks whether xl is enabled on an xl-capable node.
937 @returns: C{True} if 'xl' is enabled, C{False} otherwise
940 result = self._run_cmd_fn([constants.XEN_CMD_XL, "help"])
941 if not result.failed:
944 if "toolstack" in result.stderr:
946 # xl fails for some other reason than the toolstack
948 raise errors.HypervisorError("Cannot run xen ('%s'). Error: %s."
949 % (constants.XEN_CMD_XL, result.stderr))
952 class XenPvmHypervisor(XenHypervisor):
953 """Xen PVM hypervisor interface"""
956 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
957 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
958 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
959 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
960 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
961 constants.HV_ROOT_PATH: hv_base.NO_CHECK,
962 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
963 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
964 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
965 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
966 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
967 constants.HV_REBOOT_BEHAVIOR:
968 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
969 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
970 constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
971 constants.HV_CPU_WEIGHT:
972 (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
973 constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
974 constants.HV_XEN_CMD:
975 hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
976 constants.HV_XEN_CPUID: hv_base.NO_CHECK,
979 def _GetConfig(self, instance, startup_memory, block_devices):
980 """Write the Xen config file for the instance.
983 hvp = instance.hvparams
985 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
987 # if bootloader is True, use bootloader instead of kernel and ramdisk
989 if hvp[constants.HV_USE_BOOTLOADER]:
990 # bootloader handling
991 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
993 config.write("bootloader = '%s'\n" % bootloader_path)
995 raise errors.HypervisorError("Bootloader enabled, but missing"
998 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
1000 config.write("bootargs = '%s'\n" % bootloader_args)
1003 kpath = hvp[constants.HV_KERNEL_PATH]
1004 config.write("kernel = '%s'\n" % kpath)
1007 initrd_path = hvp[constants.HV_INITRD_PATH]
1009 config.write("ramdisk = '%s'\n" % initrd_path)
1011 # rest of the settings
1012 config.write("memory = %d\n" % startup_memory)
1013 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1014 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1015 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1017 config.write("%s\n" % cpu_pinning)
1018 cpu_cap = hvp[constants.HV_CPU_CAP]
1020 config.write("cpu_cap=%d\n" % cpu_cap)
1021 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1023 config.write("cpu_weight=%d\n" % cpu_weight)
1025 config.write("name = '%s'\n" % instance.name)
1028 for idx, nic in enumerate(instance.nics):
1029 nic_str = "mac=%s" % (nic.mac)
1030 ip = getattr(nic, "ip", None)
1032 nic_str += ", ip=%s" % ip
1033 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1034 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1035 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_OVS:
1036 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1037 if nic.nicparams[constants.NIC_VLAN]:
1038 nic_str += "%s" % nic.nicparams[constants.NIC_VLAN]
1039 if hvp[constants.HV_VIF_SCRIPT]:
1040 nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1041 vif_data.append("'%s'" % nic_str)
1042 self._WriteNICInfoFile(instance.name, idx, nic)
1045 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1047 config.write("vif = [%s]\n" % ",".join(vif_data))
1048 config.write("disk = [%s]\n" % ",".join(disk_data))
1050 if hvp[constants.HV_ROOT_PATH]:
1051 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
1052 config.write("on_poweroff = 'destroy'\n")
1053 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1054 config.write("on_reboot = 'restart'\n")
1056 config.write("on_reboot = 'destroy'\n")
1057 config.write("on_crash = 'restart'\n")
1058 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
1060 cpuid = hvp[constants.HV_XEN_CPUID]
1062 config.write("cpuid = %s\n" % _QuoteCpuidField(cpuid))
1064 return config.getvalue()
1067 class XenHvmHypervisor(XenHypervisor):
1068 """Xen HVM hypervisor interface"""
1070 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
1071 pathutils.VNC_PASSWORD_FILE,
1073 ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
1074 pathutils.VNC_PASSWORD_FILE,
1078 constants.HV_ACPI: hv_base.NO_CHECK,
1079 constants.HV_BOOT_ORDER: (True, ) +
1080 (lambda x: x and len(x.strip("acdn")) == 0,
1081 "Invalid boot order specified, must be one or more of [acdn]",
1083 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
1084 constants.HV_DISK_TYPE:
1085 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
1086 constants.HV_NIC_TYPE:
1087 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
1088 constants.HV_PAE: hv_base.NO_CHECK,
1089 constants.HV_VNC_BIND_ADDRESS:
1090 (False, netutils.IP4Address.IsValid,
1091 "VNC bind address is not a valid IP address", None, None),
1092 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
1093 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
1094 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
1095 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
1096 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
1097 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
1098 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
1099 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
1100 # Add PCI passthrough
1101 constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
1102 constants.HV_REBOOT_BEHAVIOR:
1103 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
1104 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
1105 constants.HV_CPU_CAP: hv_base.NO_CHECK,
1106 constants.HV_CPU_WEIGHT:
1107 (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
1108 constants.HV_VIF_TYPE:
1109 hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
1110 constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
1111 constants.HV_VIRIDIAN: hv_base.NO_CHECK,
1112 constants.HV_XEN_CMD:
1113 hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
1114 constants.HV_XEN_CPUID: hv_base.NO_CHECK,
1117 def _GetConfig(self, instance, startup_memory, block_devices):
1118 """Create a Xen 3.1 HVM config file.
1121 hvp = instance.hvparams
1126 kpath = hvp[constants.HV_KERNEL_PATH]
1127 config.write("kernel = '%s'\n" % kpath)
1129 config.write("builder = 'hvm'\n")
1130 config.write("memory = %d\n" % startup_memory)
1131 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1132 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1133 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1135 config.write("%s\n" % cpu_pinning)
1136 cpu_cap = hvp[constants.HV_CPU_CAP]
1138 config.write("cpu_cap=%d\n" % cpu_cap)
1139 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1141 config.write("cpu_weight=%d\n" % cpu_weight)
1143 config.write("name = '%s'\n" % instance.name)
1144 if hvp[constants.HV_PAE]:
1145 config.write("pae = 1\n")
1147 config.write("pae = 0\n")
1148 if hvp[constants.HV_ACPI]:
1149 config.write("acpi = 1\n")
1151 config.write("acpi = 0\n")
1152 if hvp[constants.HV_VIRIDIAN]:
1153 config.write("viridian = 1\n")
1155 config.write("viridian = 0\n")
1157 config.write("apic = 1\n")
1158 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1159 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1160 config.write("sdl = 0\n")
1161 config.write("usb = 1\n")
1162 config.write("usbdevice = 'tablet'\n")
1163 config.write("vnc = 1\n")
1164 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1165 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1167 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1169 if instance.network_port > constants.VNC_BASE_PORT:
1170 display = instance.network_port - constants.VNC_BASE_PORT
1171 config.write("vncdisplay = %s\n" % display)
1172 config.write("vncunused = 0\n")
1174 config.write("# vncdisplay = 1\n")
1175 config.write("vncunused = 1\n")
1177 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1179 password = utils.ReadFile(vnc_pwd_file)
1180 except EnvironmentError, err:
1181 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1182 (vnc_pwd_file, err))
1184 config.write("vncpasswd = '%s'\n" % password.rstrip())
1186 config.write("serial = 'pty'\n")
1187 if hvp[constants.HV_USE_LOCALTIME]:
1188 config.write("localtime = 1\n")
1191 # Note: what is called 'nic_type' here, is used as value for the xen nic
1192 # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1193 # the 'vif_type' to avoid a clash of notation.
1194 nic_type = hvp[constants.HV_NIC_TYPE]
1196 if nic_type is None:
1198 if hvp[constants.HV_VIF_TYPE]:
1199 vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1200 # ensure old instances don't change
1201 nic_type_str = vif_type_str
1202 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1203 nic_type_str = ", type=paravirtualized"
1205 # parameter 'model' is only valid with type 'ioemu'
1206 nic_type_str = ", model=%s, type=%s" % \
1207 (nic_type, constants.HT_HVM_VIF_IOEMU)
1208 for idx, nic in enumerate(instance.nics):
1209 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1210 ip = getattr(nic, "ip", None)
1212 nic_str += ", ip=%s" % ip
1213 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1214 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1215 if hvp[constants.HV_VIF_SCRIPT]:
1216 nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1217 vif_data.append("'%s'" % nic_str)
1218 self._WriteNICInfoFile(instance.name, idx, nic)
1220 config.write("vif = [%s]\n" % ",".join(vif_data))
1223 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1225 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1227 iso = "'file:%s,hdc:cdrom,r'" % iso_path
1228 disk_data.append(iso)
1230 config.write("disk = [%s]\n" % (",".join(disk_data)))
1231 # Add PCI passthrough
1233 pci_pass = hvp[constants.HV_PASSTHROUGH]
1235 pci_pass_arr = pci_pass.split(";")
1236 config.write("pci = %s\n" % pci_pass_arr)
1237 config.write("on_poweroff = 'destroy'\n")
1238 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1239 config.write("on_reboot = 'restart'\n")
1241 config.write("on_reboot = 'destroy'\n")
1242 config.write("on_crash = 'restart'\n")
1244 cpuid = hvp[constants.HV_XEN_CPUID]
1246 config.write("cpuid = %s\n" % _QuoteCpuidField(cpuid))
1248 return config.getvalue()