diff --git a/configure.ac b/configure.ac index 6c3fa4ac4749398d2ce5fc0e16bfb38a3eb48724..1a0f90c9873721b9e38712c0ae28f37cedc6f0d9 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 0000000000000000000000000000000000000000..f6d6560fd440b66fa3a981b79e513efacb95f1c8 --- /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 140ed3aa486f47db75fab8070d041494c728fea9..994b2a3940a9a5f3445ef275ef98cd221791f199 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 89a328306fa842605b3f22e41efd5d438c228c8c..c8816653cb58fa2b4a991eb972e270cbc20f4828 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 7a6e7e709a48d1ab42d1d41ca3b8146d878c47c4..91fcf81aaf155c56ea12eed958c947b555bdb130 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