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",
51 constants.FD_BLKTAP2: "tap2:tapdisk:aio",
55 def _CreateConfigCpus(cpu_mask):
56 """Create a CPU config string for Xen's config file.
59 # Convert the string CPU mask to a list of list of int's
60 cpu_list = utils.ParseMultiCpuMask(cpu_mask)
62 if len(cpu_list) == 1:
63 all_cpu_mapping = cpu_list[0]
64 if all_cpu_mapping == constants.CPU_PINNING_OFF:
65 # If CPU pinning has 1 entry that's "all", then remove the
66 # parameter from the config file
69 # If CPU pinning has one non-all entry, mapping all vCPUS (the entire
70 # VM) to one physical CPU, using format 'cpu = "C"'
71 return "cpu = \"%s\"" % ",".join(map(str, all_cpu_mapping))
75 if vcpu[0] == constants.CPU_PINNING_ALL_VAL:
76 cpu_map = constants.CPU_PINNING_ALL_XEN
78 cpu_map = ",".join(map(str, vcpu))
79 return "\"%s\"" % cpu_map
81 # build the result string in format 'cpus = [ "c", "c", "c" ]',
82 # where each c is a physical CPU number, a range, a list, or any
84 return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
87 def _RunInstanceList(fn, instance_list_errors):
88 """Helper function for L{_GetInstanceList} to retrieve the list of instances
92 @param fn: Function to query xen for the list of instances
93 @type instance_list_errors: list
94 @param instance_list_errors: Error list
100 logging.error("Retrieving the instance list from xen failed (%s): %s",
101 result.fail_reason, result.output)
102 instance_list_errors.append(result)
103 raise utils.RetryAgain()
105 # skip over the heading
106 return result.stdout.splitlines()
109 def _ParseInstanceList(lines, include_node):
110 """Parses the output of listing instances by xen.
113 @param lines: Result of retrieving the instance list from xen
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 instance 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 instance 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 _GetInstanceList(fn, include_node, _timeout=5):
148 """Return the list of running instances.
150 See L{_RunInstanceList} and L{_ParseInstanceList} for parameter details.
153 instance_list_errors = []
155 lines = utils.Retry(_RunInstanceList, (0.3, 1.5, 1.0), _timeout,
156 args=(fn, instance_list_errors))
157 except utils.RetryTimeout:
158 if instance_list_errors:
159 instance_list_result = instance_list_errors.pop()
161 errmsg = ("listing instances failed, timeout exceeded (%s): %s" %
162 (instance_list_result.fail_reason, instance_list_result.output))
164 errmsg = "listing instances failed"
166 raise errors.HypervisorError(errmsg)
168 return _ParseInstanceList(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, instance_list):
240 """Updates node information from L{_ParseNodeInfo} with instance info.
243 @param info: Result from L{_ParseNodeInfo}
244 @type instance_list: list of tuples
245 @param instance_list: list of instance information; one tuple per instance
251 for (name, _, mem, vcpus, _, _) in instance_list:
252 if name == _DOM0_NAME:
253 info["memory_dom0"] = mem
254 info["cpu_dom0"] = 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, instance_list):
270 """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
272 @type instance_list: list of tuples
273 @param instance_list: list of instance information; one tuple per instance
276 return _MergeInstanceInfo(_ParseNodeInfo(info), instance_list)
279 def _GetConfigFileDiskData(block_devices, blockdev_prefix,
280 _letters=_DISK_LETTERS):
281 """Get disk directives for Xen config file.
283 This method builds the xen config disk directive according to the
284 given disk_template and block_devices.
286 @param block_devices: list of tuples (cfdev, rldev):
287 - cfdev: dict containing ganeti config disk part
288 - rldev: ganeti.block.bdev.BlockDev object
289 @param blockdev_prefix: a string containing blockdevice prefix,
290 e.g. "sd" for /dev/sda
292 @return: string containing disk directive for xen instance config file
295 if len(block_devices) > len(_letters):
296 raise errors.HypervisorError("Too many disks")
300 for sd_suffix, (cfdev, dev_path, _) in zip(_letters, block_devices):
301 sd_name = blockdev_prefix + sd_suffix
303 if cfdev.mode == constants.DISK_RDWR:
308 if cfdev.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]:
309 driver = _FILE_DRIVER_MAP[cfdev.logical_id[0]]
313 disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode))
318 def _QuoteCpuidField(data):
319 """Add quotes around the CPUID field only if necessary.
321 Xen CPUID fields come in two shapes: LIBXL strings, which need quotes around
322 them, and lists of XEND strings, which don't.
324 @param data: Either type of parameter.
325 @return: The quoted version thereof.
328 return "'%s'" % data if data.startswith("host") else data
331 class XenHypervisor(hv_base.BaseHypervisor):
332 """Xen generic hypervisor interface
334 This is the Xen base class used for both Xen PVM and HVM. It contains
335 all the functionality that is identical for both.
339 REBOOT_RETRY_COUNT = 60
340 REBOOT_RETRY_INTERVAL = 10
341 _ROOT_DIR = pathutils.RUN_DIR + "/xen-hypervisor"
342 _NICS_DIR = _ROOT_DIR + "/nic" # contains NICs' info
343 _DIRS = [_ROOT_DIR, _NICS_DIR]
350 ANCILLARY_FILES_OPT = [
354 def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
355 hv_base.BaseHypervisor.__init__(self)
358 self._cfgdir = pathutils.XEN_CONFIG_DIR
360 self._cfgdir = _cfgdir
362 if _run_cmd_fn is None:
363 self._run_cmd_fn = utils.RunCmd
365 self._run_cmd_fn = _run_cmd_fn
370 def _GetCommandFromHvparams(hvparams):
371 """Returns the Xen command extracted from the given hvparams.
373 @type hvparams: dict of strings
374 @param hvparams: hypervisor parameters
377 if hvparams is None or constants.HV_XEN_CMD not in hvparams:
378 raise errors.HypervisorError("Cannot determine xen command.")
380 return hvparams[constants.HV_XEN_CMD]
382 def _GetCommand(self, hvparams):
383 """Returns Xen command to use.
385 @type hvparams: dict of strings
386 @param hvparams: hypervisor parameters
389 if self._cmd is None:
390 cmd = XenHypervisor._GetCommandFromHvparams(hvparams)
394 if cmd not in constants.KNOWN_XEN_COMMANDS:
395 raise errors.ProgrammerError("Unknown Xen command '%s'" % cmd)
399 def _RunXen(self, args, hvparams):
400 """Wrapper around L{utils.process.RunCmd} to run Xen command.
402 @type hvparams: dict of strings
403 @param hvparams: dictionary of hypervisor params
404 @see: L{utils.process.RunCmd}
407 cmd = [self._GetCommand(hvparams)]
410 return self._run_cmd_fn(cmd)
412 def _ConfigFileName(self, instance_name):
413 """Get the config file name for an instance.
415 @param instance_name: instance name
416 @type instance_name: str
417 @return: fully qualified path to instance config file
421 return utils.PathJoin(self._cfgdir, instance_name)
424 def _WriteNICInfoFile(cls, instance, idx, nic):
425 """Write the Xen config file for the instance.
427 This version of the function just writes the config file from static data.
430 instance_name = instance.name
431 dirs = [(dname, constants.RUN_DIRS_MODE)
432 for dname in cls._DIRS + [cls._InstanceNICDir(instance_name)]]
433 utils.EnsureDirs(dirs)
435 cfg_file = cls._InstanceNICFile(instance_name, idx)
438 data.write("TAGS=%s\n" % r"\ ".join(instance.GetTags()))
440 netinfo = objects.Network.FromDict(nic.netinfo)
441 for k, v in netinfo.HooksDict().iteritems():
442 data.write("%s=%s\n" % (k, v))
444 data.write("MAC=%s\n" % nic.mac)
446 data.write("IP=%s\n" % nic.ip)
447 data.write("INTERFACE_INDEX=%s\n" % str(idx))
449 data.write("INTERFACE_NAME=%s\n" % nic.name)
450 data.write("INTERFACE_UUID=%s\n" % nic.uuid)
451 data.write("MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
452 data.write("LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
455 utils.WriteFile(cfg_file, data=data.getvalue())
456 except EnvironmentError, err:
457 raise errors.HypervisorError("Cannot write Xen instance configuration"
458 " file %s: %s" % (cfg_file, err))
461 def _InstanceNICDir(cls, instance_name):
462 """Returns the directory holding the tap device files for a given instance.
465 return utils.PathJoin(cls._NICS_DIR, instance_name)
468 def _InstanceNICFile(cls, instance_name, seq):
469 """Returns the name of the file containing the tap device for a given NIC
472 return utils.PathJoin(cls._InstanceNICDir(instance_name), str(seq))
475 def _GetConfig(cls, instance, startup_memory, block_devices):
476 """Build Xen configuration for an instance.
479 raise NotImplementedError
481 def _WriteConfigFile(self, instance_name, data):
482 """Write the Xen config file for the instance.
484 This version of the function just writes the config file from static data.
487 # just in case it exists
488 utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
490 cfg_file = self._ConfigFileName(instance_name)
492 utils.WriteFile(cfg_file, data=data)
493 except EnvironmentError, err:
494 raise errors.HypervisorError("Cannot write Xen instance configuration"
495 " file %s: %s" % (cfg_file, err))
497 def _ReadConfigFile(self, instance_name):
498 """Returns the contents of the instance config file.
501 filename = self._ConfigFileName(instance_name)
504 file_content = utils.ReadFile(filename)
505 except EnvironmentError, err:
506 raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
510 def _RemoveConfigFile(self, instance_name):
511 """Remove the xen configuration file.
514 utils.RemoveFile(self._ConfigFileName(instance_name))
516 shutil.rmtree(self._InstanceNICDir(instance_name))
518 if err.errno != errno.ENOENT:
521 def _StashConfigFile(self, instance_name):
522 """Move the Xen config file to the log directory and return its new path.
525 old_filename = self._ConfigFileName(instance_name)
527 (instance_name, utils.TimestampForFilename()))
528 new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
529 utils.RenameFile(old_filename, new_filename)
532 def _GetInstanceList(self, include_node, hvparams):
533 """Wrapper around module level L{_GetInstanceList}.
535 @type hvparams: dict of strings
536 @param hvparams: hypervisor parameters to be used on this node
539 return _GetInstanceList(lambda: self._RunXen(["list"], hvparams),
542 def ListInstances(self, hvparams=None):
543 """Get the list of running instances.
546 instance_list = self._GetInstanceList(False, hvparams)
547 names = [info[0] for info in instance_list]
550 def GetInstanceInfo(self, instance_name, hvparams=None):
551 """Get instance properties.
553 @type instance_name: string
554 @param instance_name: the instance name
555 @type hvparams: dict of strings
556 @param hvparams: the instance's hypervisor params
558 @return: tuple (name, id, memory, vcpus, stat, times)
561 instance_list = self._GetInstanceList(instance_name == _DOM0_NAME, hvparams)
563 for data in instance_list:
564 if data[0] == instance_name:
569 def GetAllInstancesInfo(self, hvparams=None):
570 """Get properties of all instances.
572 @type hvparams: dict of strings
573 @param hvparams: hypervisor parameters
574 @return: list of tuples (name, id, memory, vcpus, stat, times)
577 return self._GetInstanceList(False, hvparams)
579 def _MakeConfigFile(self, instance, startup_memory, block_devices):
580 """Gather configuration details and write to disk.
582 See L{_GetConfig} for arguments.
586 buf.write("# Automatically generated by Ganeti. Do not edit!\n")
588 buf.write(self._GetConfig(instance, startup_memory, block_devices))
591 self._WriteConfigFile(instance.name, buf.getvalue())
593 def StartInstance(self, instance, block_devices, startup_paused):
594 """Start an instance.
597 startup_memory = self._InstanceStartupMemory(instance)
599 self._MakeConfigFile(instance, startup_memory, block_devices)
604 cmd.append(self._ConfigFileName(instance.name))
606 result = self._RunXen(cmd, instance.hvparams)
608 # Move the Xen configuration file to the log directory to avoid
609 # leaving a stale config file behind.
610 stashed_config = self._StashConfigFile(instance.name)
611 raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved"
612 " config file to %s" %
613 (instance.name, result.fail_reason,
614 result.output, stashed_config))
616 def StopInstance(self, instance, force=False, retry=False, name=None):
623 return self._StopInstance(name, force, instance.hvparams)
625 def _ShutdownInstance(self, name, hvparams):
626 """Shutdown an instance if the instance is running.
629 @param name: name of the instance to stop
630 @type hvparams: dict of string
631 @param hvparams: hypervisor parameters of the instance
633 The '-w' flag waits for shutdown to complete which avoids the need
634 to poll in the case where we want to destroy the domain
635 immediately after shutdown.
638 instance_info = self.GetInstanceInfo(name, hvparams=hvparams)
640 if instance_info is None or _IsInstanceShutdown(instance_info[4]):
641 logging.info("Failed to shutdown instance %s, not running", name)
644 return self._RunXen(["shutdown", "-w", name], hvparams)
646 def _DestroyInstance(self, name, hvparams):
647 """Destroy an instance if the instance if the instance exists.
650 @param name: name of the instance to destroy
651 @type hvparams: dict of string
652 @param hvparams: hypervisor parameters of the instance
655 instance_info = self.GetInstanceInfo(name, hvparams=hvparams)
657 if instance_info is None:
658 logging.info("Failed to destroy instance %s, does not exist", name)
661 return self._RunXen(["destroy", name], hvparams)
663 def _StopInstance(self, name, force, hvparams):
667 @param name: name of the instance to destroy
670 @param force: whether to do a "hard" stop (destroy)
672 @type hvparams: dict of string
673 @param hvparams: hypervisor parameters of the instance
677 result = self._DestroyInstance(name, hvparams)
679 self._ShutdownInstance(name, hvparams)
680 result = self._DestroyInstance(name, hvparams)
682 if result is not None and result.failed and \
683 self.GetInstanceInfo(name, hvparams=hvparams) is not None:
684 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
685 (name, result.fail_reason, result.output))
687 # Remove configuration file if stopping/starting instance was successful
688 self._RemoveConfigFile(name)
690 def RebootInstance(self, instance):
691 """Reboot an instance.
694 ini_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
697 raise errors.HypervisorError("Failed to reboot instance %s,"
698 " not running" % instance.name)
700 result = self._RunXen(["reboot", instance.name], instance.hvparams)
702 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
703 (instance.name, result.fail_reason,
706 def _CheckInstance():
707 new_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
709 # check if the domain ID has changed or the run time has decreased
710 if (new_info is not None and
711 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
714 raise utils.RetryAgain()
717 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
718 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
719 except utils.RetryTimeout:
720 raise errors.HypervisorError("Failed to reboot instance %s: instance"
721 " did not reboot in the expected interval" %
724 def BalloonInstanceMemory(self, instance, mem):
725 """Balloon an instance memory to a certain value.
727 @type instance: L{objects.Instance}
728 @param instance: instance to be accepted
730 @param mem: actual memory size to use for instance runtime
733 result = self._RunXen(["mem-set", instance.name, mem], instance.hvparams)
735 raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
736 (instance.name, result.fail_reason,
739 # Update configuration file
740 cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
741 cmd.append(self._ConfigFileName(instance.name))
743 result = utils.RunCmd(cmd)
745 raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
746 (instance.name, result.fail_reason,
749 def GetNodeInfo(self, hvparams=None):
750 """Return information about the node.
752 @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
755 result = self._RunXen(["info"], hvparams)
757 logging.error("Can't retrieve xen hypervisor information (%s): %s",
758 result.fail_reason, result.output)
761 instance_list = self._GetInstanceList(True, hvparams)
762 return _GetNodeInfo(result.stdout, instance_list)
765 def GetInstanceConsole(cls, instance, primary_node, hvparams, beparams):
766 """Return a command for connecting to the console of an instance.
769 xen_cmd = XenHypervisor._GetCommandFromHvparams(hvparams)
770 return objects.InstanceConsole(instance=instance.name,
771 kind=constants.CONS_SSH,
772 host=primary_node.name,
773 user=constants.SSH_CONSOLE_USER,
774 command=[pathutils.XEN_CONSOLE_WRAPPER,
775 xen_cmd, instance.name])
777 def Verify(self, hvparams=None):
778 """Verify the hypervisor.
780 For Xen, this verifies that the xend process is running.
782 @type hvparams: dict of strings
783 @param hvparams: hypervisor parameters to be verified against
785 @return: Problem description if something is wrong, C{None} otherwise
789 return "Could not verify the hypervisor, because no hvparams were" \
792 if constants.HV_XEN_CMD in hvparams:
793 xen_cmd = hvparams[constants.HV_XEN_CMD]
795 self._CheckToolstack(xen_cmd)
796 except errors.HypervisorError:
797 return "The configured xen toolstack '%s' is not available on this" \
800 result = self._RunXen(["info"], hvparams)
802 return "Retrieving information from xen failed: %s, %s" % \
803 (result.fail_reason, result.output)
807 def MigrationInfo(self, instance):
808 """Get instance information to perform a migration.
810 @type instance: L{objects.Instance}
811 @param instance: instance to be migrated
813 @return: content of the xen config file
816 return self._ReadConfigFile(instance.name)
818 def AcceptInstance(self, instance, info, target):
819 """Prepare to accept an instance.
821 @type instance: L{objects.Instance}
822 @param instance: instance to be accepted
824 @param info: content of the xen config file on the source node
826 @param target: target host (usually ip), on this node
831 def FinalizeMigrationDst(self, instance, info, success):
832 """Finalize an instance migration.
834 After a successful migration we write the xen config file.
835 We do nothing on a failure, as we did not change anything at accept time.
837 @type instance: L{objects.Instance}
838 @param instance: instance whose migration is being finalized
840 @param info: content of the xen config file on the source node
841 @type success: boolean
842 @param success: whether the migration was a success or a failure
846 self._WriteConfigFile(instance.name, info)
848 def MigrateInstance(self, cluster_name, instance, target, live):
849 """Migrate an instance to a target node.
851 The migration will not be attempted if the instance is not
854 @type instance: L{objects.Instance}
855 @param instance: the instance to be migrated
857 @param target: ip address of the target node
859 @param live: perform a live migration
862 port = instance.hvparams[constants.HV_MIGRATION_PORT]
864 return self._MigrateInstance(cluster_name, instance.name, target, port,
865 live, instance.hvparams)
867 def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
868 hvparams, _ping_fn=netutils.TcpPing):
869 """Migrate an instance to a target node.
871 @see: L{MigrateInstance} for details
875 raise errors.HypervisorError("No hvparams provided.")
877 if self.GetInstanceInfo(instance_name, hvparams=hvparams) is None:
878 raise errors.HypervisorError("Instance not running, cannot migrate")
880 cmd = self._GetCommand(hvparams)
882 if (cmd == constants.XEN_CMD_XM and
883 not _ping_fn(target, port, live_port_needed=True)):
884 raise errors.HypervisorError("Remote host %s not listening on port"
885 " %s, cannot migrate" % (target, port))
889 if cmd == constants.XEN_CMD_XM:
890 args.extend(["-p", "%d" % port])
894 elif cmd == constants.XEN_CMD_XL:
896 "-s", constants.XL_SSH_CMD % cluster_name,
897 "-C", self._ConfigFileName(instance_name),
901 raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
903 args.extend([instance_name, target])
905 result = self._RunXen(args, hvparams)
907 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
908 (instance_name, result.output))
910 def FinalizeMigrationSource(self, instance, success, live):
911 """Finalize the instance migration on the source node.
913 @type instance: L{objects.Instance}
914 @param instance: the instance that was migrated
916 @param success: whether the migration succeeded or not
918 @param live: whether the user requested a live migration or not
921 # pylint: disable=W0613
923 # remove old xen file after migration succeeded
925 self._RemoveConfigFile(instance.name)
926 except EnvironmentError:
927 logging.exception("Failure while removing instance config file")
929 def GetMigrationStatus(self, instance):
930 """Get the migration status
932 As MigrateInstance for Xen is still blocking, if this method is called it
933 means that MigrateInstance has completed successfully. So we can safely
934 assume that the migration was successful and notify this fact to the client.
936 @type instance: L{objects.Instance}
937 @param instance: the instance that is being migrated
938 @rtype: L{objects.MigrationStatus}
939 @return: the status of the current migration (one of
940 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
941 progress info that can be retrieved from the hypervisor
944 return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
946 def PowercycleNode(self, hvparams=None):
947 """Xen-specific powercycle.
949 This first does a Linux reboot (which triggers automatically a Xen
950 reboot), and if that fails it tries to do a Xen reboot. The reason
951 we don't try a Xen reboot first is that the xen reboot launches an
952 external command which connects to the Xen hypervisor, and that
953 won't work in case the root filesystem is broken and/or the xend
954 daemon is not working.
956 @type hvparams: dict of strings
957 @param hvparams: hypervisor params to be used on this node
961 self.LinuxPowercycle()
963 xen_cmd = self._GetCommand(hvparams)
964 utils.RunCmd([xen_cmd, "debug", "R"])
966 def _CheckToolstack(self, xen_cmd):
967 """Check whether the given toolstack is available on the node.
969 @type xen_cmd: string
970 @param xen_cmd: xen command (e.g. 'xm' or 'xl')
973 binary_found = self._CheckToolstackBinary(xen_cmd)
975 raise errors.HypervisorError("No '%s' binary found on node." % xen_cmd)
976 elif xen_cmd == constants.XEN_CMD_XL:
977 if not self._CheckToolstackXlConfigured():
978 raise errors.HypervisorError("Toolstack '%s' is not enabled on this"
981 def _CheckToolstackBinary(self, xen_cmd):
982 """Checks whether the xen command's binary is found on the machine.
985 if xen_cmd not in constants.KNOWN_XEN_COMMANDS:
986 raise errors.HypervisorError("Unknown xen command '%s'." % xen_cmd)
987 result = self._run_cmd_fn(["which", xen_cmd])
988 return not result.failed
990 def _CheckToolstackXlConfigured(self):
991 """Checks whether xl is enabled on an xl-capable node.
994 @returns: C{True} if 'xl' is enabled, C{False} otherwise
997 result = self._run_cmd_fn([constants.XEN_CMD_XL, "help"])
998 if not result.failed:
1001 if "toolstack" in result.stderr:
1003 # xl fails for some other reason than the toolstack
1005 raise errors.HypervisorError("Cannot run xen ('%s'). Error: %s."
1006 % (constants.XEN_CMD_XL, result.stderr))
1009 class XenPvmHypervisor(XenHypervisor):
1010 """Xen PVM hypervisor interface"""
1013 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
1014 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
1015 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
1016 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
1017 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
1018 constants.HV_ROOT_PATH: hv_base.NO_CHECK,
1019 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
1020 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
1021 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
1022 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
1023 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
1024 constants.HV_REBOOT_BEHAVIOR:
1025 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
1026 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
1027 constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
1028 constants.HV_CPU_WEIGHT:
1029 (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
1030 constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
1031 constants.HV_XEN_CMD:
1032 hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
1033 constants.HV_XEN_CPUID: hv_base.NO_CHECK,
1034 constants.HV_SOUNDHW: hv_base.NO_CHECK,
1037 def _GetConfig(self, instance, startup_memory, block_devices):
1038 """Write the Xen config file for the instance.
1041 hvp = instance.hvparams
1043 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
1045 # if bootloader is True, use bootloader instead of kernel and ramdisk
1047 if hvp[constants.HV_USE_BOOTLOADER]:
1048 # bootloader handling
1049 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
1051 config.write("bootloader = '%s'\n" % bootloader_path)
1053 raise errors.HypervisorError("Bootloader enabled, but missing"
1056 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
1058 config.write("bootargs = '%s'\n" % bootloader_args)
1061 kpath = hvp[constants.HV_KERNEL_PATH]
1062 config.write("kernel = '%s'\n" % kpath)
1065 initrd_path = hvp[constants.HV_INITRD_PATH]
1067 config.write("ramdisk = '%s'\n" % initrd_path)
1069 # rest of the settings
1070 config.write("memory = %d\n" % startup_memory)
1071 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1072 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1073 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1075 config.write("%s\n" % cpu_pinning)
1076 cpu_cap = hvp[constants.HV_CPU_CAP]
1078 config.write("cpu_cap=%d\n" % cpu_cap)
1079 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1081 config.write("cpu_weight=%d\n" % cpu_weight)
1083 config.write("name = '%s'\n" % instance.name)
1086 for idx, nic in enumerate(instance.nics):
1087 nic_str = "mac=%s" % (nic.mac)
1088 ip = getattr(nic, "ip", None)
1090 nic_str += ", ip=%s" % ip
1091 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1092 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1093 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_OVS:
1094 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1095 if nic.nicparams[constants.NIC_VLAN]:
1096 nic_str += "%s" % nic.nicparams[constants.NIC_VLAN]
1097 if hvp[constants.HV_VIF_SCRIPT]:
1098 nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1099 vif_data.append("'%s'" % nic_str)
1100 self._WriteNICInfoFile(instance, idx, nic)
1103 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1105 config.write("vif = [%s]\n" % ",".join(vif_data))
1106 config.write("disk = [%s]\n" % ",".join(disk_data))
1108 if hvp[constants.HV_ROOT_PATH]:
1109 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
1110 config.write("on_poweroff = 'destroy'\n")
1111 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1112 config.write("on_reboot = 'restart'\n")
1114 config.write("on_reboot = 'destroy'\n")
1115 config.write("on_crash = 'restart'\n")
1116 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
1118 cpuid = hvp[constants.HV_XEN_CPUID]
1120 config.write("cpuid = %s\n" % _QuoteCpuidField(cpuid))
1122 if hvp[constants.HV_SOUNDHW]:
1123 config.write("soundhw = '%s'\n" % hvp[constants.HV_SOUNDHW])
1125 return config.getvalue()
1128 class XenHvmHypervisor(XenHypervisor):
1129 """Xen HVM hypervisor interface"""
1131 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
1132 pathutils.VNC_PASSWORD_FILE,
1134 ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
1135 pathutils.VNC_PASSWORD_FILE,
1139 constants.HV_ACPI: hv_base.NO_CHECK,
1140 constants.HV_BOOT_ORDER: (True, ) +
1141 (lambda x: x and len(x.strip("acdn")) == 0,
1142 "Invalid boot order specified, must be one or more of [acdn]",
1144 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
1145 constants.HV_DISK_TYPE:
1146 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
1147 constants.HV_NIC_TYPE:
1148 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
1149 constants.HV_PAE: hv_base.NO_CHECK,
1150 constants.HV_VNC_BIND_ADDRESS:
1151 (False, netutils.IP4Address.IsValid,
1152 "VNC bind address is not a valid IP address", None, None),
1153 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
1154 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
1155 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
1156 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
1157 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
1158 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
1159 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
1160 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
1161 # Add PCI passthrough
1162 constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
1163 constants.HV_REBOOT_BEHAVIOR:
1164 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
1165 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
1166 constants.HV_CPU_CAP: hv_base.NO_CHECK,
1167 constants.HV_CPU_WEIGHT:
1168 (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
1169 constants.HV_VIF_TYPE:
1170 hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
1171 constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
1172 constants.HV_VIRIDIAN: hv_base.NO_CHECK,
1173 constants.HV_XEN_CMD:
1174 hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
1175 constants.HV_XEN_CPUID: hv_base.NO_CHECK,
1176 constants.HV_SOUNDHW: hv_base.NO_CHECK,
1179 def _GetConfig(self, instance, startup_memory, block_devices):
1180 """Create a Xen 3.1 HVM config file.
1183 hvp = instance.hvparams
1188 kpath = hvp[constants.HV_KERNEL_PATH]
1189 config.write("kernel = '%s'\n" % kpath)
1191 config.write("builder = 'hvm'\n")
1192 config.write("memory = %d\n" % startup_memory)
1193 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1194 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1195 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1197 config.write("%s\n" % cpu_pinning)
1198 cpu_cap = hvp[constants.HV_CPU_CAP]
1200 config.write("cpu_cap=%d\n" % cpu_cap)
1201 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1203 config.write("cpu_weight=%d\n" % cpu_weight)
1205 config.write("name = '%s'\n" % instance.name)
1206 if hvp[constants.HV_PAE]:
1207 config.write("pae = 1\n")
1209 config.write("pae = 0\n")
1210 if hvp[constants.HV_ACPI]:
1211 config.write("acpi = 1\n")
1213 config.write("acpi = 0\n")
1214 if hvp[constants.HV_VIRIDIAN]:
1215 config.write("viridian = 1\n")
1217 config.write("viridian = 0\n")
1219 config.write("apic = 1\n")
1220 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1221 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1222 config.write("sdl = 0\n")
1223 config.write("usb = 1\n")
1224 config.write("usbdevice = 'tablet'\n")
1225 config.write("vnc = 1\n")
1226 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1227 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1229 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1231 if instance.network_port > constants.VNC_BASE_PORT:
1232 display = instance.network_port - constants.VNC_BASE_PORT
1233 config.write("vncdisplay = %s\n" % display)
1234 config.write("vncunused = 0\n")
1236 config.write("# vncdisplay = 1\n")
1237 config.write("vncunused = 1\n")
1239 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1241 password = utils.ReadFile(vnc_pwd_file)
1242 except EnvironmentError, err:
1243 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1244 (vnc_pwd_file, err))
1246 config.write("vncpasswd = '%s'\n" % password.rstrip())
1248 config.write("serial = 'pty'\n")
1249 if hvp[constants.HV_USE_LOCALTIME]:
1250 config.write("localtime = 1\n")
1253 # Note: what is called 'nic_type' here, is used as value for the xen nic
1254 # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1255 # the 'vif_type' to avoid a clash of notation.
1256 nic_type = hvp[constants.HV_NIC_TYPE]
1258 if nic_type is None:
1260 if hvp[constants.HV_VIF_TYPE]:
1261 vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1262 # ensure old instances don't change
1263 nic_type_str = vif_type_str
1264 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1265 nic_type_str = ", type=paravirtualized"
1267 # parameter 'model' is only valid with type 'ioemu'
1268 nic_type_str = ", model=%s, type=%s" % \
1269 (nic_type, constants.HT_HVM_VIF_IOEMU)
1270 for idx, nic in enumerate(instance.nics):
1271 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1272 ip = getattr(nic, "ip", None)
1274 nic_str += ", ip=%s" % ip
1275 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1276 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1277 if hvp[constants.HV_VIF_SCRIPT]:
1278 nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1279 vif_data.append("'%s'" % nic_str)
1280 self._WriteNICInfoFile(instance, idx, nic)
1282 config.write("vif = [%s]\n" % ",".join(vif_data))
1285 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1287 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1289 iso = "'file:%s,hdc:cdrom,r'" % iso_path
1290 disk_data.append(iso)
1292 config.write("disk = [%s]\n" % (",".join(disk_data)))
1293 # Add PCI passthrough
1295 pci_pass = hvp[constants.HV_PASSTHROUGH]
1297 pci_pass_arr = pci_pass.split(";")
1298 config.write("pci = %s\n" % pci_pass_arr)
1299 config.write("on_poweroff = 'destroy'\n")
1300 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1301 config.write("on_reboot = 'restart'\n")
1303 config.write("on_reboot = 'destroy'\n")
1304 config.write("on_crash = 'restart'\n")
1306 cpuid = hvp[constants.HV_XEN_CPUID]
1308 config.write("cpuid = %s\n" % _QuoteCpuidField(cpuid))
1310 if hvp[constants.HV_SOUNDHW]:
1311 config.write("soundhw = '%s'\n" % hvp[constants.HV_SOUNDHW])
1313 return config.getvalue()