Revision 7ee7c0c7
b/lib/locking.py | ||
---|---|---|
26 | 26 |
# Wouldn't it be better to define LockingError in the locking module? |
27 | 27 |
# Well, for now that's how the rest of the code does it... |
28 | 28 |
from ganeti import errors |
29 |
from ganeti import utils |
|
29 | 30 |
|
30 | 31 |
|
31 | 32 |
class SharedLock: |
... | ... | |
555 | 556 |
|
556 | 557 |
return delete_failed |
557 | 558 |
|
559 |
|
|
560 |
# Locking levels, must be acquired in increasing order. |
|
561 |
# Current rules are: |
|
562 |
# - at level LEVEL_CLUSTER resides the Big Ganeti Lock (BGL) which must be |
|
563 |
# acquired before performing any operation, either in shared or in exclusive |
|
564 |
# mode. acquiring the BGL in exclusive mode is discouraged and should be |
|
565 |
# avoided. |
|
566 |
# - at levels LEVEL_NODE and LEVEL_INSTANCE reside node and instance locks. |
|
567 |
# If you need more than one node, or more than one instance, acquire them at |
|
568 |
# the same time. |
|
569 |
# - level LEVEL_CONFIG contains the configuration lock, which you must acquire |
|
570 |
# before reading or changing the config file. |
|
571 |
LEVEL_CLUSTER = 0 |
|
572 |
LEVEL_NODE = 1 |
|
573 |
LEVEL_INSTANCE = 2 |
|
574 |
LEVEL_CONFIG = 3 |
|
575 |
|
|
576 |
LEVELS = [LEVEL_CLUSTER, |
|
577 |
LEVEL_NODE, |
|
578 |
LEVEL_INSTANCE, |
|
579 |
LEVEL_CONFIG] |
|
580 |
|
|
581 |
# Lock levels which are modifiable |
|
582 |
LEVELS_MOD = [LEVEL_NODE, LEVEL_INSTANCE] |
|
583 |
|
|
584 |
# Constant for the big ganeti lock and config lock |
|
585 |
BGL = 'BGL' |
|
586 |
CONFIG = 'config' |
|
587 |
|
|
588 |
|
|
589 |
class GanetiLockManager: |
|
590 |
"""The Ganeti Locking Library |
|
591 |
|
|
592 |
The purpouse of this small library is to manage locking for ganeti clusters |
|
593 |
in a central place, while at the same time doing dynamic checks against |
|
594 |
possible deadlocks. It will also make it easier to transition to a different |
|
595 |
lock type should we migrate away from python threads. |
|
596 |
|
|
597 |
""" |
|
598 |
_instance = None |
|
599 |
|
|
600 |
def __init__(self, nodes=None, instances=None): |
|
601 |
"""Constructs a new GanetiLockManager object. |
|
602 |
|
|
603 |
There should be only a |
|
604 |
GanetiLockManager object at any time, so this function raises an error if this |
|
605 |
is not the case. |
|
606 |
|
|
607 |
Args: |
|
608 |
nodes: list of node names |
|
609 |
instances: list of instance names |
|
610 |
|
|
611 |
""" |
|
612 |
assert self.__class__._instance is None, "double GanetiLockManager instance" |
|
613 |
self.__class__._instance = self |
|
614 |
|
|
615 |
# The keyring contains all the locks, at their level and in the correct |
|
616 |
# locking order. |
|
617 |
self.__keyring = { |
|
618 |
LEVEL_CLUSTER: LockSet([BGL]), |
|
619 |
LEVEL_NODE: LockSet(nodes), |
|
620 |
LEVEL_INSTANCE: LockSet(instances), |
|
621 |
LEVEL_CONFIG: LockSet([CONFIG]), |
|
622 |
} |
|
623 |
|
|
624 |
def _names(self, level): |
|
625 |
"""List the lock names at the given level. |
|
626 |
Used for debugging/testing purposes. |
|
627 |
|
|
628 |
Args: |
|
629 |
level: the level whose list of locks to get |
|
630 |
|
|
631 |
""" |
|
632 |
assert level in LEVELS, "Invalid locking level %s" % level |
|
633 |
return self.__keyring[level]._names() |
|
634 |
|
|
635 |
def _is_owned(self, level): |
|
636 |
"""Check whether we are owning locks at the given level |
|
637 |
|
|
638 |
""" |
|
639 |
return self.__keyring[level]._is_owned() |
|
640 |
|
|
641 |
def _list_owned(self, level): |
|
642 |
"""Get the set of owned locks at the given level |
|
643 |
|
|
644 |
""" |
|
645 |
return self.__keyring[level]._list_owned() |
|
646 |
|
|
647 |
def _upper_owned(self, level): |
|
648 |
"""Check that we don't own any lock at a level greater than the given one. |
|
649 |
|
|
650 |
""" |
|
651 |
# This way of checking only works if LEVELS[i] = i, which we check for in |
|
652 |
# the test cases. |
|
653 |
return utils.any((self._is_owned(l) for l in LEVELS[level + 1:])) |
|
654 |
|
|
655 |
def _BGL_owned(self): |
|
656 |
"""Check if the current thread owns the BGL. |
|
657 |
|
|
658 |
Both an exclusive or a shared acquisition work. |
|
659 |
|
|
660 |
""" |
|
661 |
return BGL in self.__keyring[LEVEL_CLUSTER]._list_owned() |
|
662 |
|
|
663 |
def _contains_BGL(self, level, names): |
|
664 |
"""Check if acting on the given level and set of names will change the |
|
665 |
status of the Big Ganeti Lock. |
|
666 |
|
|
667 |
""" |
|
668 |
return level == LEVEL_CLUSTER and (names is None or BGL in names) |
|
669 |
|
|
670 |
def acquire(self, level, names, blocking=1, shared=0): |
|
671 |
"""Acquire a set of resource locks, at the same level. |
|
672 |
|
|
673 |
Args: |
|
674 |
level: the level at which the locks shall be acquired. |
|
675 |
It must be a memmber of LEVELS. |
|
676 |
names: the names of the locks which shall be acquired. |
|
677 |
(special lock names, or instance/node names) |
|
678 |
shared: whether to acquire in shared mode. By default an exclusive lock |
|
679 |
will be acquired. |
|
680 |
blocking: whether to block while trying to acquire or to operate in try-lock mode. |
|
681 |
this locking mode is not supported yet. |
|
682 |
|
|
683 |
""" |
|
684 |
assert level in LEVELS, "Invalid locking level %s" % level |
|
685 |
|
|
686 |
# Check that we are either acquiring the Big Ganeti Lock or we already own |
|
687 |
# it. Some "legacy" opcodes need to be sure they are run non-concurrently |
|
688 |
# so even if we've migrated we need to at least share the BGL to be |
|
689 |
# compatible with them. Of course if we own the BGL exclusively there's no |
|
690 |
# point in acquiring any other lock, unless perhaps we are half way through |
|
691 |
# the migration of the current opcode. |
|
692 |
assert (self._contains_BGL(level, names) or self._BGL_owned()), ( |
|
693 |
"You must own the Big Ganeti Lock before acquiring any other") |
|
694 |
|
|
695 |
# Check we don't own locks at the same or upper levels. |
|
696 |
assert not self._upper_owned(level), ("Cannot acquire locks at a level" |
|
697 |
" while owning some at a greater one") |
|
698 |
|
|
699 |
# Acquire the locks in the set. |
|
700 |
return self.__keyring[level].acquire(names, shared=shared, |
|
701 |
blocking=blocking) |
|
702 |
|
|
703 |
def release(self, level, names=None): |
|
704 |
"""Release a set of resource locks, at the same level. |
|
705 |
|
|
706 |
You must have acquired the locks, either in shared or in exclusive mode, |
|
707 |
before releasing them. |
|
708 |
|
|
709 |
Args: |
|
710 |
level: the level at which the locks shall be released. |
|
711 |
It must be a memmber of LEVELS. |
|
712 |
names: the names of the locks which shall be released. |
|
713 |
(defaults to all the locks acquired at that level). |
|
714 |
|
|
715 |
""" |
|
716 |
assert level in LEVELS, "Invalid locking level %s" % level |
|
717 |
assert (not self._contains_BGL(level, names) or |
|
718 |
not self._upper_owned(LEVEL_CLUSTER)), ( |
|
719 |
"Cannot release the Big Ganeti Lock while holding something" |
|
720 |
" at upper levels") |
|
721 |
|
|
722 |
# Release will complain if we don't own the locks already |
|
723 |
return self.__keyring[level].release(names) |
|
724 |
|
|
725 |
def add(self, level, names, acquired=0, shared=0): |
|
726 |
"""Add locks at the specified level. |
|
727 |
|
|
728 |
Args: |
|
729 |
level: the level at which the locks shall be added. |
|
730 |
It must be a memmber of LEVELS_MOD. |
|
731 |
names: names of the locks to acquire |
|
732 |
acquired: whether to acquire the newly added locks |
|
733 |
shared: whether the acquisition will be shared |
|
734 |
""" |
|
735 |
assert level in LEVELS_MOD, "Invalid or immutable level %s" % level |
|
736 |
assert self._BGL_owned(), ("You must own the BGL before performing other" |
|
737 |
" operations") |
|
738 |
assert not self._upper_owned(level), ("Cannot add locks at a level" |
|
739 |
" while owning some at a greater one") |
|
740 |
return self.__keyring[level].add(names, acquired=acquired, shared=shared) |
|
741 |
|
|
742 |
def remove(self, level, names, blocking=1): |
|
743 |
"""Remove locks from the specified level. |
|
744 |
|
|
745 |
You must either already own the locks you are trying to remove exclusively |
|
746 |
or not own any lock at an upper level. |
|
747 |
|
|
748 |
Args: |
|
749 |
level: the level at which the locks shall be removed. |
|
750 |
It must be a memmber of LEVELS_MOD. |
|
751 |
names: the names of the locks which shall be removed. |
|
752 |
(special lock names, or instance/node names) |
|
753 |
blocking: whether to block while trying to operate in try-lock mode. |
|
754 |
this locking mode is not supported yet. |
|
755 |
|
|
756 |
""" |
|
757 |
assert level in LEVELS_MOD, "Invalid or immutable level %s" % level |
|
758 |
assert self._BGL_owned(), ("You must own the BGL before performing other" |
|
759 |
" operations") |
|
760 |
# Check we either own the level or don't own anything from here up. |
|
761 |
# LockSet.remove() will check the case in which we don't own all the needed |
|
762 |
# resources, or we have a shared ownership. |
|
763 |
assert self._is_owned(level) or not self._upper_owned(level), ( |
|
764 |
"Cannot remove locks at a level while not owning it or" |
|
765 |
" owning some at a greater one") |
|
766 |
return self.__keyring[level].remove(names, blocking) |
|
767 |
|
b/test/ganeti.locking_unittest.py | ||
---|---|---|
412 | 412 |
self.ls.release() |
413 | 413 |
|
414 | 414 |
|
415 |
class TestGanetiLockManager(unittest.TestCase): |
|
416 |
|
|
417 |
def setUp(self): |
|
418 |
self.nodes=['n1', 'n2'] |
|
419 |
self.instances=['i1', 'i2', 'i3'] |
|
420 |
self.GL = locking.GanetiLockManager(nodes=self.nodes, |
|
421 |
instances=self.instances) |
|
422 |
self.done = Queue.Queue(0) |
|
423 |
|
|
424 |
def tearDown(self): |
|
425 |
# Don't try this at home... |
|
426 |
locking.GanetiLockManager._instance = None |
|
427 |
|
|
428 |
def testLockingConstants(self): |
|
429 |
# The locking library internally cheats by assuming its constants have some |
|
430 |
# relationships with each other. Check those hold true. |
|
431 |
for i in range(len(locking.LEVELS)): |
|
432 |
self.assertEqual(i, locking.LEVELS[i]) |
|
433 |
|
|
434 |
def testDoubleGLFails(self): |
|
435 |
# We are not passing test=True, so instantiating a new one should fail |
|
436 |
self.assertRaises(AssertionError, locking.GanetiLockManager) |
|
437 |
|
|
438 |
def testLockNames(self): |
|
439 |
self.assertEqual(self.GL._names(locking.LEVEL_CLUSTER), set(['BGL'])) |
|
440 |
self.assertEqual(self.GL._names(locking.LEVEL_NODE), set(self.nodes)) |
|
441 |
self.assertEqual(self.GL._names(locking.LEVEL_INSTANCE), set(self.instances)) |
|
442 |
self.assertEqual(self.GL._names(locking.LEVEL_CONFIG), set(['config'])) |
|
443 |
|
|
444 |
def testInitAndResources(self): |
|
445 |
locking.GanetiLockManager._instance = None |
|
446 |
self.GL = locking.GanetiLockManager() |
|
447 |
self.assertEqual(self.GL._names(locking.LEVEL_CLUSTER), set(['BGL'])) |
|
448 |
self.assertEqual(self.GL._names(locking.LEVEL_NODE), set()) |
|
449 |
self.assertEqual(self.GL._names(locking.LEVEL_INSTANCE), set()) |
|
450 |
self.assertEqual(self.GL._names(locking.LEVEL_CONFIG), set(['config'])) |
|
451 |
|
|
452 |
locking.GanetiLockManager._instance = None |
|
453 |
self.GL = locking.GanetiLockManager(nodes=self.nodes) |
|
454 |
self.assertEqual(self.GL._names(locking.LEVEL_CLUSTER), set(['BGL'])) |
|
455 |
self.assertEqual(self.GL._names(locking.LEVEL_NODE), set(self.nodes)) |
|
456 |
self.assertEqual(self.GL._names(locking.LEVEL_INSTANCE), set()) |
|
457 |
self.assertEqual(self.GL._names(locking.LEVEL_CONFIG), set(['config'])) |
|
458 |
|
|
459 |
locking.GanetiLockManager._instance = None |
|
460 |
self.GL = locking.GanetiLockManager(instances=self.instances) |
|
461 |
self.assertEqual(self.GL._names(locking.LEVEL_CLUSTER), set(['BGL'])) |
|
462 |
self.assertEqual(self.GL._names(locking.LEVEL_NODE), set()) |
|
463 |
self.assertEqual(self.GL._names(locking.LEVEL_INSTANCE), set(self.instances)) |
|
464 |
self.assertEqual(self.GL._names(locking.LEVEL_CONFIG), set(['config'])) |
|
465 |
|
|
466 |
def testAcquireRelease(self): |
|
467 |
self.GL.acquire(locking.LEVEL_CLUSTER, ['BGL'], shared=1) |
|
468 |
self.assertEquals(self.GL._list_owned(locking.LEVEL_CLUSTER), set(['BGL'])) |
|
469 |
self.GL.acquire(locking.LEVEL_NODE, ['n1', 'n2'], shared=1) |
|
470 |
self.GL.release(locking.LEVEL_NODE) |
|
471 |
self.GL.acquire(locking.LEVEL_NODE, ['n1']) |
|
472 |
self.assertEquals(self.GL._list_owned(locking.LEVEL_NODE), set(['n1'])) |
|
473 |
self.GL.acquire(locking.LEVEL_INSTANCE, ['i1', 'i2']) |
|
474 |
self.GL.acquire(locking.LEVEL_CONFIG, ['config']) |
|
475 |
self.GL.release(locking.LEVEL_INSTANCE, ['i2']) |
|
476 |
self.assertEquals(self.GL._list_owned(locking.LEVEL_INSTANCE), set(['i1'])) |
|
477 |
self.GL.release(locking.LEVEL_NODE) |
|
478 |
self.GL.release(locking.LEVEL_INSTANCE) |
|
479 |
self.GL.release(locking.LEVEL_CONFIG) |
|
480 |
self.assertRaises(errors.LockError, self.GL.acquire, |
|
481 |
locking.LEVEL_INSTANCE, ['i5']) |
|
482 |
self.GL.acquire(locking.LEVEL_INSTANCE, ['i3'], shared=1) |
|
483 |
self.assertEquals(self.GL._list_owned(locking.LEVEL_INSTANCE), set(['i3'])) |
|
484 |
|
|
485 |
def testBGLDependency(self): |
|
486 |
self.assertRaises(AssertionError, self.GL.acquire, |
|
487 |
locking.LEVEL_NODE, ['n1', 'n2']) |
|
488 |
self.assertRaises(AssertionError, self.GL.acquire, |
|
489 |
locking.LEVEL_INSTANCE, ['i3']) |
|
490 |
self.GL.acquire(locking.LEVEL_CLUSTER, ['BGL'], shared=1) |
|
491 |
self.GL.acquire(locking.LEVEL_NODE, ['n1']) |
|
492 |
self.assertRaises(AssertionError, self.GL.release, |
|
493 |
locking.LEVEL_CLUSTER, ['BGL']) |
|
494 |
self.assertRaises(AssertionError, self.GL.release, |
|
495 |
locking.LEVEL_CLUSTER) |
|
496 |
self.GL.release(locking.LEVEL_NODE) |
|
497 |
self.GL.acquire(locking.LEVEL_INSTANCE, ['i1', 'i2']) |
|
498 |
self.assertRaises(AssertionError, self.GL.release, |
|
499 |
locking.LEVEL_CLUSTER, ['BGL']) |
|
500 |
self.assertRaises(AssertionError, self.GL.release, |
|
501 |
locking.LEVEL_CLUSTER) |
|
502 |
self.GL.release(locking.LEVEL_INSTANCE) |
|
503 |
self.GL.acquire(locking.LEVEL_CONFIG, ['config']) |
|
504 |
self.assertRaises(AssertionError, self.GL.release, |
|
505 |
locking.LEVEL_CLUSTER) |
|
506 |
|
|
507 |
def testWrongOrder(self): |
|
508 |
self.GL.acquire(locking.LEVEL_CLUSTER, ['BGL'], shared=1) |
|
509 |
self.GL.acquire(locking.LEVEL_INSTANCE, ['i3']) |
|
510 |
self.assertRaises(AssertionError, self.GL.acquire, |
|
511 |
locking.LEVEL_NODE, ['n1']) |
|
512 |
self.assertRaises(AssertionError, self.GL.acquire, |
|
513 |
locking.LEVEL_INSTANCE, ['i2']) |
|
514 |
self.GL.acquire(locking.LEVEL_CONFIG, ['config']) |
|
515 |
self.assertRaises(AssertionError, self.GL.acquire, |
|
516 |
locking.LEVEL_CONFIG, ['config']) |
|
517 |
self.GL.release(locking.LEVEL_INSTANCE) |
|
518 |
self.assertRaises(AssertionError, self.GL.acquire, |
|
519 |
locking.LEVEL_NODE, ['n1']) |
|
520 |
self.assertRaises(AssertionError, self.GL.acquire, |
|
521 |
locking.LEVEL_INSTANCE, ['i2']) |
|
522 |
self.assertRaises(AssertionError, self.GL.acquire, |
|
523 |
locking.LEVEL_CONFIG, ['config']) |
|
524 |
|
|
525 |
# Helper function to run as a thread that shared the BGL and then acquires |
|
526 |
# some locks at another level. |
|
527 |
def _doLock(self, level, names, shared): |
|
528 |
try: |
|
529 |
self.GL.acquire(locking.LEVEL_CLUSTER, ['BGL'], shared=1) |
|
530 |
self.GL.acquire(level, names, shared=shared) |
|
531 |
self.done.put('DONE') |
|
532 |
self.GL.release(level) |
|
533 |
self.GL.release(locking.LEVEL_CLUSTER) |
|
534 |
except errors.LockError: |
|
535 |
self.done.put('ERR') |
|
536 |
|
|
537 |
def testConcurrency(self): |
|
538 |
self.GL.acquire(locking.LEVEL_CLUSTER, ['BGL'], shared=1) |
|
539 |
Thread(target=self._doLock, args=(locking.LEVEL_INSTANCE, 'i1', 1)).start() |
|
540 |
self.assertEqual(self.done.get(True, 1), 'DONE') |
|
541 |
self.GL.acquire(locking.LEVEL_NODE, ['n1']) |
|
542 |
self.GL.acquire(locking.LEVEL_INSTANCE, ['i3']) |
|
543 |
self.GL.acquire(locking.LEVEL_CONFIG, ['config']) |
|
544 |
Thread(target=self._doLock, args=(locking.LEVEL_INSTANCE, 'i1', 1)).start() |
|
545 |
self.assertEqual(self.done.get(True, 1), 'DONE') |
|
546 |
Thread(target=self._doLock, args=(locking.LEVEL_INSTANCE, 'i3', 1)).start() |
|
547 |
self.assertRaises(Queue.Empty, self.done.get, True, 0.2) |
|
548 |
self.GL.release(locking.LEVEL_CONFIG) |
|
549 |
self.GL.release(locking.LEVEL_INSTANCE) |
|
550 |
self.assertEqual(self.done.get(True, 1), 'DONE') |
|
551 |
self.GL.acquire(locking.LEVEL_INSTANCE, ['i2'], shared=1) |
|
552 |
Thread(target=self._doLock, args=(locking.LEVEL_INSTANCE, 'i2', 1)).start() |
|
553 |
self.assertEqual(self.done.get(True, 1), 'DONE') |
|
554 |
Thread(target=self._doLock, args=(locking.LEVEL_INSTANCE, 'i2', 0)).start() |
|
555 |
self.assertRaises(Queue.Empty, self.done.get, True, 0.2) |
|
556 |
self.GL.release(locking.LEVEL_INSTANCE) |
|
557 |
self.assertEqual(self.done.get(True, 1), 'DONE') |
|
558 |
|
|
559 |
|
|
415 | 560 |
if __name__ == '__main__': |
416 | 561 |
unittest.main() |
417 | 562 |
#suite = unittest.TestLoader().loadTestsFromTestCase(TestSharedLock) |
Also available in: Unified diff