Add new lock level for node allocations
authorMichael Hanselmann <hansmi@google.com>
Thu, 22 Nov 2012 21:41:44 +0000 (22:41 +0100)
committerMichael Hanselmann <hansmi@google.com>
Fri, 23 Nov 2012 16:19:01 +0000 (17:19 +0100)
The new lock is similar to the BGL in the sense that it has its own
level and there is only one. It is called “node allocation lock”.
Logical units will use it to synchronize with instance creations, which
in turn will start using opportunistic locks on nodes.

Additionally, an assertion in GanetiLockManager gained a message.

Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: Guido Trotter <ultrotter@google.com>

lib/locking.py
test/ganeti.locking_unittest.py

index 5a02fd5..e28f2f0 100644 (file)
@@ -1467,26 +1467,33 @@ class LockSet:
     return removed
 
 
-# Locking levels, must be acquired in increasing order.
-# Current rules are:
-#   - at level LEVEL_CLUSTER resides the Big Ganeti Lock (BGL) which must be
-#   acquired before performing any operation, either in shared or in exclusive
-#   mode. acquiring the BGL in exclusive mode is discouraged and should be
-#   avoided.
-#   - at levels LEVEL_NODE and LEVEL_INSTANCE reside node and instance locks.
-#   If you need more than one node, or more than one instance, acquire them at
-#   the same time.
-LEVEL_CLUSTER = 0
-LEVEL_INSTANCE = 1
-LEVEL_NODEGROUP = 2
-LEVEL_NODE = 3
-#: Level for node resources, used for operations with possibly high impact on
-#: the node's disks.
-LEVEL_NODE_RES = 4
-LEVEL_NETWORK = 5
+# Locking levels, must be acquired in increasing order. Current rules are:
+# - At level LEVEL_CLUSTER resides the Big Ganeti Lock (BGL) which must be
+#   acquired before performing any operation, either in shared or exclusive
+#   mode. Acquiring the BGL in exclusive mode is discouraged and should be
+#   avoided..
+# - At levels LEVEL_NODE and LEVEL_INSTANCE reside node and instance locks. If
+#   you need more than one node, or more than one instance, acquire them at the
+#   same time.
+# - LEVEL_NODE_RES is for node resources and should be used by operations with
+#   possibly high impact on the node's disks.
+# - LEVEL_NODE_ALLOC blocks instance allocations for the whole cluster
+#   ("NAL" is the only lock at this level). It should be acquired in shared
+#   mode when an opcode blocks all or a significant amount of a cluster's
+#   locks. Opcodes doing instance allocations should acquire in exclusive mode.
+#   Once the set of acquired locks for an opcode has been reduced to the working
+#   set, the NAL should be released as well to allow allocations to proceed.
+(LEVEL_CLUSTER,
+ LEVEL_NODE_ALLOC,
+ LEVEL_INSTANCE,
+ LEVEL_NODEGROUP,
+ LEVEL_NODE,
+ LEVEL_NODE_RES,
+ LEVEL_NETWORK) = range(0, 7)
 
 LEVELS = [
   LEVEL_CLUSTER,
+  LEVEL_NODE_ALLOC,
   LEVEL_INSTANCE,
   LEVEL_NODEGROUP,
   LEVEL_NODE,
@@ -1511,11 +1518,15 @@ LEVEL_NAMES = {
   LEVEL_NODE: "node",
   LEVEL_NODE_RES: "node-res",
   LEVEL_NETWORK: "network",
+  LEVEL_NODE_ALLOC: "node-alloc",
   }
 
 # Constant for the big ganeti lock
 BGL = "BGL"
 
+#: Node allocation lock
+NAL = "NAL"
+
 
 class GanetiLockManager:
   """The Ganeti Locking Library
@@ -1555,10 +1566,12 @@ class GanetiLockManager:
       LEVEL_NODEGROUP: LockSet(nodegroups, "nodegroup", monitor=self._monitor),
       LEVEL_INSTANCE: LockSet(instances, "instance", monitor=self._monitor),
       LEVEL_NETWORK: LockSet(networks, "network", monitor=self._monitor),
+      LEVEL_NODE_ALLOC: LockSet([NAL], "node-alloc", monitor=self._monitor),
       }
 
     assert compat.all(ls.name == LEVEL_NAMES[level]
-                      for (level, ls) in self.__keyring.items())
+                      for (level, ls) in self.__keyring.items()), \
+      "Keyring name mismatch"
 
   def AddToLockMonitor(self, provider):
     """Registers a new lock with the monitor.
index 45c8f6d..04b46fc 100755 (executable)
@@ -1804,6 +1804,7 @@ class TestGanetiLockManager(_ThreadedTestCase):
 
   def testLockNames(self):
     self.assertEqual(self.GL._names(locking.LEVEL_CLUSTER), set(["BGL"]))
+    self.assertEqual(self.GL._names(locking.LEVEL_NODE_ALLOC), set(["NAL"]))
     self.assertEqual(self.GL._names(locking.LEVEL_NODE), set(self.nodes))
     self.assertEqual(self.GL._names(locking.LEVEL_NODEGROUP),
                      set(self.nodegroups))
@@ -1816,6 +1817,7 @@ class TestGanetiLockManager(_ThreadedTestCase):
     locking.GanetiLockManager._instance = None
     self.GL = locking.GanetiLockManager([], [], [], [])
     self.assertEqual(self.GL._names(locking.LEVEL_CLUSTER), set(["BGL"]))
+    self.assertEqual(self.GL._names(locking.LEVEL_NODE_ALLOC), set(["NAL"]))
     self.assertEqual(self.GL._names(locking.LEVEL_NODE), set())
     self.assertEqual(self.GL._names(locking.LEVEL_NODEGROUP), set())
     self.assertEqual(self.GL._names(locking.LEVEL_INSTANCE), set())
@@ -1824,6 +1826,7 @@ class TestGanetiLockManager(_ThreadedTestCase):
     locking.GanetiLockManager._instance = None
     self.GL = locking.GanetiLockManager(self.nodes, self.nodegroups, [], [])
     self.assertEqual(self.GL._names(locking.LEVEL_CLUSTER), set(["BGL"]))
+    self.assertEqual(self.GL._names(locking.LEVEL_NODE_ALLOC), set(["NAL"]))
     self.assertEqual(self.GL._names(locking.LEVEL_NODE), set(self.nodes))
     self.assertEqual(self.GL._names(locking.LEVEL_NODEGROUP),
                                     set(self.nodegroups))
@@ -1833,6 +1836,7 @@ class TestGanetiLockManager(_ThreadedTestCase):
     locking.GanetiLockManager._instance = None
     self.GL = locking.GanetiLockManager([], [], self.instances, [])
     self.assertEqual(self.GL._names(locking.LEVEL_CLUSTER), set(["BGL"]))
+    self.assertEqual(self.GL._names(locking.LEVEL_NODE_ALLOC), set(["NAL"]))
     self.assertEqual(self.GL._names(locking.LEVEL_NODE), set())
     self.assertEqual(self.GL._names(locking.LEVEL_NODEGROUP), set())
     self.assertEqual(self.GL._names(locking.LEVEL_INSTANCE),
@@ -1841,6 +1845,7 @@ class TestGanetiLockManager(_ThreadedTestCase):
     locking.GanetiLockManager._instance = None
     self.GL = locking.GanetiLockManager([], [], [], self.networks)
     self.assertEqual(self.GL._names(locking.LEVEL_CLUSTER), set(["BGL"]))
+    self.assertEqual(self.GL._names(locking.LEVEL_NODE_ALLOC), set(["NAL"]))
     self.assertEqual(self.GL._names(locking.LEVEL_NODE), set())
     self.assertEqual(self.GL._names(locking.LEVEL_NODEGROUP), set())
     self.assertEqual(self.GL._names(locking.LEVEL_INSTANCE), set())
@@ -1946,6 +1951,8 @@ class TestGanetiLockManager(_ThreadedTestCase):
   def testModifiableLevels(self):
     self.assertRaises(AssertionError, self.GL.add, locking.LEVEL_CLUSTER,
                       ["BGL2"])
+    self.assertRaises(AssertionError, self.GL.add, locking.LEVEL_NODE_ALLOC,
+                      ["NAL2"])
     self.GL.acquire(locking.LEVEL_CLUSTER, ["BGL"])
     self.GL.add(locking.LEVEL_INSTANCE, ["i4"])
     self.GL.remove(locking.LEVEL_INSTANCE, ["i3"])