diff --git a/lib/cmdlib.py b/lib/cmdlib.py index c21eafce7da11c29b5f6df06308e3ddeffe507e3..89a328306fa842605b3f22e41efd5d438c228c8c 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -30,6 +30,7 @@ import time import tempfile import re import platform +import simplejson from ganeti import rpc from ganeti import ssh @@ -44,6 +45,14 @@ from ganeti import opcodes from ganeti import ssconf +# Check whether the simplejson module supports indentation +_JSON_INDENT = 2 +try: + simplejson.dumps(1, indent=_JSON_INDENT) +except TypeError: + _JSON_INDENT = None + + class LogicalUnit(object): """Logical Unit base class. @@ -4639,3 +4648,194 @@ class LUTestDelay(NoHooksLU): if not node_result: raise errors.OpExecError("Failure during rpc call to node %s," " result: %s" % (node, node_result)) + + +def _AllocatorGetClusterData(cfg, sstore): + """Compute the generic allocator input data. + + This is the data that is independent of the actual operation. + + """ + # 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, + } + instance_data[iname] = pir + + data["instances"] = instance_data + + return data + + +def _AllocatorAddNewInstance(data, op): + """Add new instance data to allocator structure. + + 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. + + """ + 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, + "nics": op.nics, + } + data["request"] = request + + +def _AllocatorAddRelocateInstance(data, op): + """Add relocate instance data to allocator structure. + + 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. + + """ + request = { + "type": "replace_secondary", + "name": op.name, + } + data["request"] = request + + +class LUTestAllocator(NoHooksLU): + """Run allocator tests. + + This LU runs the allocator tests + + """ + _OP_REQP = ["direction", "mode", "name"] + + def CheckPrereq(self): + """Check prerequisites. + + This checks the opcode parameters depending on the director and mode test. + + """ + if self.op.mode == constants.ALF_MODE_ALLOC: + for attr in ["name", "mem_size", "disks", "disk_template", + "os", "tags", "nics", "vcpus"]: + if not hasattr(self.op, attr): + raise errors.OpPrereqError("Missing attribute '%s' on opcode input" % + attr) + iname = self.cfg.ExpandInstanceName(self.op.name) + if iname is not None: + raise errors.OpPrereqError("Instance '%s' already in the cluster" % + iname) + if not isinstance(self.op.nics, list): + raise errors.OpPrereqError("Invalid parameter 'nics'") + for row in self.op.nics: + if (not isinstance(row, dict) or + "mac" not in row or + "ip" not in row or + "bridge" not in row): + raise errors.OpPrereqError("Invalid contents of the" + " 'nics' parameter") + if not isinstance(self.op.disks, list): + raise errors.OpPrereqError("Invalid parameter 'disks'") + for row in self.op.disks: + if (not isinstance(row, dict) or + "size" not in row or + not isinstance(row["size"], int) or + "mode" not in row or + row["mode"] not in ['r', 'w']): + raise errors.OpPrereqError("Invalid contents of the" + " 'disks' parameter") + elif self.op.mode == constants.ALF_MODE_RELOC: + if not hasattr(self.op, "name"): + raise errors.OpPrereqError("Missing attribute 'name' on opcode input") + fname = self.cfg.ExpandInstanceName(self.op.name) + if fname is None: + raise errors.OpPrereqError("Instance '%s' not found for relocation" % + self.op.name) + self.op.name = fname + else: + 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"): + raise errors.OpPrereqError("Missing allocator name") + raise errors.OpPrereqError("Allocator out mode not supported yet") + elif self.op.direction != constants.ALF_DIR_IN: + raise errors.OpPrereqError("Wrong allocator test '%s'" % + self.op.direction) + + def Exec(self, feedback_fn): + """Run the allocator test. + + """ + data = _AllocatorGetClusterData(self.cfg, self.sstore) + if self.op.mode == constants.ALF_MODE_ALLOC: + _AllocatorAddNewInstance(data, self.op) + else: + _AllocatorAddRelocateInstance(data, self.op) + + if _JSON_INDENT is None: + text = simplejson.dumps(data) + else: + text = simplejson.dumps(data, indent=_JSON_INDENT) + return text diff --git a/lib/constants.py b/lib/constants.py index 1879ffd2e6016876ec92a96eeaf4cd889ca75a6c..7a6e7e709a48d1ab42d1d41ca3b8146d878c47c4 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -184,3 +184,8 @@ VNC_PASSWORD_FILE = _autoconf.SYSCONFDIR + "/ganeti/vnc-cluster-password" 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" diff --git a/lib/mcpu.py b/lib/mcpu.py index 06a1a8e091d9eb1c2a790c5f007b84a579ad61e5..99cd737f026a48336073b2cd065010979eb8e9e3 100644 --- a/lib/mcpu.py +++ b/lib/mcpu.py @@ -87,6 +87,7 @@ class Processor(object): opcodes.OpDelTags: cmdlib.LUDelTags, # test lu opcodes.OpTestDelay: cmdlib.LUTestDelay, + opcodes.OpTestAllocator: cmdlib.LUTestAllocator, } def __init__(self, feedback=None): diff --git a/lib/opcodes.py b/lib/opcodes.py index 7e2f780c382084a3e572bd9bb48df5089cc313a3..a80bb166c254f56926aad8dc7e45759c6d679119 100644 --- a/lib/opcodes.py +++ b/lib/opcodes.py @@ -444,3 +444,22 @@ class OpTestDelay(OpCode): """ OP_ID = "OP_TEST_DELAY" __slots__ = ["duration", "on_master", "on_nodes"] + + +class OpTestAllocator(OpCode): + """Allocator framework testing. + + This opcode has two modes: + - gather and return allocator input for a given mode (allocate new + or replace secondary) and a given instance definition (direction + 'in') + - run a selected allocator for a given operation (as above) and + return the allocator output (direction 'out') + + """ + OP_ID = "OP_TEST_ALLOCATOR" + __slots__ = [ + "direction", "mode", "allocator", "name", + "mem_size", "disks", "disk_template", + "os", "tags", "nics", "vcpus", + ] diff --git a/scripts/gnt-debug b/scripts/gnt-debug index 20c5da5724c9ea39c21ad9b6039844746f3101c5..f57dd5d805e257b022eccfd158e70d9b6a71ecb5 100644 --- a/scripts/gnt-debug +++ b/scripts/gnt-debug @@ -97,6 +97,47 @@ def GenericOpCodes(opts, args): return 0 +def TestAllocator(opts, args): + """Runs the test allocator opcode""" + + try: + disks = [{"size": utils.ParseUnit(val), "mode": 'w'} + for val in opts.disks.split(",")] + except errors.UnitParseError, err: + print >> sys.stderr, "Invalid disks parameter '%s': %s" % (opts.disks, err) + return 1 + + nics = [val.split("/") for val in opts.nics.split(",")] + for row in nics: + while len(row) < 3: + row.append(None) + for i in range(3): + if row[i] == '': + row[i] = None + nic_dict = [{"mac": v[0], "ip": v[1], "bridge": v[2]} for v in nics] + + if opts.tags is None: + opts.tags = [] + else: + opts.tags = opts.tags.split(",") + + op = opcodes.OpTestAllocator(mode=opts.mode, + name=args[0], + mem_size=opts.mem, + disks=disks, + disk_template=opts.disk_template, + nics=nic_dict, + os=opts.os_type, + vcpus=opts.vcpus, + tags=opts.tags, + direction=opts.direction, + allocator=opts.allocator, + ) + result = SubmitOpCode(op) + print result + return 0 + + commands = { 'delay': (Delay, ARGS_ONE, [DEBUG_OPT, @@ -113,6 +154,37 @@ commands = { ], "<op_list_file>", "Submits a job built from a json-file" " with a list of serialized opcodes"), + 'allocator': (TestAllocator, ARGS_ONE, + [DEBUG_OPT, + make_option("--dir", dest="direction", + default="in", choices=["in", "out"], + help="Show allocator input (in) or allocator" + " results (out)"), + make_option("--algorithm", dest="allocator", + default=None, + help="Allocator algorithm name"), + make_option("-m", "--mode", default="relocate", + choices=["relocate", "allocate"], + help="Request mode, either allocate or" + "relocate"), + cli_option("--mem", default=128, type="unit", + help="Memory size for the instance (MiB)"), + make_option("--disks", default="4096,4096", + help="Comma separated list of disk sizes (MiB)"), + make_option("-t", "--disk-template", default="drbd", + help="Select the disk template"), + make_option("--nics", default="00:11:22:33:44:55", + help="Comma separated list of nics, each nic" + " definition is of form mac/ip/bridge, if" + " missing values are replace by None"), + make_option("-o", "--os-type", default=None, + help="Select os for the instance"), + make_option("-p", "--vcpus", default=1, type="int", + help="Select number of VCPUs for the instance"), + make_option("--tags", default=None, + help="Comma separated list of tags"), + ], + "{opts...} <instance>", "Executes a TestAllocator OpCode"), }