From 298fe380f101684b9bf1148fa4f9c2d735aff262 Mon Sep 17 00:00:00 2001 From: Iustin Pop <iustin@google.com> Date: Wed, 16 Apr 2008 13:56:37 +0000 Subject: [PATCH] Implement 'out' direction on allocator tests This patch adds the paths for searching for instance allocators and makes the LUTestAllocator code run the allocator and return the results if the direction specified is 'out'. 'out' means that the opcode will return the result of the allocator run, instead of the allocator input file ('in'). The patch unifies all names to refer to 'iallocator' instead of plain allocator. The patch also adds an example allocator that can be used for testing this new functionality. Reviewed-by: ultrotter --- configure.ac | 11 ++++ doc/examples/dumb-allocator | 100 ++++++++++++++++++++++++++++++++++++ lib/Makefile.am | 1 + lib/cmdlib.py | 65 +++++++++++++++++------ lib/constants.py | 9 ++-- 5 files changed, 167 insertions(+), 19 deletions(-) create mode 100755 doc/examples/dumb-allocator diff --git a/configure.ac b/configure.ac index 6c3fa4ac4..1a0f90c98 100644 --- a/configure.ac +++ b/configure.ac @@ -50,6 +50,17 @@ AC_ARG_WITH([os-search-path], [os_search_path="'/srv/ganeti/os'"]) AC_SUBST(OS_SEARCH_PATH, $os_search_path) +# --with-iallocator-search-path=... +# do a bit of black sed magic to for quoting of the strings in the list +AC_ARG_WITH([iallocator-search-path], + [AS_HELP_STRING([--with-iallocator-search-path=LIST], + [comma separated list of directories to] + [ search for instance allocators (default is $libdir/ganeti/iallocators)] + )], + [iallocator_search_path=`echo -n "$withval" | sed -e "s/\([[^,]]*\)/'\1'/g"`], + [iallocator_search_path="'$libdir/$PACKAGE_NAME/iallocators'"]) +AC_SUBST(IALLOCATOR_SEARCH_PATH, $iallocator_search_path) + # --with-xen-kernel=... AC_ARG_WITH([xen-kernel], [AS_HELP_STRING([--with-xen-kernel=PATH], diff --git a/doc/examples/dumb-allocator b/doc/examples/dumb-allocator new file mode 100755 index 000000000..f6d6560fd --- /dev/null +++ b/doc/examples/dumb-allocator @@ -0,0 +1,100 @@ +#!/usr/bin/python +# + +# Copyright (C) 2008 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + +"""Simple first-fit allocator for ganeti instance allocation framework. + +This allocator just iterates over the nodes and selects the first one +that fits in both memory and disk space, without any consideration for +equal spread or VCPU oversubscription. + +""" +import simplejson +import sys + + +def SelectNode(nodes, request, to_skip): + """Select a node for the given instance + + """ + disk_size = request["disk_space_total"] + selected = None + for nname, ninfo in nodes.iteritems(): + if nname in to_skip: + continue + if request["memory"] > ninfo["free_memory"]: + continue + if disk_size > ninfo["free_disk"]: + continue + selected = nname + break + return selected + + +def OutputError(text): + """Builds an error response with a given info message. + + """ + error = { + "success": False, + "info": text, + } + print simplejson.dumps(error, indent=2) + return 1 + + +def main(): + """Main function. + + """ + if len(sys.argv) < 2: + print >> sys.stderr, "Usage: %s cluster.json" % (sys.argv[0]) + return 1 + + data = simplejson.load(open(sys.argv[1])) + + nodes = data["nodes"] + request = data["request"] + req_type = request["type"] + if req_type != "allocate": + print >> sys.stderr, "Unsupported allocator mode '%s'" % req_type + return 1 + + npri = SelectNode(nodes, request, []) + if npri is None: + return OutputError("Can't find a suitable primary node") + + result_nodes = [npri] + if request["disk_template"] == "drbd": + nsec = SelectNode(nodes, request, result_nodes) + if nsec is None: + return OutputError("Can't find a suitable secondary node (%s selected" + " as primary)" % npri) + result_nodes.append(nsec) + + result = { + "success": True, + "info": "Allocation successful", + "nodes": result_nodes, + } + print simplejson.dumps(result, indent=2) + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/lib/Makefile.am b/lib/Makefile.am index 140ed3aa4..994b2a394 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -25,6 +25,7 @@ _autoconf.py: Makefile echo "XEN_KERNEL = '$(XEN_KERNEL)'"; \ echo "XEN_INITRD = '$(XEN_INITRD)'"; \ echo "FILE_STORAGE_DIR = '$(FILE_STORAGE_DIR)'"; \ + echo "IALLOCATOR_SEARCH_PATH = [$(IALLOCATOR_SEARCH_PATH)]"; \ } > $@ pre-check: all diff --git a/lib/cmdlib.py b/lib/cmdlib.py index 89a328306..c8816653c 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -4650,7 +4650,7 @@ class LUTestDelay(NoHooksLU): " result: %s" % (node, node_result)) -def _AllocatorGetClusterData(cfg, sstore): +def _IAllocatorGetClusterData(cfg, sstore): """Compute the generic allocator input data. This is the data that is independent of the actual operation. @@ -4720,7 +4720,7 @@ def _AllocatorGetClusterData(cfg, sstore): return data -def _AllocatorAddNewInstance(data, op): +def _IAllocatorAddNewInstance(data, op): """Add new instance data to allocator structure. This in combination with _AllocatorGetClusterData will create the @@ -4730,6 +4730,12 @@ def _AllocatorAddNewInstance(data, op): done. """ + if len(op.disks) != 2: + raise errors.OpExecError("Only two-disk configurations supported") + + disk_space = _ComputeDiskSize(op.disk_template, + op.disks[0]["size"], op.disks[1]["size"]) + request = { "type": "allocate", "name": op.name, @@ -4739,15 +4745,16 @@ def _AllocatorAddNewInstance(data, op): "vcpus": op.vcpus, "memory": op.mem_size, "disks": op.disks, + "disk_space_total": disk_space, "nics": op.nics, } data["request"] = request -def _AllocatorAddRelocateInstance(data, op): +def _IAllocatorAddRelocateInstance(data, op): """Add relocate instance data to allocator structure. - This in combination with _AllocatorGetClusterData will create the + 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 @@ -4761,6 +4768,29 @@ def _AllocatorAddRelocateInstance(data, op): data["request"] = request +def _IAllocatorRun(name, data): + """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") + + 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 + + class LUTestAllocator(NoHooksLU): """Run allocator tests. @@ -4775,7 +4805,7 @@ class LUTestAllocator(NoHooksLU): This checks the opcode parameters depending on the director and mode test. """ - if self.op.mode == constants.ALF_MODE_ALLOC: + if self.op.mode == constants.IALLOCATOR_MODE_ALLOC: for attr in ["name", "mem_size", "disks", "disk_template", "os", "tags", "nics", "vcpus"]: if not hasattr(self.op, attr): @@ -4796,6 +4826,8 @@ class LUTestAllocator(NoHooksLU): " 'nics' parameter") if not isinstance(self.op.disks, list): raise errors.OpPrereqError("Invalid parameter 'disks'") + if len(self.op.disks) != 2: + raise errors.OpPrereqError("Only two-disk configurations supported") for row in self.op.disks: if (not isinstance(row, dict) or "size" not in row or @@ -4804,7 +4836,7 @@ class LUTestAllocator(NoHooksLU): row["mode"] not in ['r', 'w']): raise errors.OpPrereqError("Invalid contents of the" " 'disks' parameter") - elif self.op.mode == constants.ALF_MODE_RELOC: + elif self.op.mode == constants.IALLOCATOR_MODE_RELOC: if not hasattr(self.op, "name"): raise errors.OpPrereqError("Missing attribute 'name' on opcode input") fname = self.cfg.ExpandInstanceName(self.op.name) @@ -4816,11 +4848,10 @@ class LUTestAllocator(NoHooksLU): raise errors.OpPrereqError("Invalid test allocator mode '%s'" % self.op.mode) - if self.op.direction == constants.ALF_DIR_OUT: - if not hasattr(self.op, "allocator"): + if self.op.direction == constants.IALLOCATOR_DIR_OUT: + if not hasattr(self.op, "allocator") or self.op.allocator is None: raise errors.OpPrereqError("Missing allocator name") - raise errors.OpPrereqError("Allocator out mode not supported yet") - elif self.op.direction != constants.ALF_DIR_IN: + elif self.op.direction != constants.IALLOCATOR_DIR_IN: raise errors.OpPrereqError("Wrong allocator test '%s'" % self.op.direction) @@ -4828,14 +4859,18 @@ class LUTestAllocator(NoHooksLU): """Run the allocator test. """ - data = _AllocatorGetClusterData(self.cfg, self.sstore) - if self.op.mode == constants.ALF_MODE_ALLOC: - _AllocatorAddNewInstance(data, self.op) + data = _IAllocatorGetClusterData(self.cfg, self.sstore) + if self.op.mode == constants.IALLOCATOR_MODE_ALLOC: + _IAllocatorAddNewInstance(data, self.op) else: - _AllocatorAddRelocateInstance(data, self.op) + _IAllocatorAddRelocateInstance(data, self.op) if _JSON_INDENT is None: text = simplejson.dumps(data) else: text = simplejson.dumps(data, indent=_JSON_INDENT) - return text + if self.op.direction == constants.IALLOCATOR_DIR_IN: + result = text + else: + result = _IAllocatorRun(self.op.allocator, text) + return result diff --git a/lib/constants.py b/lib/constants.py index 7a6e7e709..91fcf81aa 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -185,7 +185,8 @@ VERIFY_NPLUSONE_MEM = 'nplusone_mem' VERIFY_OPTIONAL_CHECKS = frozenset([VERIFY_NPLUSONE_MEM]) # Allocator framework constants -ALF_DIR_IN = "in" -ALF_DIR_OUT = "out" -ALF_MODE_ALLOC = "allocate" -ALF_MODE_RELOC = "relocate" +IALLOCATOR_DIR_IN = "in" +IALLOCATOR_DIR_OUT = "out" +IALLOCATOR_MODE_ALLOC = "allocate" +IALLOCATOR_MODE_RELOC = "relocate" +IALLOCATOR_SEARCH_PATH = _autoconf.IALLOCATOR_SEARCH_PATH -- GitLab