locking: Allow checking if lock is owned in certain mode
authorMichael Hanselmann <hansmi@google.com>
Tue, 29 Nov 2011 15:36:26 +0000 (16:36 +0100)
committerMichael Hanselmann <hansmi@google.com>
Wed, 30 Nov 2011 10:16:07 +0000 (11:16 +0100)
With this patch the “LockSet” and “GanetiLockManager” classes have a new
function to check if a single or a group of locks (at a certain level)
have been acquired in a specific mode. This will be used for additional
assertions. Until now they could only check if a lock has been acquired,
but not in which mode. One use-case will be updating the node state in
various places, where the node lock must be acquired in exclusive mode.

Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: Iustin Pop <iustin@google.com>

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

index a1c78f4..e5861c5 100644 (file)
@@ -943,9 +943,45 @@ class LockSet:
     return self.__lockdict
 
   def is_owned(self):
-    """Is the current thread a current level owner?"""
+    """Is the current thread a current level owner?
+
+    @note: Use L{check_owned} to check if a specific lock is held
+
+    """
     return threading.currentThread() in self.__owners
 
+  def check_owned(self, names, shared=-1):
+    """Check if locks are owned in a specific mode.
+
+    @type names: sequence or string
+    @param names: Lock names (or a single lock name)
+    @param shared: See L{SharedLock.is_owned}
+    @rtype: bool
+    @note: Use L{is_owned} to check if the current thread holds I{any} lock and
+      L{list_owned} to get the names of all owned locks
+
+    """
+    if isinstance(names, basestring):
+      names = [names]
+
+    # Avoid check if no locks are owned anyway
+    if names and self.is_owned():
+      candidates = []
+
+      # Gather references to all locks (in case they're deleted in the meantime)
+      for lname in names:
+        try:
+          lock = self.__lockdict[lname]
+        except KeyError:
+          raise errors.LockError("Non-existing lock '%s' in set '%s' (it may"
+                                 " have been removed)" % (lname, self.name))
+        else:
+          candidates.append(lock)
+
+      return compat.all(lock.is_owned(shared=shared) for lock in candidates)
+    else:
+      return False
+
   def _add_owned(self, name=None):
     """Note the current thread owns the given lock"""
     if name is None:
@@ -1512,6 +1548,14 @@ class GanetiLockManager:
     """
     return self.__keyring[level].list_owned()
 
+  def check_owned(self, level, names, shared=-1):
+    """Check if locks at a certain level are owned in a specific mode.
+
+    @see: L{LockSet.check_owned}
+
+    """
+    return self.__keyring[level].check_owned(names, shared=shared)
+
   def _upper_owned(self, level):
     """Check that we don't own any lock at a level greater than the given one.
 
index c0afdb8..7e53690 100755 (executable)
@@ -1035,18 +1035,50 @@ class TestLockSet(_ThreadedTestCase):
     newls = locking.LockSet([], "TestLockSet.testResources")
     self.assertEquals(newls._names(), set())
 
+  def testCheckOwnedUnknown(self):
+    self.assertFalse(self.ls.check_owned("certainly-not-owning-this-one"))
+    for shared in [-1, 0, 1, 6378, 24255]:
+      self.assertFalse(self.ls.check_owned("certainly-not-owning-this-one",
+                                           shared=shared))
+
+  def testCheckOwnedUnknownWhileHolding(self):
+    self.assertFalse(self.ls.check_owned([]))
+    self.ls.acquire("one", shared=1)
+    self.assertRaises(errors.LockError, self.ls.check_owned, "nonexist")
+    self.assertTrue(self.ls.check_owned("one", shared=1))
+    self.assertFalse(self.ls.check_owned("one", shared=0))
+    self.assertFalse(self.ls.check_owned(["one", "two"]))
+    self.assertRaises(errors.LockError, self.ls.check_owned,
+                      ["one", "nonexist"])
+    self.assertRaises(errors.LockError, self.ls.check_owned, "")
+    self.ls.release()
+    self.assertFalse(self.ls.check_owned([]))
+    self.assertFalse(self.ls.check_owned("one"))
+
   def testAcquireRelease(self):
+    self.assertFalse(self.ls.check_owned(self.ls._names()))
     self.assert_(self.ls.acquire('one'))
     self.assertEquals(self.ls.list_owned(), set(['one']))
+    self.assertTrue(self.ls.check_owned("one"))
+    self.assertTrue(self.ls.check_owned("one", shared=0))
+    self.assertFalse(self.ls.check_owned("one", shared=1))
     self.ls.release()
     self.assertEquals(self.ls.list_owned(), set())
+    self.assertFalse(self.ls.check_owned(self.ls._names()))
     self.assertEquals(self.ls.acquire(['one']), set(['one']))
     self.assertEquals(self.ls.list_owned(), set(['one']))
     self.ls.release()
     self.assertEquals(self.ls.list_owned(), set())
     self.ls.acquire(['one', 'two', 'three'])
     self.assertEquals(self.ls.list_owned(), set(['one', 'two', 'three']))
+    self.assertTrue(self.ls.check_owned(self.ls._names()))
+    self.assertTrue(self.ls.check_owned(self.ls._names(), shared=0))
+    self.assertFalse(self.ls.check_owned(self.ls._names(), shared=1))
     self.ls.release('one')
+    self.assertFalse(self.ls.check_owned(["one"]))
+    self.assertTrue(self.ls.check_owned(["two", "three"]))
+    self.assertTrue(self.ls.check_owned(["two", "three"], shared=0))
+    self.assertFalse(self.ls.check_owned(["two", "three"], shared=1))
     self.assertEquals(self.ls.list_owned(), set(['two', 'three']))
     self.ls.release(['three'])
     self.assertEquals(self.ls.list_owned(), set(['two']))
@@ -1056,6 +1088,8 @@ class TestLockSet(_ThreadedTestCase):
     self.assertEquals(self.ls.list_owned(), set(['one', 'three']))
     self.ls.release()
     self.assertEquals(self.ls.list_owned(), set())
+    for name in self.ls._names():
+      self.assertFalse(self.ls.check_owned(name))
 
   def testNoDoubleAcquire(self):
     self.ls.acquire('one')
@@ -1504,11 +1538,20 @@ class TestLockSet(_ThreadedTestCase):
 
     self.assertFalse(compat.any(i.is_owned()
                                 for i in self.ls._get_lockdict().values()))
+    self.assertFalse(self.ls.check_owned(self.ls._names()))
+    for name in self.ls._names():
+      self.assertFalse(self.ls.check_owned(name))
 
     self.assertEquals(self.ls.acquire(None, shared=0),
                       set(["one", "two", "three"]))
     self.assertRaises(AssertionError, self.ls.downgrade, "unknown lock")
 
+    self.assertTrue(self.ls.check_owned(self.ls._names(), shared=0))
+    for name in self.ls._names():
+      self.assertTrue(self.ls.check_owned(name))
+      self.assertTrue(self.ls.check_owned(name, shared=0))
+      self.assertFalse(self.ls.check_owned(name, shared=1))
+
     self.assertTrue(self.ls._get_lock().is_owned(shared=0))
     self.assertTrue(compat.all(i.is_owned(shared=0)
                                for i in self.ls._get_lockdict().values()))
@@ -1520,6 +1563,12 @@ class TestLockSet(_ThreadedTestCase):
                                for name, lock in
                                  self.ls._get_lockdict().items()))
 
+    self.assertFalse(self.ls.check_owned("one", shared=0))
+    self.assertTrue(self.ls.check_owned("one", shared=1))
+    self.assertTrue(self.ls.check_owned("two", shared=0))
+    self.assertTrue(self.ls.check_owned("three", shared=0))
+
+    # Downgrade second lock
     self.assertTrue(self.ls.downgrade(names="two"))
     self.assertTrue(self.ls._get_lock().is_owned(shared=0))
     should_share = lambda name: [0, 1][int(name in ("one", "two"))]
@@ -1527,6 +1576,12 @@ class TestLockSet(_ThreadedTestCase):
                                for name, lock in
                                  self.ls._get_lockdict().items()))
 
+    self.assertFalse(self.ls.check_owned("one", shared=0))
+    self.assertTrue(self.ls.check_owned("one", shared=1))
+    self.assertFalse(self.ls.check_owned("two", shared=0))
+    self.assertTrue(self.ls.check_owned("two", shared=1))
+    self.assertTrue(self.ls.check_owned("three", shared=0))
+
     # Downgrading the last exclusive lock to shared must downgrade the
     # lockset-internal lock too
     self.assertTrue(self.ls.downgrade(names="three"))
@@ -1534,6 +1589,10 @@ class TestLockSet(_ThreadedTestCase):
     self.assertTrue(compat.all(i.is_owned(shared=1)
                                for i in self.ls._get_lockdict().values()))
 
+    # Verify owned locks
+    for name in self.ls._names():
+      self.assertTrue(self.ls.check_owned(name, shared=1))
+
     # Downgrading a shared lock must be a no-op
     self.assertTrue(self.ls.downgrade(names=["one", "three"]))
     self.assertTrue(self.ls._get_lock().is_owned(shared=1))
@@ -1657,6 +1716,9 @@ class TestGanetiLockManager(_ThreadedTestCase):
     self.GL.acquire(locking.LEVEL_INSTANCE, ['i1'])
     self.GL.acquire(locking.LEVEL_NODEGROUP, ['g2'])
     self.GL.acquire(locking.LEVEL_NODE, ['n1', 'n2'], shared=1)
+    self.assertTrue(self.GL.check_owned(locking.LEVEL_NODE, ["n1", "n2"],
+                                        shared=1))
+    self.assertFalse(self.GL.check_owned(locking.LEVEL_INSTANCE, ["i1", "i3"]))
     self.GL.release(locking.LEVEL_NODE, ['n2'])
     self.assertEquals(self.GL.list_owned(locking.LEVEL_NODE), set(['n1']))
     self.assertEquals(self.GL.list_owned(locking.LEVEL_NODEGROUP), set(['g2']))