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