iallocator: Add node whitelist
authorMichael Hanselmann <hansmi@google.com>
Fri, 23 Nov 2012 01:56:04 +0000 (02:56 +0100)
committerMichael Hanselmann <hansmi@google.com>
Tue, 4 Dec 2012 17:54:37 +0000 (18:54 +0100)
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 <hansmi@google.com>
Reviewed-by: Helga Velroyen <helgav@google.com>

lib/masterd/iallocator.py
test/ganeti.masterd.iallocator_unittest.py

index 2db8ac4..45b0ba1 100644 (file)
@@ -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,
index 8aa2bd0..85b88a9 100755 (executable)
@@ -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()