Commit d1c2dd75 authored by Iustin Pop's avatar Iustin Pop
Browse files

Move all iallocator functions into a class

This patch moves all the iallocator function into a separate class that
is then somewhat easier to use. It doesn't bring any new functionality.

The patch also changes the way the iallocator is called - the
OpTestAllocator opcode is no longer needed, and all its parameters
should be passed directly to the IAllocator constructor.

Reviewed-by: ultrotter
parent a424ce50
......@@ -3138,47 +3138,42 @@ class LUCreateInstance(LogicalUnit):
"""Run the allocator based on input opcode.
"""
al_data = _IAllocatorGetClusterData(self.cfg, self.sstore)
disks = [{"size": self.op.disk_size, "mode": "w"},
{"size": self.op.swap_size, "mode": "w"}]
nics = [{"mac": self.op.mac, "ip": getattr(self.op, "ip", None),
"bridge": self.op.bridge}]
op = opcodes.OpTestAllocator(name=self.op.instance_name,
disk_template=self.op.disk_template,
tags=[],
os=self.op.os_type,
vcpus=self.op.vcpus,
mem_size=self.op.mem_size,
disks=disks,
nics=nics)
_IAllocatorAddNewInstance(al_data, op)
text = serializer.Dump(al_data)
result = _IAllocatorRun(self.op.iallocator, text)
result = _IAllocatorValidateResult(result)
if not result["success"]:
ial = IAllocator(self.cfg, self.sstore,
name=self.op.instance_name,
disk_template=self.op.disk_template,
tags=[],
os=self.op.os_type,
vcpus=self.op.vcpus,
mem_size=self.op.mem_size,
disks=disks,
nics=nics,
mode=constants.IALLOCATOR_MODE_ALLOC)
ial.Run(self.op.iallocator)
if not ial.success:
raise errors.OpPrereqError("Can't compute nodes using"
" iallocator '%s': %s" % (self.op.iallocator,
result["info"]))
ial.info))
req_nodes = 1
if self.op.disk_template in constants.DTS_NET_MIRROR:
req_nodes += 1
if len(result["nodes"]) != req_nodes:
if len(ial.nodes) != req_nodes:
raise errors.OpPrereqError("iallocator '%s' returned invalid number"
" of nodes (%s), required %s" %
(len(result["nodes"]), req_nodes))
self.op.pnode = result["nodes"][0]
(len(ial.nodes), req_nodes))
self.op.pnode = ial.nodes[0]
logger.ToStdout("Selected nodes for the instance: %s" %
(", ".join(result["nodes"]),))
(", ".join(ial.nodes),))
logger.Info("Selected nodes for instance %s via iallocator %s: %s" %
(self.op.instance_name, self.op.iallocator, result["nodes"]))
(self.op.instance_name, self.op.iallocator, ial.nodes))
if req_nodes == 2:
self.op.snode = result["nodes"][1]
self.op.snode = ial.nodes[1]
def BuildHooksEnv(self):
"""Build hooks env.
......@@ -4719,168 +4714,229 @@ class LUTestDelay(NoHooksLU):
" result: %s" % (node, node_result))
def _IAllocatorGetClusterData(cfg, sstore):
"""Compute the generic allocator input data.
class IAllocator(object):
"""IAllocator framework.
This is the data that is independent of the actual operation.
An IAllocator instance has three sets of attributes:
- cfg/sstore that are needed to query the cluster
- input data (all members of the _KEYS class attribute are required)
- four buffer attributes (in|out_data|text), that represent the
input (to the external script) in text and data structure format,
and the output from it, again in two formats
- the result variables from the script (success, info, nodes) for
easy usage
"""
# cluster data
data = {
"version": 1,
"cluster_name": sstore.GetClusterName(),
"cluster_tags": list(cfg.GetClusterInfo().GetTags()),
# we don't have job IDs
}
# node data
node_results = {}
node_list = cfg.GetNodeList()
node_data = rpc.call_node_info(node_list, cfg.GetVGName())
for nname in node_list:
ninfo = cfg.GetNodeInfo(nname)
if nname not in node_data or not isinstance(node_data[nname], dict):
raise errors.OpExecError("Can't get data for node %s" % nname)
remote_info = node_data[nname]
for attr in ['memory_total', 'memory_free',
'vg_size', 'vg_free']:
if attr not in remote_info:
raise errors.OpExecError("Node '%s' didn't return attribute '%s'" %
(nname, attr))
try:
int(remote_info[attr])
except ValueError, err:
raise errors.OpExecError("Node '%s' returned invalid value for '%s':"
" %s" % (nname, attr, str(err)))
pnr = {
"tags": list(ninfo.GetTags()),
"total_memory": utils.TryConvert(int, remote_info['memory_total']),
"free_memory": utils.TryConvert(int, remote_info['memory_free']),
"total_disk": utils.TryConvert(int, remote_info['vg_size']),
"free_disk": utils.TryConvert(int, remote_info['vg_free']),
"primary_ip": ninfo.primary_ip,
"secondary_ip": ninfo.secondary_ip,
}
node_results[nname] = pnr
data["nodes"] = node_results
# instance data
instance_data = {}
i_list = cfg.GetInstanceList()
for iname in i_list:
iinfo = cfg.GetInstanceInfo(iname)
nic_data = [{"mac": n.mac, "ip": n.ip, "bridge": n.bridge}
for n in iinfo.nics]
pir = {
"tags": list(iinfo.GetTags()),
"should_run": iinfo.status == "up",
"vcpus": iinfo.vcpus,
"memory": iinfo.memory,
"os": iinfo.os,
"nodes": [iinfo.primary_node] + list(iinfo.secondary_nodes),
"nics": nic_data,
"disks": [{"size": dsk.size, "mode": "w"} for dsk in iinfo.disks],
"disk_template": iinfo.disk_template,
_KEYS = [
"mode", "name",
"mem_size", "disks", "disk_template",
"os", "tags", "nics", "vcpus",
]
def __init__(self, cfg, sstore, **kwargs):
self.cfg = cfg
self.sstore = sstore
# init buffer variables
self.in_text = self.out_text = self.in_data = self.out_data = None
# init all input fields so that pylint is happy
self.mode = self.name = None
self.mem_size = self.disks = self.disk_template = None
self.os = self.tags = self.nics = self.vcpus = None
# init result fields
self.success = self.info = self.nodes = None
for key in kwargs:
if key not in self._KEYS:
raise errors.ProgrammerError("Invalid input parameter '%s' to"
" IAllocator" % key)
setattr(self, key, kwargs[key])
for key in self._KEYS:
if key not in kwargs:
raise errors.ProgrammerError("Missing input parameter '%s' to"
" IAllocator" % key)
self._BuildInputData()
def _ComputeClusterData(self):
"""Compute the generic allocator input data.
This is the data that is independent of the actual operation.
"""
cfg = self.cfg
# cluster data
data = {
"version": 1,
"cluster_name": self.sstore.GetClusterName(),
"cluster_tags": list(cfg.GetClusterInfo().GetTags()),
# we don't have job IDs
}
instance_data[iname] = pir
data["instances"] = instance_data
# node data
node_results = {}
node_list = cfg.GetNodeList()
node_data = rpc.call_node_info(node_list, cfg.GetVGName())
for nname in node_list:
ninfo = cfg.GetNodeInfo(nname)
if nname not in node_data or not isinstance(node_data[nname], dict):
raise errors.OpExecError("Can't get data for node %s" % nname)
remote_info = node_data[nname]
for attr in ['memory_total', 'memory_free',
'vg_size', 'vg_free']:
if attr not in remote_info:
raise errors.OpExecError("Node '%s' didn't return attribute '%s'" %
(nname, attr))
try:
int(remote_info[attr])
except ValueError, err:
raise errors.OpExecError("Node '%s' returned invalid value for '%s':"
" %s" % (nname, attr, str(err)))
pnr = {
"tags": list(ninfo.GetTags()),
"total_memory": utils.TryConvert(int, remote_info['memory_total']),
"free_memory": utils.TryConvert(int, remote_info['memory_free']),
"total_disk": utils.TryConvert(int, remote_info['vg_size']),
"free_disk": utils.TryConvert(int, remote_info['vg_free']),
"primary_ip": ninfo.primary_ip,
"secondary_ip": ninfo.secondary_ip,
}
node_results[nname] = pnr
data["nodes"] = node_results
# instance data
instance_data = {}
i_list = cfg.GetInstanceList()
for iname in i_list:
iinfo = cfg.GetInstanceInfo(iname)
nic_data = [{"mac": n.mac, "ip": n.ip, "bridge": n.bridge}
for n in iinfo.nics]
pir = {
"tags": list(iinfo.GetTags()),
"should_run": iinfo.status == "up",
"vcpus": iinfo.vcpus,
"memory": iinfo.memory,
"os": iinfo.os,
"nodes": [iinfo.primary_node] + list(iinfo.secondary_nodes),
"nics": nic_data,
"disks": [{"size": dsk.size, "mode": "w"} for dsk in iinfo.disks],
"disk_template": iinfo.disk_template,
}
instance_data[iname] = pir
return data
data["instances"] = instance_data
self.in_data = data
def _IAllocatorAddNewInstance(data, op):
"""Add new instance data to allocator structure.
def _AddNewInstance(self):
"""Add new instance data to allocator structure.
This in combination with _AllocatorGetClusterData will create the
correct structure needed as input for the allocator.
This in combination with _AllocatorGetClusterData will create the
correct structure needed as input for the allocator.
The checks for the completeness of the opcode must have already been
done.
The checks for the completeness of the opcode must have already been
done.
"""
if len(op.disks) != 2:
raise errors.OpExecError("Only two-disk configurations supported")
"""
data = self.in_data
if len(self.disks) != 2:
raise errors.OpExecError("Only two-disk configurations supported")
disk_space = _ComputeDiskSize(self.disk_template,
self.disks[0]["size"], self.disks[1]["size"])
request = {
"type": "allocate",
"name": self.name,
"disk_template": self.disk_template,
"tags": self.tags,
"os": self.os,
"vcpus": self.vcpus,
"memory": self.mem_size,
"disks": self.disks,
"disk_space_total": disk_space,
"nics": self.nics,
}
data["request"] = request
disk_space = _ComputeDiskSize(op.disk_template,
op.disks[0]["size"], op.disks[1]["size"])
def _AddRelocateInstance(self):
"""Add relocate instance data to allocator structure.
request = {
"type": "allocate",
"name": op.name,
"disk_template": op.disk_template,
"tags": op.tags,
"os": op.os,
"vcpus": op.vcpus,
"memory": op.mem_size,
"disks": op.disks,
"disk_space_total": disk_space,
"nics": op.nics,
}
data["request"] = request
This in combination with _IAllocatorGetClusterData will create the
correct structure needed as input for the allocator.
The checks for the completeness of the opcode must have already been
done.
def _IAllocatorAddRelocateInstance(data, op):
"""Add relocate instance data to allocator structure.
"""
data = self.in_data
request = {
"type": "replace_secondary",
"name": self.name,
}
data["request"] = request
This in combination with _IAllocatorGetClusterData will create the
correct structure needed as input for the allocator.
def _BuildInputData(self):
"""Build input data structures.
The checks for the completeness of the opcode must have already been
done.
"""
self._ComputeClusterData()
"""
request = {
"type": "replace_secondary",
"name": op.name,
}
data["request"] = request
if self.mode == constants.IALLOCATOR_MODE_ALLOC:
self._AddNewInstance()
else:
self._AddRelocateInstance()
self.in_text = serializer.Dump(self.in_data)
def _IAllocatorRun(name, data):
"""Run an instance allocator and return the results.
def Run(self, name, validate=True):
"""Run an instance allocator and return the results.
"""
alloc_script = utils.FindFile(name, constants.IALLOCATOR_SEARCH_PATH,
os.path.isfile)
if alloc_script is None:
raise errors.OpExecError("Can't find allocator '%s'" % name)
"""
data = self.in_text
fd, fin_name = tempfile.mkstemp(prefix="ganeti-iallocator.")
try:
os.write(fd, data)
os.close(fd)
result = utils.RunCmd([alloc_script, fin_name])
if result.failed:
raise errors.OpExecError("Instance allocator call failed: %s,"
" output: %s" %
(result.fail_reason, result.stdout))
finally:
os.unlink(fin_name)
return result.stdout
alloc_script = utils.FindFile(name, constants.IALLOCATOR_SEARCH_PATH,
os.path.isfile)
if alloc_script is None:
raise errors.OpExecError("Can't find allocator '%s'" % name)
fd, fin_name = tempfile.mkstemp(prefix="ganeti-iallocator.")
try:
os.write(fd, data)
os.close(fd)
result = utils.RunCmd([alloc_script, fin_name])
if result.failed:
raise errors.OpExecError("Instance allocator call failed: %s,"
" output: %s" %
(result.fail_reason, result.stdout))
finally:
os.unlink(fin_name)
self.out_text = result.stdout
if validate:
self._ValidateResult()
def _IAllocatorValidateResult(data):
"""Process the allocator results.
def _ValidateResult(self):
"""Process the allocator results.
"""
try:
rdict = serializer.Load(data)
except Exception, err:
raise errors.OpExecError("Can't parse iallocator results: %s" % str(err))
This will process and if successful save the result in
self.out_data and the other parameters.
if not isinstance(rdict, dict):
raise errors.OpExecError("Can't parse iallocator results: not a dict")
"""
try:
rdict = serializer.Load(self.out_text)
except Exception, err:
raise errors.OpExecError("Can't parse iallocator results: %s" % str(err))
if not isinstance(rdict, dict):
raise errors.OpExecError("Can't parse iallocator results: not a dict")
for key in "success", "info", "nodes":
if key not in rdict:
raise errors.OpExecError("Can't parse iallocator results:"
" missing key '%s'" % key)
for key in "success", "info", "nodes":
if key not in rdict:
raise errors.OpExecError("Can't parse iallocator results:"
" missing key '%s'" % key)
setattr(self, key, rdict[key])
if not isinstance(rdict["nodes"], list):
raise errors.OpExecError("Can't parse iallocator results: 'nodes' key"
" is not a list")
return rdict
if not isinstance(rdict["nodes"], list):
raise errors.OpExecError("Can't parse iallocator results: 'nodes' key"
" is not a list")
self.out_data = rdict
class LUTestAllocator(NoHooksLU):
......@@ -4951,15 +5007,21 @@ class LUTestAllocator(NoHooksLU):
"""Run the allocator test.
"""
data = _IAllocatorGetClusterData(self.cfg, self.sstore)
if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
_IAllocatorAddNewInstance(data, self.op)
else:
_IAllocatorAddRelocateInstance(data, self.op)
ial = IAllocator(self.cfg, self.sstore,
mode=self.op.mode,
name=self.op.name,
mem_size=self.op.mem_size,
disks=self.op.disks,
disk_template=self.op.disk_template,
os=self.op.os,
tags=self.op.tags,
nics=self.op.nics,
vcpus=self.op.vcpus,
)
text = serializer.Dump(data)
if self.op.direction == constants.IALLOCATOR_DIR_IN:
result = text
result = ial.in_text
else:
result = _IAllocatorRun(self.op.allocator, text)
ial.Run(self.op.allocator, validate=False)
result = ial.out_text
return result
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment