diff --git a/lib/locking.py b/lib/locking.py index a1c78f4e51f8b260615d85c76460547110ed053c..e5861c5bf6f28bc8cee05bc42b04f9054df0a56f 100644 --- a/lib/locking.py +++ b/lib/locking.py @@ -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. diff --git a/test/ganeti.locking_unittest.py b/test/ganeti.locking_unittest.py index c0afdb8d7710574e579b96a29eea5f00c80fe3e6..7e5369005e76d3a5466b0abeabc1ba5a79c4e123 100755 --- a/test/ganeti.locking_unittest.py +++ b/test/ganeti.locking_unittest.py @@ -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']))