blob: f7410f4e89f141bb6e20a7fb920f8f787c33a970 [file] [log] [blame]
#
#
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Xen hypervisors
"""
import logging
import errno
import string # pylint: disable=W0402
import shutil
import time
from cStringIO import StringIO
from ganeti import constants
from ganeti import errors
from ganeti import utils
from ganeti.hypervisor import hv_base
from ganeti import netutils
from ganeti import objects
from ganeti import pathutils
XEND_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xend-config.sxp")
XL_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xen/xl.conf")
VIF_BRIDGE_SCRIPT = utils.PathJoin(pathutils.XEN_CONFIG_DIR,
"scripts/vif-bridge")
_DOM0_NAME = "Domain-0"
_DISK_LETTERS = string.ascii_lowercase
_FILE_DRIVER_MAP = {
constants.FD_LOOP: "file",
constants.FD_BLKTAP: "tap:aio",
constants.FD_BLKTAP2: "tap2:tapdisk:aio",
}
def _CreateConfigCpus(cpu_mask):
"""Create a CPU config string for Xen's config file.
"""
# Convert the string CPU mask to a list of list of int's
cpu_list = utils.ParseMultiCpuMask(cpu_mask)
if len(cpu_list) == 1:
all_cpu_mapping = cpu_list[0]
if all_cpu_mapping == constants.CPU_PINNING_OFF:
# If CPU pinning has 1 entry that's "all", then remove the
# parameter from the config file
return None
else:
# If CPU pinning has one non-all entry, mapping all vCPUS (the entire
# VM) to one physical CPU, using format 'cpu = "C"'
return "cpu = \"%s\"" % ",".join(map(str, all_cpu_mapping))
else:
def _GetCPUMap(vcpu):
if vcpu[0] == constants.CPU_PINNING_ALL_VAL:
cpu_map = constants.CPU_PINNING_ALL_XEN
else:
cpu_map = ",".join(map(str, vcpu))
return "\"%s\"" % cpu_map
# build the result string in format 'cpus = [ "c", "c", "c" ]',
# where each c is a physical CPU number, a range, a list, or any
# combination
return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
def _RunInstanceList(fn, instance_list_errors):
"""Helper function for L{_GetAllInstanceList} to retrieve the list
of instances from xen.
@type fn: callable
@param fn: Function to query xen for the list of instances
@type instance_list_errors: list
@param instance_list_errors: Error list
@rtype: list
"""
result = fn()
if result.failed:
logging.error("Retrieving the instance list from xen failed (%s): %s",
result.fail_reason, result.output)
instance_list_errors.append(result)
raise utils.RetryAgain()
# skip over the heading
return result.stdout.splitlines()
class _InstanceCrashed(errors.GenericError):
"""Instance has reached a violent ending.
This is raised within the Xen hypervisor only, and should not be seen or used
outside.
"""
def _ParseInstanceList(lines, include_node):
"""Parses the output of listing instances by xen.
@type lines: list
@param lines: Result of retrieving the instance list from xen
@type include_node: boolean
@param include_node: If True, return information for Dom0
@return: list of tuple containing (name, id, memory, vcpus, state, time
spent)
"""
result = []
# Iterate through all lines while ignoring header
for line in lines[1:]:
# The format of lines is:
# Name ID Mem(MiB) VCPUs State Time(s)
# Domain-0 0 3418 4 r----- 266.2
data = line.split()
if len(data) != 6:
raise errors.HypervisorError("Can't parse instance list,"
" line: %s" % line)
try:
data[1] = int(data[1])
data[2] = int(data[2])
data[3] = int(data[3])
data[4] = _XenToHypervisorInstanceState(data[4])
data[5] = float(data[5])
except (TypeError, ValueError), err:
raise errors.HypervisorError("Can't parse instance list,"
" line: %s, error: %s" % (line, err))
except _InstanceCrashed:
# The crashed instance can be interpreted as being down, so we omit it
# from the instance list.
continue
# skip the Domain-0 (optional)
if include_node or data[0] != _DOM0_NAME:
result.append(data)
return result
def _GetAllInstanceList(fn, include_node, delays, timeout):
"""Return the list of instances including running and shutdown.
See L{_RunInstanceList} and L{_ParseInstanceList} for parameter details.
"""
instance_list_errors = []
try:
lines = utils.Retry(_RunInstanceList, delays, timeout,
args=(fn, instance_list_errors))
except utils.RetryTimeout:
if instance_list_errors:
instance_list_result = instance_list_errors.pop()
errmsg = ("listing instances failed, timeout exceeded (%s): %s" %
(instance_list_result.fail_reason, instance_list_result.output))
else:
errmsg = "listing instances failed"
raise errors.HypervisorError(errmsg)
return _ParseInstanceList(lines, include_node)
def _IsInstanceRunning(instance_info):
"""Determine whether an instance is running.
An instance is running if it is in the following Xen states:
running, blocked, paused, or dying (about to be destroyed / shutdown).
For some strange reason, Xen once printed 'rb----' which does not make any
sense because an instance cannot be both running and blocked. Fortunately,
for Ganeti 'running' or 'blocked' is the same as 'running'.
A state of nothing '------' means that the domain is runnable but it is not
currently running. That means it is in the queue behind other domains waiting
to be scheduled to run.
http://old-list-archives.xenproject.org/xen-users/2007-06/msg00849.html
A dying instance is about to be removed, but it is still consuming resources,
and counts as running.
@type instance_info: string
@param instance_info: Information about instance, as supplied by Xen.
@rtype: bool
@return: Whether an instance is running.
"""
allowable_running_prefixes = [
"r--",
"rb-",
"-b-",
"---",
]
def _RunningWithSuffix(suffix):
return map(lambda x: x + suffix, allowable_running_prefixes)
# The shutdown suspend ("ss") state is encountered during migration, where
# the instance is still considered to be running.
# The shutdown restart ("sr") is probably encountered during restarts - still
# running.
# See Xen commit e1475a6693aac8cddc4bdd456548aa05a625556b
return instance_info in _RunningWithSuffix("---") \
or instance_info in _RunningWithSuffix("ss-") \
or instance_info in _RunningWithSuffix("sr-") \
or instance_info == "-----d"
def _IsInstanceShutdown(instance_info):
"""Determine whether the instance is shutdown.
An instance is shutdown when a user shuts it down from within, and we do not
remove domains to be able to detect that.
The dying state has been added as a precaution, as Xen's status reporting is
weird.
"""
return instance_info == "---s--" \
or instance_info == "---s-d"
def _IgnorePaused(instance_info):
"""Removes information about whether a Xen state is paused from the state.
As it turns out, an instance can be reported as paused in almost any
condition. Paused instances can be paused, running instances can be paused for
scheduling, and any other condition can appear to be paused as a result of
races or improbable conditions in Xen's status reporting.
As we do not use Xen's pause commands in any way at the time, we can simply
ignore the paused field and save ourselves a lot of trouble.
Should we ever use the pause commands, several samples would be needed before
we could confirm the domain as paused.
"""
return instance_info.replace('p', '-')
def _IsCrashed(instance_info):
"""Returns whether an instance is in the crashed Xen state.
When a horrible misconfiguration happens to a Xen domain, it can crash,
meaning that it encounters a violent ending. While this state usually flashes
only temporarily before the domain is restarted, being able to check for it
allows Ganeti not to act confused and do something about it.
"""
return instance_info.count('c') > 0
def _XenToHypervisorInstanceState(instance_info):
"""Maps Xen states to hypervisor states.
@type instance_info: string
@param instance_info: Information about instance, as supplied by Xen.
@rtype: L{hv_base.HvInstanceState}
"""
instance_info = _IgnorePaused(instance_info)
if _IsCrashed(instance_info):
raise _InstanceCrashed("Instance detected as crashed, should be omitted")
if _IsInstanceRunning(instance_info):
return hv_base.HvInstanceState.RUNNING
elif _IsInstanceShutdown(instance_info):
return hv_base.HvInstanceState.SHUTDOWN
else:
raise errors.HypervisorError("hv_xen._XenToHypervisorInstanceState:"
" unhandled Xen instance state '%s'" %
instance_info)
def _GetRunningInstanceList(fn, include_node, delays, timeout):
"""Return the list of running instances.
See L{_GetAllInstanceList} for parameter details.
"""
instances = _GetAllInstanceList(fn, include_node, delays, timeout)
return [i for i in instances if hv_base.HvInstanceState.IsRunning(i[4])]
def _GetShutdownInstanceList(fn, include_node, delays, timeout):
"""Return the list of shutdown instances.
See L{_GetAllInstanceList} for parameter details.
"""
instances = _GetAllInstanceList(fn, include_node, delays, timeout)
return [i for i in instances if hv_base.HvInstanceState.IsShutdown(i[4])]
def _ParseNodeInfo(info):
"""Return information about the node.
@return: a dict with the following keys (memory values in MiB):
- memory_total: the total memory size on the node
- memory_free: the available memory on the node for instances
- nr_cpus: total number of CPUs
- nr_nodes: in a NUMA system, the number of domains
- nr_sockets: the number of physical CPU sockets in the node
- hv_version: the hypervisor version in the form (major, minor)
"""
result = {}
cores_per_socket = threads_per_core = nr_cpus = None
xen_major, xen_minor = None, None
memory_total = None
memory_free = None
for line in info.splitlines():
fields = line.split(":", 1)
if len(fields) < 2:
continue
(key, val) = map(lambda s: s.strip(), fields)
# Note: in Xen 3, memory has changed to total_memory
if key in ("memory", "total_memory"):
memory_total = int(val)
elif key == "free_memory":
memory_free = int(val)
elif key == "nr_cpus":
nr_cpus = result["cpu_total"] = int(val)
elif key == "nr_nodes":
result["cpu_nodes"] = int(val)
elif key == "cores_per_socket":
cores_per_socket = int(val)
elif key == "threads_per_core":
threads_per_core = int(val)
elif key == "xen_major":
xen_major = int(val)
elif key == "xen_minor":
xen_minor = int(val)
if None not in [cores_per_socket, threads_per_core, nr_cpus]:
result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
if memory_free is not None:
result["memory_free"] = memory_free
if memory_total is not None:
result["memory_total"] = memory_total
if not (xen_major is None or xen_minor is None):
result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
return result
def _MergeInstanceInfo(info, instance_list):
"""Updates node information from L{_ParseNodeInfo} with instance info.
@type info: dict
@param info: Result from L{_ParseNodeInfo}
@type instance_list: list of tuples
@param instance_list: list of instance information; one tuple per instance
@rtype: dict
"""
total_instmem = 0
for (name, _, mem, vcpus, _, _) in instance_list:
if name == _DOM0_NAME:
info["memory_dom0"] = mem
info["cpu_dom0"] = vcpus
# Include Dom0 in total memory usage
total_instmem += mem
memory_free = info.get("memory_free")
memory_total = info.get("memory_total")
# Calculate memory used by hypervisor
if None not in [memory_total, memory_free, total_instmem]:
info["memory_hv"] = memory_total - memory_free - total_instmem
return info
def _GetNodeInfo(info, instance_list):
"""Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
@type instance_list: list of tuples
@param instance_list: list of instance information; one tuple per instance
"""
return _MergeInstanceInfo(_ParseNodeInfo(info), instance_list)
def _GetConfigFileDiskData(block_devices, blockdev_prefix,
_letters=_DISK_LETTERS):
"""Get disk directives for Xen config file.
This method builds the xen config disk directive according to the
given disk_template and block_devices.
@param block_devices: list of tuples (cfdev, rldev):
- cfdev: dict containing ganeti config disk part
- rldev: ganeti.block.bdev.BlockDev object
@param blockdev_prefix: a string containing blockdevice prefix,
e.g. "sd" for /dev/sda
@return: string containing disk directive for xen instance config file
"""
if len(block_devices) > len(_letters):
raise errors.HypervisorError("Too many disks")
disk_data = []
for sd_suffix, (cfdev, dev_path, _) in zip(_letters, block_devices):
sd_name = blockdev_prefix + sd_suffix
if cfdev.mode == constants.DISK_RDWR:
mode = "w"
else:
mode = "r"
if cfdev.dev_type in constants.DTS_FILEBASED:
driver = _FILE_DRIVER_MAP[cfdev.logical_id[0]]
else:
driver = "phy"
disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode))
return disk_data
def _QuoteCpuidField(data):
"""Add quotes around the CPUID field only if necessary.
Xen CPUID fields come in two shapes: LIBXL strings, which need quotes around
them, and lists of XEND strings, which don't.
@param data: Either type of parameter.
@return: The quoted version thereof.
"""
return "'%s'" % data if data.startswith("host") else data
def _ConfigureNIC(instance, seq, nic, tap):
"""Run the network configuration script for a specified NIC
See L{hv_base.ConfigureNIC}.
@type instance: instance object
@param instance: instance we're acting on
@type seq: int
@param seq: nic sequence number
@type nic: nic object
@param nic: nic we're acting on
@type tap: str
@param tap: the host's tap interface this NIC corresponds to
"""
hv_base.ConfigureNIC(pathutils.XEN_IFUP_OS, instance, seq, nic, tap)
class XenHypervisor(hv_base.BaseHypervisor):
"""Xen generic hypervisor interface
This is the Xen base class used for both Xen PVM and HVM. It contains
all the functionality that is identical for both.
"""
CAN_MIGRATE = True
REBOOT_RETRY_COUNT = 60
REBOOT_RETRY_INTERVAL = 10
_ROOT_DIR = pathutils.RUN_DIR + "/xen-hypervisor"
# contains NICs' info
_NICS_DIR = _ROOT_DIR + "/nic"
# contains the pidfiles of socat processes used to migrate instaces under xl
_MIGRATION_DIR = _ROOT_DIR + "/migration"
_DIRS = [_ROOT_DIR, _NICS_DIR, _MIGRATION_DIR]
_INSTANCE_LIST_DELAYS = (0.3, 1.5, 1.0)
_INSTANCE_LIST_TIMEOUT = 5
ANCILLARY_FILES = [
XEND_CONFIG_FILE,
XL_CONFIG_FILE,
VIF_BRIDGE_SCRIPT,
]
ANCILLARY_FILES_OPT = [
XEND_CONFIG_FILE,
XL_CONFIG_FILE,
]
def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
hv_base.BaseHypervisor.__init__(self)
if _cfgdir is None:
self._cfgdir = pathutils.XEN_CONFIG_DIR
else:
self._cfgdir = _cfgdir
if _run_cmd_fn is None:
self._run_cmd_fn = utils.RunCmd
else:
self._run_cmd_fn = _run_cmd_fn
self._cmd = _cmd
@staticmethod
def _GetCommandFromHvparams(hvparams):
"""Returns the Xen command extracted from the given hvparams.
@type hvparams: dict of strings
@param hvparams: hypervisor parameters
"""
if hvparams is None or constants.HV_XEN_CMD not in hvparams:
raise errors.HypervisorError("Cannot determine xen command.")
else:
return hvparams[constants.HV_XEN_CMD]
def _GetCommand(self, hvparams):
"""Returns Xen command to use.
@type hvparams: dict of strings
@param hvparams: hypervisor parameters
"""
if self._cmd is None:
cmd = XenHypervisor._GetCommandFromHvparams(hvparams)
else:
cmd = self._cmd
if cmd not in constants.KNOWN_XEN_COMMANDS:
raise errors.ProgrammerError("Unknown Xen command '%s'" % cmd)
return cmd
def _RunXen(self, args, hvparams, timeout=None):
"""Wrapper around L{utils.process.RunCmd} to run Xen command.
@type hvparams: dict of strings
@param hvparams: dictionary of hypervisor params
@type timeout: int or None
@param timeout: if a timeout (in seconds) is specified, the command will be
terminated after that number of seconds.
@see: L{utils.process.RunCmd}
"""
cmd = []
if timeout is not None:
cmd.extend(["timeout", str(timeout)])
cmd.extend([self._GetCommand(hvparams)])
cmd.extend(args)
return self._run_cmd_fn(cmd)
def _ConfigFileName(self, instance_name):
"""Get the config file name for an instance.
@param instance_name: instance name
@type instance_name: str
@return: fully qualified path to instance config file
@rtype: str
"""
return utils.PathJoin(self._cfgdir, instance_name)
@classmethod
def _EnsureDirs(cls, extra_dirs=None):
"""Makes sure that the directories needed by the hypervisor exist.
@type extra_dirs: list of string or None
@param extra_dirs: Additional directories which ought to exist.
"""
if extra_dirs is None:
extra_dirs = []
dirs = [(dname, constants.RUN_DIRS_MODE) for dname in
(cls._DIRS + extra_dirs)]
utils.EnsureDirs(dirs)
@classmethod
def _WriteNICInfoFile(cls, instance, idx, nic):
"""Write the Xen config file for the instance.
This version of the function just writes the config file from static data.
"""
instance_name = instance.name
cls._EnsureDirs(extra_dirs=[cls._InstanceNICDir(instance_name)])
cfg_file = cls._InstanceNICFile(instance_name, idx)
data = StringIO()
data.write("TAGS=\"%s\"\n" % r"\ ".join(instance.GetTags()))
if nic.netinfo:
netinfo = objects.Network.FromDict(nic.netinfo)
for k, v in netinfo.HooksDict().iteritems():
data.write("%s=\"%s\"\n" % (k, v))
data.write("MAC=%s\n" % nic.mac)
if nic.ip:
data.write("IP=%s\n" % nic.ip)
data.write("INTERFACE_INDEX=%s\n" % str(idx))
if nic.name:
data.write("INTERFACE_NAME=%s\n" % nic.name)
data.write("INTERFACE_UUID=%s\n" % nic.uuid)
data.write("MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
data.write("LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
data.write("VLAN=%s\n" % nic.nicparams[constants.NIC_VLAN])
try:
utils.WriteFile(cfg_file, data=data.getvalue())
except EnvironmentError, err:
raise errors.HypervisorError("Cannot write Xen instance configuration"
" file %s: %s" % (cfg_file, err))
@staticmethod
def VersionsSafeForMigration(src, target):
"""Decide if migration is likely to suceed for hypervisor versions.
Given two versions of a hypervisor, give a guess whether live migration
from the one version to the other version is likely to succeed. For Xen,
the heuristics is, that an increase by one on the second digit is OK. This
fits with the current numbering scheme.
@type src: list or tuple
@type target: list or tuple
@rtype: bool
"""
if src == target:
return True
if len(src) < 2 or len(target) < 2:
return False
return src[0] == target[0] and target[1] in [src[1], src[1] + 1]
@classmethod
def _InstanceNICDir(cls, instance_name):
"""Returns the directory holding the tap device files for a given instance.
"""
return utils.PathJoin(cls._NICS_DIR, instance_name)
@classmethod
def _InstanceNICFile(cls, instance_name, seq):
"""Returns the name of the file containing the tap device for a given NIC
"""
return utils.PathJoin(cls._InstanceNICDir(instance_name), str(seq))
@classmethod
def _InstanceMigrationPidfile(cls, _instance_name):
"""Returns the name of the pid file for a socat process used to migrate.
"""
#TODO(riba): At the moment, we are using a single pidfile because we
# use a single port for migrations at the moment. This is because we do not
# allow more migrations, so dynamic port selection and the needed port
# modifications are not needed.
# The _instance_name parameter has been left here for future use.
return utils.PathJoin(cls._MIGRATION_DIR, constants.XL_MIGRATION_PIDFILE)
@classmethod
def _GetConfig(cls, instance, startup_memory, block_devices):
"""Build Xen configuration for an instance.
"""
raise NotImplementedError
def _WriteNicConfig(self, config, instance, hvp):
vif_data = []
# only XenHvmHypervisor has these hvparams
nic_type = hvp.get(constants.HV_NIC_TYPE, None)
vif_type = hvp.get(constants.HV_VIF_TYPE, None)
nic_type_str = ""
if nic_type or vif_type:
if nic_type is None:
if vif_type:
nic_type_str = ", type=%s" % vif_type
elif nic_type == constants.HT_NIC_PARAVIRTUAL:
nic_type_str = ", type=paravirtualized"
else:
# parameter 'model' is only valid with type 'ioemu'
nic_type_str = ", model=%s, type=%s" % \
(nic_type, constants.HT_HVM_VIF_IOEMU)
for idx, nic in enumerate(instance.nics):
nic_args = {}
nic_args["mac"] = "%s%s" % (nic.mac, nic_type_str)
if nic.name and \
nic.name.startswith(constants.INSTANCE_COMMUNICATION_NIC_PREFIX):
tap = hv_base.GenerateTapName()
nic_args["vifname"] = tap
nic_args["script"] = pathutils.XEN_VIF_METAD_SETUP
nic.name = tap
else:
ip = getattr(nic, "ip", None)
if ip is not None:
nic_args["ip"] = ip
if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
nic_args["bridge"] = nic.nicparams[constants.NIC_LINK]
elif nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_OVS:
nic_args["bridge"] = nic.nicparams[constants.NIC_LINK]
if nic.nicparams[constants.NIC_VLAN]:
nic_args["bridge"] += nic.nicparams[constants.NIC_VLAN]
if hvp[constants.HV_VIF_SCRIPT]:
nic_args["script"] = hvp[constants.HV_VIF_SCRIPT]
nic_str = ", ".join(["%s=%s" % p for p in nic_args.items()])
vif_data.append("'%s'" % (nic_str, ))
self._WriteNICInfoFile(instance, idx, nic)
config.write("vif = [%s]\n" % ",".join(vif_data))
def _WriteConfigFile(self, instance_name, data):
"""Write the Xen config file for the instance.
This version of the function just writes the config file from static data.
"""
# just in case it exists
utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
cfg_file = self._ConfigFileName(instance_name)
try:
utils.WriteFile(cfg_file, data=data)
except EnvironmentError, err:
raise errors.HypervisorError("Cannot write Xen instance configuration"
" file %s: %s" % (cfg_file, err))
def _ReadConfigFile(self, instance_name):
"""Returns the contents of the instance config file.
"""
filename = self._ConfigFileName(instance_name)
try:
file_content = utils.ReadFile(filename)
except EnvironmentError, err:
raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
return file_content
def _RemoveConfigFile(self, instance_name):
"""Remove the xen configuration file.
"""
utils.RemoveFile(self._ConfigFileName(instance_name))
try:
shutil.rmtree(self._InstanceNICDir(instance_name))
except OSError, err:
if err.errno != errno.ENOENT:
raise
def _StashConfigFile(self, instance_name):
"""Move the Xen config file to the log directory and return its new path.
"""
old_filename = self._ConfigFileName(instance_name)
base = ("%s-%s" %
(instance_name, utils.TimestampForFilename()))
new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
utils.RenameFile(old_filename, new_filename)
return new_filename
def _GetInstanceList(self, include_node, hvparams):
"""Wrapper around module level L{_GetAllInstanceList}.
@type hvparams: dict of strings
@param hvparams: hypervisor parameters to be used on this node
"""
return _GetAllInstanceList(lambda: self._RunXen(["list"], hvparams),
include_node, delays=self._INSTANCE_LIST_DELAYS,
timeout=self._INSTANCE_LIST_TIMEOUT)
def ListInstances(self, hvparams=None):
"""Get the list of running instances.
@type hvparams: dict of strings
@param hvparams: the instance's hypervisor params
@rtype: list of strings
@return: names of running instances
"""
instance_list = _GetRunningInstanceList(
lambda: self._RunXen(["list"], hvparams),
False, delays=self._INSTANCE_LIST_DELAYS,
timeout=self._INSTANCE_LIST_TIMEOUT)
return [info[0] for info in instance_list]
def GetInstanceInfo(self, instance_name, hvparams=None):
"""Get instance properties.
@type instance_name: string
@param instance_name: the instance name
@type hvparams: dict of strings
@param hvparams: the instance's hypervisor params
@return: tuple (name, id, memory, vcpus, stat, times)
"""
instance_list = self._GetInstanceList(instance_name == _DOM0_NAME, hvparams)
result = None
for data in instance_list:
if data[0] == instance_name:
result = data
break
return result
def GetAllInstancesInfo(self, hvparams=None):
"""Get properties of all instances.
@type hvparams: dict of strings
@param hvparams: hypervisor parameters
@rtype: (string, string, int, int, HypervisorInstanceState, int)
@return: list of tuples (name, id, memory, vcpus, state, times)
"""
return self._GetInstanceList(False, hvparams)
def _MakeConfigFile(self, instance, startup_memory, block_devices):
"""Gather configuration details and write to disk.
See L{_GetConfig} for arguments.
"""
buf = StringIO()
buf.write("# Automatically generated by Ganeti. Do not edit!\n")
buf.write("\n")
buf.write(self._GetConfig(instance, startup_memory, block_devices))
buf.write("\n")
self._WriteConfigFile(instance.name, buf.getvalue())
def StartInstance(self, instance, block_devices, startup_paused):
"""Start an instance.
"""
startup_memory = self._InstanceStartupMemory(instance)
self._MakeConfigFile(instance, startup_memory, block_devices)
cmd = ["create"]
if startup_paused:
cmd.append("-p")
cmd.append(self._ConfigFileName(instance.name))
result = self._RunXen(cmd, instance.hvparams)
if result.failed:
# Move the Xen configuration file to the log directory to avoid
# leaving a stale config file behind.
stashed_config = self._StashConfigFile(instance.name)
raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved"
" config file to %s" %
(instance.name, result.fail_reason,
result.output, stashed_config))
for nic_seq, nic in enumerate(instance.nics):
if nic.name and nic.name.startswith("gnt.com."):
_ConfigureNIC(instance, nic_seq, nic, nic.name)
def StopInstance(self, instance, force=False, retry=False, name=None,
timeout=None):
"""Stop an instance.
A soft shutdown can be interrupted. A hard shutdown tries forever.
"""
assert(timeout is None or force is not None)
if name is None:
name = instance.name
return self._StopInstance(name, force, instance.hvparams, timeout)
def _ShutdownInstance(self, name, hvparams, timeout):
"""Shutdown an instance if the instance is running.
The '-w' flag waits for shutdown to complete which avoids the need
to poll in the case where we want to destroy the domain
immediately after shutdown.
@type name: string
@param name: name of the instance to stop
@type hvparams: dict of string
@param hvparams: hypervisor parameters of the instance
@type timeout: int or None
@param timeout: a timeout after which the shutdown command should be killed,
or None for no timeout
"""
instance_info = self.GetInstanceInfo(name, hvparams=hvparams)
if instance_info is None or _IsInstanceShutdown(instance_info[4]):
logging.info("Failed to shutdown instance %s, not running", name)
return None
return self._RunXen(["shutdown", "-w", name], hvparams, timeout)
def _DestroyInstance(self, name, hvparams):
"""Destroy an instance if the instance if the instance exists.
@type name: string
@param name: name of the instance to destroy
@type hvparams: dict of string
@param hvparams: hypervisor parameters of the instance
"""
instance_info = self.GetInstanceInfo(name, hvparams=hvparams)
if instance_info is None:
logging.info("Failed to destroy instance %s, does not exist", name)
return None
return self._RunXen(["destroy", name], hvparams)
# Destroy a domain only if necessary
#
# This method checks if the domain has already been destroyed before
# issuing the 'destroy' command. This step is necessary to handle
# domains created by other versions of Ganeti. For example, an
# instance created with 2.10 will be destroy by the
# '_ShutdownInstance', thus not requiring an additional destroy,
# which would cause an error if issued. See issue 619.
def _DestroyInstanceIfAlive(self, name, hvparams):
instance_info = self.GetInstanceInfo(name, hvparams=hvparams)
if instance_info is None:
raise errors.HypervisorError("Failed to destroy instance %s, already"
" destroyed" % name)
else:
self._DestroyInstance(name, hvparams)
def _StopInstance(self, name, force, hvparams, timeout):
"""Stop an instance.
@type name: string
@param name: name of the instance to destroy
@type force: boolean
@param force: whether to do a "hard" stop (destroy)
@type hvparams: dict of string
@param hvparams: hypervisor parameters of the instance
@type timeout: int or None
@param timeout: a timeout after which the shutdown command should be killed,
or None for no timeout
"""
instance_info = self.GetInstanceInfo(name, hvparams=hvparams)
if instance_info is None:
raise errors.HypervisorError("Failed to shutdown instance %s,"
" not running" % name)
if not force:
self._ShutdownInstance(name, hvparams, timeout)
# TODO: Xen does always destroy the instnace after trying a gracefull
# shutdown. That means doing another attempt with force=True will not make
# any difference. This differs in behaviour from other hypervisors and
# should be cleaned up.
result = self._DestroyInstanceIfAlive(name, hvparams)
if result is not None and result.failed and \
self.GetInstanceInfo(name, hvparams=hvparams) is not None:
raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
(name, result.fail_reason, result.output))
# Remove configuration file if stopping/starting instance was successful
self._RemoveConfigFile(name)
def RebootInstance(self, instance):
"""Reboot an instance.
"""
ini_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
if ini_info is None:
raise errors.HypervisorError("Failed to reboot instance %s,"
" not running" % instance.name)
result = self._RunXen(["reboot", instance.name], instance.hvparams)
if result.failed:
raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
(instance.name, result.fail_reason,
result.output))
def _CheckInstance():
new_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
# check if the domain ID has changed or the run time has decreased
if (new_info is not None and
(new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
return
raise utils.RetryAgain()
try:
utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
except utils.RetryTimeout:
raise errors.HypervisorError("Failed to reboot instance %s: instance"
" did not reboot in the expected interval" %
(instance.name, ))
def BalloonInstanceMemory(self, instance, mem):
"""Balloon an instance memory to a certain value.
@type instance: L{objects.Instance}
@param instance: instance to be accepted
@type mem: int
@param mem: actual memory size to use for instance runtime
"""
result = self._RunXen(["mem-set", instance.name, mem], instance.hvparams)
if result.failed:
raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
(instance.name, result.fail_reason,
result.output))
# Update configuration file
cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
cmd.append(self._ConfigFileName(instance.name))
result = utils.RunCmd(cmd)
if result.failed:
raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
(instance.name, result.fail_reason,
result.output))
def GetNodeInfo(self, hvparams=None):
"""Return information about the node.
@see: L{_GetNodeInfo} and L{_ParseNodeInfo}
"""
result = self._RunXen(["info"], hvparams)
if result.failed:
logging.error("Can't retrieve xen hypervisor information (%s): %s",
result.fail_reason, result.output)
return None
instance_list = self._GetInstanceList(True, hvparams)
return _GetNodeInfo(result.stdout, instance_list)
@classmethod
def GetInstanceConsole(cls, instance, primary_node, node_group,
hvparams, beparams):
"""Return a command for connecting to the console of an instance.
"""
xen_cmd = XenHypervisor._GetCommandFromHvparams(hvparams)
ndparams = node_group.FillND(primary_node)
return objects.InstanceConsole(instance=instance.name,
kind=constants.CONS_SSH,
host=primary_node.name,
port=ndparams.get(constants.ND_SSH_PORT),
user=constants.SSH_CONSOLE_USER,
command=[pathutils.XEN_CONSOLE_WRAPPER,
xen_cmd, instance.name])
def Verify(self, hvparams=None):
"""Verify the hypervisor.
For Xen, this verifies that the xend process is running.
@type hvparams: dict of strings
@param hvparams: hypervisor parameters to be verified against
@return: Problem description if something is wrong, C{None} otherwise
"""
if hvparams is None:
return "Could not verify the hypervisor, because no hvparams were" \
" provided."
if constants.HV_XEN_CMD in hvparams:
xen_cmd = hvparams[constants.HV_XEN_CMD]
try:
self._CheckToolstack(xen_cmd)
except errors.HypervisorError:
return "The configured xen toolstack '%s' is not available on this" \
" node." % xen_cmd
result = self._RunXen(["info"], hvparams)
if result.failed:
return "Retrieving information from xen failed: %s, %s" % \
(result.fail_reason, result.output)
return None
def MigrationInfo(self, instance):
"""Get instance information to perform a migration.
@type instance: L{objects.Instance}
@param instance: instance to be migrated
@rtype: string
@return: content of the xen config file
"""
return self._ReadConfigFile(instance.name)
def _UseMigrationDaemon(self, hvparams):
"""Whether to start a socat daemon when accepting an instance.
@rtype: bool
"""
return self._GetCommand(hvparams) == constants.XEN_CMD_XL
@classmethod
def _KillMigrationDaemon(cls, instance):
"""Kills the migration daemon if present.
"""
pidfile = cls._InstanceMigrationPidfile(instance.name)
read_pid = utils.ReadPidFile(pidfile)
# There is no pidfile, hence nothing for us to do
if read_pid == 0:
return
if utils.IsProcessAlive(read_pid):
# If the process is alive, let's make sure we are killing the right one
cmdline = ' '.join(utils.GetProcCmdline(read_pid))
if cmdline.count("xl migrate-receive") > 0:
utils.KillProcess(read_pid)
# By this point the process is not running, whether killed or initially
# nonexistent, so it is safe to remove the pidfile.
utils.RemoveFile(pidfile)
def AcceptInstance(self, instance, info, target):
"""Prepare to accept an instance.
@type instance: L{objects.Instance}
@param instance: instance to be accepted
@type info: string
@param info: content of the xen config file on the source node
@type target: string
@param target: target host (usually ip), on this node
"""
if self._UseMigrationDaemon(instance.hvparams):
port = instance.hvparams[constants.HV_MIGRATION_PORT]
# Make sure there is somewhere to put the pidfile.
XenHypervisor._EnsureDirs()
pidfile = XenHypervisor._InstanceMigrationPidfile(instance.name)
# And try and kill a previous daemon
XenHypervisor._KillMigrationDaemon(instance)
listening_arg = "TCP-LISTEN:%d,bind=%s,reuseaddr" % (port, target)
socat_pid = utils.StartDaemon(["socat", "-b524288", listening_arg,
"SYSTEM:'xl migrate-receive'"],
pidfile=pidfile)
# Wait for a while to make sure the socat process has successfully started
# listening
time.sleep(1)
if not utils.IsProcessAlive(socat_pid):
raise errors.HypervisorError("Could not start receiving socat process"
" on port %d: check if port is available" %
port)
def FinalizeMigrationDst(self, instance, info, success):
"""Finalize an instance migration.
After a successful migration we write the xen config file.
We do nothing on a failure, as we did not change anything at accept time.
@type instance: L{objects.Instance}
@param instance: instance whose migration is being finalized
@type info: string
@param info: content of the xen config file on the source node
@type success: boolean
@param success: whether the migration was a success or a failure
"""
if success:
self._WriteConfigFile(instance.name, info)
elif self._UseMigrationDaemon(instance.hvparams):
XenHypervisor._KillMigrationDaemon(instance)
def MigrateInstance(self, _cluster_name, instance, target, live):
"""Migrate an instance to a target node.
The migration will not be attempted if the instance is not
currently running.
@type instance: L{objects.Instance}
@param instance: the instance to be migrated
@type target: string
@param target: ip address of the target node
@type live: boolean
@param live: perform a live migration
"""
port = instance.hvparams[constants.HV_MIGRATION_PORT]
return self._MigrateInstance(instance.name, target, port, live,
instance.hvparams)
def _MigrateInstance(self, instance_name, target, port, live, hvparams,
_ping_fn=netutils.TcpPing):
"""Migrate an instance to a target node.
@see: L{MigrateInstance} for details
"""
if hvparams is None:
raise errors.HypervisorError("No hvparams provided.")
if self.GetInstanceInfo(instance_name, hvparams=hvparams) is None:
raise errors.HypervisorError("Instance not running, cannot migrate")
cmd = self._GetCommand(hvparams)
args = ["migrate"]
if cmd == constants.XEN_CMD_XM:
# Try and see if xm is listening on the specified port
if not _ping_fn(target, port, live_port_needed=True):
raise errors.HypervisorError("Remote host %s not listening on port"
" %s, cannot migrate" % (target, port))
args.extend(["-p", "%d" % port])
if live:
args.append("-l")
elif cmd == constants.XEN_CMD_XL:
# Rather than using SSH, use socat as Ganeti cannot guarantee the presence
# of usable SSH keys as of 2.13
args.extend([
"-s", constants.XL_SOCAT_CMD % (target, port),
"-C", self._ConfigFileName(instance_name),
])
else:
raise errors.HypervisorError("Unsupported Xen command: %s" % cmd)
args.extend([instance_name, target])
result = self._RunXen(args, hvparams)
if result.failed:
raise errors.HypervisorError("Failed to migrate instance %s: %s" %
(instance_name, result.output))
def FinalizeMigrationSource(self, instance, success, live):
"""Finalize the instance migration on the source node.
@type instance: L{objects.Instance}
@param instance: the instance that was migrated
@type success: bool
@param success: whether the migration succeeded or not
@type live: bool
@param live: whether the user requested a live migration or not
"""
# pylint: disable=W0613
if success:
# remove old xen file after migration succeeded
try:
self._RemoveConfigFile(instance.name)
except EnvironmentError:
logging.exception("Failure while removing instance config file")
def GetMigrationStatus(self, instance):
"""Get the migration status
As MigrateInstance for Xen is still blocking, if this method is called it
means that MigrateInstance has completed successfully. So we can safely
assume that the migration was successful and notify this fact to the client.
@type instance: L{objects.Instance}
@param instance: the instance that is being migrated
@rtype: L{objects.MigrationStatus}
@return: the status of the current migration (one of
L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
progress info that can be retrieved from the hypervisor
"""
return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
def PowercycleNode(self, hvparams=None):
"""Xen-specific powercycle.
This first does a Linux reboot (which triggers automatically a Xen
reboot), and if that fails it tries to do a Xen reboot. The reason
we don't try a Xen reboot first is that the xen reboot launches an
external command which connects to the Xen hypervisor, and that
won't work in case the root filesystem is broken and/or the xend
daemon is not working.
@type hvparams: dict of strings
@param hvparams: hypervisor params to be used on this node
"""
try:
self.LinuxPowercycle()
finally:
xen_cmd = self._GetCommand(hvparams)
utils.RunCmd([xen_cmd, "debug", "R"])
def _CheckToolstack(self, xen_cmd):
"""Check whether the given toolstack is available on the node.
@type xen_cmd: string
@param xen_cmd: xen command (e.g. 'xm' or 'xl')
"""
binary_found = self._CheckToolstackBinary(xen_cmd)
if not binary_found:
raise errors.HypervisorError("No '%s' binary found on node." % xen_cmd)
elif xen_cmd == constants.XEN_CMD_XL:
if not self._CheckToolstackXlConfigured():
raise errors.HypervisorError("Toolstack '%s' is not enabled on this"
"node." % xen_cmd)
def _CheckToolstackBinary(self, xen_cmd):
"""Checks whether the xen command's binary is found on the machine.
"""
if xen_cmd not in constants.KNOWN_XEN_COMMANDS:
raise errors.HypervisorError("Unknown xen command '%s'." % xen_cmd)
result = self._run_cmd_fn(["which", xen_cmd])
return not result.failed
def _CheckToolstackXlConfigured(self):
"""Checks whether xl is enabled on an xl-capable node.
@rtype: bool
@returns: C{True} if 'xl' is enabled, C{False} otherwise
"""
result = self._run_cmd_fn([constants.XEN_CMD_XL, "help"])
if not result.failed:
return True
elif result.failed:
if "toolstack" in result.stderr:
return False
# xl fails for some other reason than the toolstack
else:
raise errors.HypervisorError("Cannot run xen ('%s'). Error: %s."
% (constants.XEN_CMD_XL, result.stderr))
def WriteXenConfigEvents(config, hvp):
config.write("on_poweroff = 'preserve'\n")
if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
config.write("on_reboot = 'restart'\n")
else:
config.write("on_reboot = 'destroy'\n")
config.write("on_crash = 'restart'\n")
class XenPvmHypervisor(XenHypervisor):
"""Xen PVM hypervisor interface"""
PARAMETERS = {
constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
constants.HV_ROOT_PATH: hv_base.NO_CHECK,
constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
# TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
constants.HV_REBOOT_BEHAVIOR:
hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
constants.HV_CPU_WEIGHT:
(False, lambda x: 0 < x < 65536, "invalid weight", None, None),
constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
constants.HV_XEN_CMD:
hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
constants.HV_XEN_CPUID: hv_base.NO_CHECK,
constants.HV_SOUNDHW: hv_base.NO_CHECK,
}
def _GetConfig(self, instance, startup_memory, block_devices):
"""Write the Xen config file for the instance.
"""
hvp = instance.hvparams
config = StringIO()
config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
# if bootloader is True, use bootloader instead of kernel and ramdisk
# parameters.
if hvp[constants.HV_USE_BOOTLOADER]:
# bootloader handling
bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
if bootloader_path:
config.write("bootloader = '%s'\n" % bootloader_path)
else:
raise errors.HypervisorError("Bootloader enabled, but missing"
" bootloader path")
bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
if bootloader_args:
config.write("bootargs = '%s'\n" % bootloader_args)
else:
# kernel handling
kpath = hvp[constants.HV_KERNEL_PATH]
config.write("kernel = '%s'\n" % kpath)
# initrd handling
initrd_path = hvp[constants.HV_INITRD_PATH]
if initrd_path:
config.write("ramdisk = '%s'\n" % initrd_path)
# rest of the settings
config.write("memory = %d\n" % startup_memory)
config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
if cpu_pinning:
config.write("%s\n" % cpu_pinning)
cpu_cap = hvp[constants.HV_CPU_CAP]
if cpu_cap:
config.write("cpu_cap=%d\n" % cpu_cap)
cpu_weight = hvp[constants.HV_CPU_WEIGHT]
if cpu_weight:
config.write("cpu_weight=%d\n" % cpu_weight)
config.write("name = '%s'\n" % instance.name)
self._WriteNicConfig(config, instance, hvp)
disk_data = \
_GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
config.write("disk = [%s]\n" % ",".join(disk_data))
if hvp[constants.HV_ROOT_PATH]:
config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
WriteXenConfigEvents(config, hvp)
config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
cpuid = hvp[constants.HV_XEN_CPUID]
if cpuid:
config.write("cpuid = %s\n" % _QuoteCpuidField(cpuid))
if hvp[constants.HV_SOUNDHW]:
config.write("soundhw = '%s'\n" % hvp[constants.HV_SOUNDHW])
return config.getvalue()
class XenHvmHypervisor(XenHypervisor):
"""Xen HVM hypervisor interface"""
ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
pathutils.VNC_PASSWORD_FILE,
]
ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
pathutils.VNC_PASSWORD_FILE,
]
PARAMETERS = {
constants.HV_ACPI: hv_base.NO_CHECK,
constants.HV_BOOT_ORDER: (True, ) +
(lambda x: x and len(x.strip("acdn")) == 0,
"Invalid boot order specified, must be one or more of [acdn]",
None, None),
constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
constants.HV_DISK_TYPE:
hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
constants.HV_NIC_TYPE:
hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
constants.HV_PAE: hv_base.NO_CHECK,
constants.HV_VNC_BIND_ADDRESS:
(False, netutils.IP4Address.IsValid,
"VNC bind address is not a valid IP address", None, None),
constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
# TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
# Add PCI passthrough
constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
constants.HV_REBOOT_BEHAVIOR:
hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
constants.HV_CPU_CAP: hv_base.NO_CHECK,
constants.HV_CPU_WEIGHT:
(False, lambda x: 0 < x < 65535, "invalid weight", None, None),
constants.HV_VIF_TYPE:
hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
constants.HV_VIRIDIAN: hv_base.NO_CHECK,
constants.HV_XEN_CMD:
hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
constants.HV_XEN_CPUID: hv_base.NO_CHECK,
constants.HV_SOUNDHW: hv_base.NO_CHECK,
}
def _GetConfig(self, instance, startup_memory, block_devices):
"""Create a Xen 3.1 HVM config file.
"""
hvp = instance.hvparams
config = StringIO()
# kernel handling
kpath = hvp[constants.HV_KERNEL_PATH]
config.write("kernel = '%s'\n" % kpath)
config.write("builder = 'hvm'\n")
config.write("memory = %d\n" % startup_memory)
config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
if cpu_pinning:
config.write("%s\n" % cpu_pinning)
cpu_cap = hvp[constants.HV_CPU_CAP]
if cpu_cap:
config.write("cpu_cap=%d\n" % cpu_cap)
cpu_weight = hvp[constants.HV_CPU_WEIGHT]
if cpu_weight:
config.write("cpu_weight=%d\n" % cpu_weight)
config.write("name = '%s'\n" % instance.name)
if hvp[constants.HV_PAE]:
config.write("pae = 1\n")
else:
config.write("pae = 0\n")
if hvp[constants.HV_ACPI]:
config.write("acpi = 1\n")
else:
config.write("acpi = 0\n")
if hvp[constants.HV_VIRIDIAN]:
config.write("viridian = 1\n")
else:
config.write("viridian = 0\n")
config.write("apic = 1\n")
config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
config.write("sdl = 0\n")
config.write("usb = 1\n")
config.write("usbdevice = 'tablet'\n")
config.write("vnc = 1\n")
if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
else:
config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
if instance.network_port > constants.VNC_BASE_PORT:
display = instance.network_port - constants.VNC_BASE_PORT
config.write("vncdisplay = %s\n" % display)
config.write("vncunused = 0\n")
else:
config.write("# vncdisplay = 1\n")
config.write("vncunused = 1\n")
vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
try:
password = utils.ReadFile(vnc_pwd_file)
except EnvironmentError, err:
raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
(vnc_pwd_file, err))
config.write("vncpasswd = '%s'\n" % password.rstrip())
config.write("serial = 'pty'\n")
if hvp[constants.HV_USE_LOCALTIME]:
config.write("localtime = 1\n")
self._WriteNicConfig(config, instance, hvp)
disk_data = \
_GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
if iso_path:
iso = "'file:%s,hdc:cdrom,r'" % iso_path
disk_data.append(iso)
config.write("disk = [%s]\n" % (",".join(disk_data)))
# Add PCI passthrough
pci_pass_arr = []
pci_pass = hvp[constants.HV_PASSTHROUGH]
if pci_pass:
pci_pass_arr = pci_pass.split(";")
config.write("pci = %s\n" % pci_pass_arr)
WriteXenConfigEvents(config, hvp)
cpuid = hvp[constants.HV_XEN_CPUID]
if cpuid:
config.write("cpuid = %s\n" % _QuoteCpuidField(cpuid))
if hvp[constants.HV_SOUNDHW]:
config.write("soundhw = '%s'\n" % hvp[constants.HV_SOUNDHW])
return config.getvalue()