From fb60bc6a1bbf06a350a680f430f9ec833f1e3628 Mon Sep 17 00:00:00 2001 From: Michael Hanselmann Date: Fri, 23 Nov 2012 02:56:04 +0100 Subject: [PATCH] iallocator: Add node whitelist MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit In the future instance creations might have a lock on all nodes as was the case until the implementation of opportunistic locking. Nodes for which the lock is not held will be shown to the iallocator plugin as if they were marked offline. This patch adds a new parameter named “node_whitelist” to “IAReqInstanceAlloc”. If set to a list, only nodes contained within are shown as online. Signed-off-by: Michael Hanselmann Reviewed-by: Helga Velroyen --- lib/masterd/iallocator.py | 12 +++- test/ganeti.masterd.iallocator_unittest.py | 90 ++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/lib/masterd/iallocator.py b/lib/masterd/iallocator.py index 2db8ac4..45b0ba1 100644 --- a/lib/masterd/iallocator.py +++ b/lib/masterd/iallocator.py @@ -153,6 +153,7 @@ class IAReqInstanceAlloc(IARequestBase): ("nics", ht.TListOf(ht.TDict)), ("vcpus", ht.TInt), ("hypervisor", ht.TString), + ("node_whitelist", ht.TMaybeListOf(ht.TNonEmptyString)), ] REQ_RESULT = ht.TList @@ -421,10 +422,13 @@ class IAllocator(object): if isinstance(self.req, IAReqInstanceAlloc): hypervisor_name = self.req.hypervisor + node_whitelist = self.req.node_whitelist elif isinstance(self.req, IAReqRelocate): hypervisor_name = cfg.GetInstanceInfo(self.req.name).hypervisor + node_whitelist = None else: hypervisor_name = cluster_info.primary_hypervisor + node_whitelist = None node_data = self.rpc.call_node_info(node_list, [cfg.GetVGName()], [hypervisor_name]) @@ -434,7 +438,7 @@ class IAllocator(object): data["nodegroups"] = self._ComputeNodeGroupData(cfg) - config_ndata = self._ComputeBasicNodeData(cfg, ninfo) + config_ndata = self._ComputeBasicNodeData(cfg, ninfo, node_whitelist) data["nodes"] = self._ComputeDynamicNodeData(ninfo, node_data, node_iinfo, i_list, config_ndata) assert len(data["nodes"]) == len(ninfo), \ @@ -461,7 +465,7 @@ class IAllocator(object): return ng @staticmethod - def _ComputeBasicNodeData(cfg, node_cfg): + def _ComputeBasicNodeData(cfg, node_cfg, node_whitelist): """Compute global node data. @rtype: dict @@ -473,7 +477,9 @@ class IAllocator(object): "tags": list(ninfo.GetTags()), "primary_ip": ninfo.primary_ip, "secondary_ip": ninfo.secondary_ip, - "offline": ninfo.offline, + "offline": (ninfo.offline or + not (node_whitelist is None or + ninfo.name in node_whitelist)), "drained": ninfo.drained, "master_candidate": ninfo.master_candidate, "group": ninfo.group, diff --git a/test/ganeti.masterd.iallocator_unittest.py b/test/ganeti.masterd.iallocator_unittest.py index 8aa2bd0..85b88a9 100755 --- a/test/ganeti.masterd.iallocator_unittest.py +++ b/test/ganeti.masterd.iallocator_unittest.py @@ -26,6 +26,7 @@ import unittest from ganeti import compat from ganeti import constants from ganeti import errors +from ganeti import objects from ganeti import ht from ganeti.masterd import iallocator @@ -88,5 +89,94 @@ class TestIARequestBase(unittest.TestCase): stub.ValidateResult(_StubIAllocator(False), "foo") +class _FakeConfigWithNdParams: + def GetNdParams(self, _): + return None + + +class TestComputeBasicNodeData(unittest.TestCase): + def setUp(self): + self.fn = compat.partial(iallocator.IAllocator._ComputeBasicNodeData, + _FakeConfigWithNdParams()) + + def testEmpty(self): + self.assertEqual(self.fn({}, None), {}) + + def testSimple(self): + node1 = objects.Node(name="node1", + primary_ip="192.0.2.1", + secondary_ip="192.0.2.2", + offline=False, + drained=False, + master_candidate=True, + master_capable=True, + group="11112222", + vm_capable=False) + + node2 = objects.Node(name="node2", + primary_ip="192.0.2.3", + secondary_ip="192.0.2.4", + offline=True, + drained=False, + master_candidate=False, + master_capable=False, + group="11112222", + vm_capable=True) + + assert node1 != node2 + + ninfo = { + "#unused-1#": node1, + "#unused-2#": node2, + } + + self.assertEqual(self.fn(ninfo, None), { + "node1": { + "tags": [], + "primary_ip": "192.0.2.1", + "secondary_ip": "192.0.2.2", + "offline": False, + "drained": False, + "master_candidate": True, + "group": "11112222", + "master_capable": True, + "vm_capable": False, + "ndparams": None, + }, + "node2": { + "tags": [], + "primary_ip": "192.0.2.3", + "secondary_ip": "192.0.2.4", + "offline": True, + "drained": False, + "master_candidate": False, + "group": "11112222", + "master_capable": False, + "vm_capable": True, + "ndparams": None, + }, + }) + + def testOfflineNode(self): + for whitelist in [None, [], set(), ["node1"], ["node2"]]: + result = self.fn({ + "node1": objects.Node(name="node1", offline=True) + }, whitelist) + self.assertEqual(len(result), 1) + self.assertTrue(result["node1"]["offline"]) + + def testWhitelist(self): + for whitelist in [None, [], set(), ["node1"], ["node2"]]: + result = self.fn({ + "node1": objects.Node(name="node1", offline=False) + }, whitelist) + self.assertEqual(len(result), 1) + + if whitelist is None or "node1" in whitelist: + self.assertFalse(result["node1"]["offline"]) + else: + self.assertTrue(result["node1"]["offline"]) + + if __name__ == "__main__": testutils.GanetiTestProgram() -- 1.7.10.4