SharedLock: restrict assertion condition
[ganeti-local] / lib / locking.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21 """Module implementing the Ganeti locking code."""
22
23 # pylint: disable-msg=W0613,W0201
24
25 import threading
26 # Wouldn't it be better to define LockingError in the locking module?
27 # Well, for now that's how the rest of the code does it...
28 from ganeti import errors
29 from ganeti import utils
30
31
32 class SharedLock:
33   """Implements a shared lock.
34
35   Multiple threads can acquire the lock in a shared way, calling
36   acquire_shared().  In order to acquire the lock in an exclusive way threads
37   can call acquire_exclusive().
38
39   The lock prevents starvation but does not guarantee that threads will acquire
40   the shared lock in the order they queued for it, just that they will
41   eventually do so.
42
43   """
44   def __init__(self):
45     """Construct a new SharedLock"""
46     # we have two conditions, c_shr and c_exc, sharing the same lock.
47     self.__lock = threading.Lock()
48     self.__turn_shr = threading.Condition(self.__lock)
49     self.__turn_exc = threading.Condition(self.__lock)
50
51     # current lock holders
52     self.__shr = set()
53     self.__exc = None
54
55     # lock waiters
56     self.__nwait_exc = 0
57     self.__nwait_shr = 0
58     self.__npass_shr = 0
59
60     # is this lock in the deleted state?
61     self.__deleted = False
62
63   def __is_sharer(self):
64     """Is the current thread sharing the lock at this time?"""
65     return threading.currentThread() in self.__shr
66
67   def __is_exclusive(self):
68     """Is the current thread holding the lock exclusively at this time?"""
69     return threading.currentThread() == self.__exc
70
71   def __is_owned(self, shared=-1):
72     """Is the current thread somehow owning the lock at this time?
73
74     This is a private version of the function, which presumes you're holding
75     the internal lock.
76
77     """
78     if shared < 0:
79       return self.__is_sharer() or self.__is_exclusive()
80     elif shared:
81       return self.__is_sharer()
82     else:
83       return self.__is_exclusive()
84
85   def _is_owned(self, shared=-1):
86     """Is the current thread somehow owning the lock at this time?
87
88     Args:
89       shared:
90         < 0: check for any type of ownership (default)
91         0: check for exclusive ownership
92         > 0: check for shared ownership
93
94     """
95     self.__lock.acquire()
96     try:
97       result = self.__is_owned(shared=shared)
98     finally:
99       self.__lock.release()
100
101     return result
102
103   def __wait(self, c):
104     """Wait on the given condition, and raise an exception if the current lock
105     is declared deleted in the meantime.
106
107     Args:
108       c: condition to wait on
109
110     """
111     c.wait()
112     if self.__deleted:
113       raise errors.LockError('deleted lock')
114
115   def __exclusive_acquire(self):
116     """Acquire the lock exclusively.
117
118     This is a private function that presumes you are already holding the
119     internal lock. It's defined separately to avoid code duplication between
120     acquire() and delete()
121
122     """
123     self.__nwait_exc += 1
124     try:
125       # This is to save ourselves from a nasty race condition that could
126       # theoretically make the sharers starve.
127       if self.__nwait_shr > 0 or self.__nwait_exc > 1:
128         self.__wait(self.__turn_exc)
129
130       while len(self.__shr) > 0 or self.__exc is not None:
131         self.__wait(self.__turn_exc)
132
133       self.__exc = threading.currentThread()
134     finally:
135       self.__nwait_exc -= 1
136
137     assert self.__npass_shr == 0, "SharedLock: internal fairness violation"
138
139   def acquire(self, blocking=1, shared=0):
140     """Acquire a shared lock.
141
142     Args:
143       shared: whether to acquire in shared mode. By default an exclusive lock
144               will be acquired.
145       blocking: whether to block while trying to acquire or to operate in
146                 try-lock mode. this locking mode is not supported yet.
147
148     """
149     if not blocking:
150       # We don't have non-blocking mode for now
151       raise NotImplementedError
152
153     self.__lock.acquire()
154     try:
155       if self.__deleted:
156         raise errors.LockError('deleted lock')
157
158       # We cannot acquire the lock if we already have it
159       assert not self.__is_owned(), "double acquire() on a non-recursive lock"
160       assert self.__npass_shr >= 0, "Internal fairness condition weirdness"
161
162       if shared:
163         self.__nwait_shr += 1
164         try:
165           wait = False
166           # If there is an exclusive holder waiting we have to wait.  We'll
167           # only do this once, though, when we start waiting for the lock. Then
168           # we'll just wait while there are no exclusive holders.
169           if self.__nwait_exc > 0:
170             # TODO: if !blocking...
171             wait = True
172             self.__wait(self.__turn_shr)
173
174           while self.__exc is not None:
175             wait = True
176             # TODO: if !blocking...
177             self.__wait(self.__turn_shr)
178
179           self.__shr.add(threading.currentThread())
180
181           # If we were waiting note that we passed
182           if wait:
183             self.__npass_shr -= 1
184
185         finally:
186           self.__nwait_shr -= 1
187
188         assert self.__npass_shr >= 0, "Internal fairness condition weirdness"
189       else:
190         # TODO: if !blocking...
191         # (or modify __exclusive_acquire for non-blocking mode)
192         self.__exclusive_acquire()
193
194     finally:
195       self.__lock.release()
196
197     return True
198
199   def release(self):
200     """Release a Shared Lock.
201
202     You must have acquired the lock, either in shared or in exclusive mode,
203     before calling this function.
204
205     """
206     self.__lock.acquire()
207     try:
208       assert self.__npass_shr >= 0, "Internal fairness condition weirdness"
209       # Autodetect release type
210       if self.__is_exclusive():
211         self.__exc = None
212
213         # An exclusive holder has just had the lock, time to put it in shared
214         # mode if there are shared holders waiting. Otherwise wake up the next
215         # exclusive holder.
216         if self.__nwait_shr > 0:
217           # Make sure at least the ones which were blocked pass.
218           self.__npass_shr = self.__nwait_shr
219           self.__turn_shr.notifyAll()
220         elif self.__nwait_exc > 0:
221           self.__turn_exc.notify()
222
223       elif self.__is_sharer():
224         self.__shr.remove(threading.currentThread())
225
226         # If there are shared holders waiting (and not just scheduled to pass)
227         # there *must* be an exclusive holder waiting as well; otherwise what
228         # were they waiting for?
229         assert (self.__nwait_exc > 0 or self.__npass_shr == self.__nwait_shr), \
230                "Lock sharers waiting while no exclusive is queueing"
231
232         # If there are no more shared holders either in or scheduled to pass,
233         # and some exclusive holders are waiting let's wake one up.
234         if (len(self.__shr) == 0 and
235             self.__nwait_exc > 0 and
236             not self.__npass_shr > 0):
237           self.__turn_exc.notify()
238
239       else:
240         assert False, "Cannot release non-owned lock"
241
242     finally:
243       self.__lock.release()
244
245   def delete(self, blocking=1):
246     """Delete a Shared Lock.
247
248     This operation will declare the lock for removal. First the lock will be
249     acquired in exclusive mode if you don't already own it, then the lock
250     will be put in a state where any future and pending acquire() fail.
251
252     Args:
253       blocking: whether to block while trying to acquire or to operate in
254                 try-lock mode.  this locking mode is not supported yet unless
255                 you are already holding exclusively the lock.
256
257     """
258     self.__lock.acquire()
259     try:
260       assert not self.__is_sharer(), "cannot delete() a lock while sharing it"
261
262       if self.__deleted:
263         raise errors.LockError('deleted lock')
264
265       if not self.__is_exclusive():
266         if not blocking:
267           # We don't have non-blocking mode for now
268           raise NotImplementedError
269         self.__exclusive_acquire()
270
271       self.__deleted = True
272       self.__exc = None
273       # Wake up everybody, they will fail acquiring the lock and
274       # raise an exception instead.
275       self.__turn_exc.notifyAll()
276       self.__turn_shr.notifyAll()
277
278     finally:
279       self.__lock.release()
280
281
282 class LockSet:
283   """Implements a set of locks.
284
285   This abstraction implements a set of shared locks for the same resource type,
286   distinguished by name. The user can lock a subset of the resources and the
287   LockSet will take care of acquiring the locks always in the same order, thus
288   preventing deadlock.
289
290   All the locks needed in the same set must be acquired together, though.
291
292   """
293   def __init__(self, members=None):
294     """Constructs a new LockSet.
295
296     Args:
297       members: initial members of the set
298
299     """
300     # Used internally to guarantee coherency.
301     self.__lock = SharedLock()
302
303     # The lockdict indexes the relationship name -> lock
304     # The order-of-locking is implied by the alphabetical order of names
305     self.__lockdict = {}
306
307     if members is not None:
308       for name in members:
309         self.__lockdict[name] = SharedLock()
310
311     # The owner dict contains the set of locks each thread owns. For
312     # performance each thread can access its own key without a global lock on
313     # this structure. It is paramount though that *no* other type of access is
314     # done to this structure (eg. no looping over its keys). *_owner helper
315     # function are defined to guarantee access is correct, but in general never
316     # do anything different than __owners[threading.currentThread()], or there
317     # will be trouble.
318     self.__owners = {}
319
320   def _is_owned(self):
321     """Is the current thread a current level owner?"""
322     return threading.currentThread() in self.__owners
323
324   def _add_owned(self, name=None):
325     """Note the current thread owns the given lock"""
326     if name is None:
327       if not self._is_owned():
328         self.__owners[threading.currentThread()] = set()
329     else:
330       if self._is_owned():
331         self.__owners[threading.currentThread()].add(name)
332       else:
333         self.__owners[threading.currentThread()] = set([name])
334
335
336   def _del_owned(self, name=None):
337     """Note the current thread owns the given lock"""
338
339     if name is not None:
340       self.__owners[threading.currentThread()].remove(name)
341
342     # Only remove the key if we don't hold the set-lock as well
343     if (not self.__lock._is_owned() and
344         not self.__owners[threading.currentThread()]):
345       del self.__owners[threading.currentThread()]
346
347   def _list_owned(self):
348     """Get the set of resource names owned by the current thread"""
349     if self._is_owned():
350       return self.__owners[threading.currentThread()].copy()
351     else:
352       return set()
353
354   def __names(self):
355     """Return the current set of names.
356
357     Only call this function while holding __lock and don't iterate on the
358     result after releasing the lock.
359
360     """
361     return self.__lockdict.keys()
362
363   def _names(self):
364     """Return a copy of the current set of elements.
365
366     Used only for debugging purposes.
367
368     """
369     self.__lock.acquire(shared=1)
370     try:
371       result = self.__names()
372     finally:
373       self.__lock.release()
374     return set(result)
375
376   def acquire(self, names, blocking=1, shared=0):
377     """Acquire a set of resource locks.
378
379     Args:
380       names: the names of the locks which shall be acquired.
381              (special lock names, or instance/node names)
382       shared: whether to acquire in shared mode. By default an exclusive lock
383               will be acquired.
384       blocking: whether to block while trying to acquire or to operate in
385                 try-lock mode.  this locking mode is not supported yet.
386
387     Returns:
388       True: when all the locks are successfully acquired
389
390     Raises:
391       errors.LockError: when any lock we try to acquire has been deleted
392       before we succeed. In this case none of the locks requested will be
393       acquired.
394
395     """
396     if not blocking:
397       # We don't have non-blocking mode for now
398       raise NotImplementedError
399
400     # Check we don't already own locks at this level
401     assert not self._is_owned(), "Cannot acquire locks in the same set twice"
402
403     if names is None:
404       # If no names are given acquire the whole set by not letting new names
405       # being added before we release, and getting the current list of names.
406       # Some of them may then be deleted later, but we'll cope with this.
407       #
408       # We'd like to acquire this lock in a shared way, as it's nice if
409       # everybody else can use the instances at the same time. If are acquiring
410       # them exclusively though they won't be able to do this anyway, though,
411       # so we'll get the list lock exclusively as well in order to be able to
412       # do add() on the set while owning it.
413       self.__lock.acquire(shared=shared)
414       try:
415         # note we own the set-lock
416         self._add_owned()
417         names = self.__names()
418       except:
419         # We shouldn't have problems adding the lock to the owners list, but
420         # if we did we'll try to release this lock and re-raise exception.
421         # Of course something is going to be really wrong, after this.
422         self.__lock.release()
423         raise
424
425     try:
426       # Support passing in a single resource to acquire rather than many
427       if isinstance(names, basestring):
428         names = [names]
429       else:
430         names.sort()
431
432       acquire_list = []
433       # First we look the locks up on __lockdict. We have no way of being sure
434       # they will still be there after, but this makes it a lot faster should
435       # just one of them be the already wrong
436       for lname in names:
437         try:
438           lock = self.__lockdict[lname] # raises KeyError if lock is not there
439           acquire_list.append((lname, lock))
440         except (KeyError):
441           if self.__lock._is_owned():
442             # We are acquiring all the set, it doesn't matter if this particular
443             # element is not there anymore.
444             continue
445           else:
446             raise errors.LockError('non-existing lock in set (%s)' % lname)
447
448       # This will hold the locknames we effectively acquired.
449       acquired = set()
450       # Now acquire_list contains a sorted list of resources and locks we want.
451       # In order to get them we loop on this (private) list and acquire() them.
452       # We gave no real guarantee they will still exist till this is done but
453       # .acquire() itself is safe and will alert us if the lock gets deleted.
454       for (lname, lock) in acquire_list:
455         try:
456           lock.acquire(shared=shared) # raises LockError if the lock is deleted
457           # now the lock cannot be deleted, we have it!
458           self._add_owned(name=lname)
459           acquired.add(lname)
460         except (errors.LockError):
461           if self.__lock._is_owned():
462             # We are acquiring all the set, it doesn't matter if this particular
463             # element is not there anymore.
464             continue
465           else:
466             name_fail = lname
467             for lname in self._list_owned():
468               self.__lockdict[lname].release()
469               self._del_owned(name=lname)
470             raise errors.LockError('non-existing lock in set (%s)' % name_fail)
471         except:
472           # We shouldn't have problems adding the lock to the owners list, but
473           # if we did we'll try to release this lock and re-raise exception.
474           # Of course something is going to be really wrong, after this.
475           if lock._is_owned():
476             lock.release()
477             raise
478
479     except:
480       # If something went wrong and we had the set-lock let's release it...
481       if self.__lock._is_owned():
482         self.__lock.release()
483       raise
484
485     return acquired
486
487   def release(self, names=None):
488     """Release a set of resource locks, at the same level.
489
490     You must have acquired the locks, either in shared or in exclusive mode,
491     before releasing them.
492
493     Args:
494       names: the names of the locks which shall be released.
495              (defaults to all the locks acquired at that level).
496
497     """
498     assert self._is_owned(), "release() on lock set while not owner"
499
500     # Support passing in a single resource to release rather than many
501     if isinstance(names, basestring):
502       names = [names]
503
504     if names is None:
505       names = self._list_owned()
506     else:
507       names = set(names)
508       assert self._list_owned().issuperset(names), (
509                "release() on unheld resources %s" %
510                names.difference(self._list_owned()))
511
512     # First of all let's release the "all elements" lock, if set.
513     # After this 'add' can work again
514     if self.__lock._is_owned():
515       self.__lock.release()
516       self._del_owned()
517
518     for lockname in names:
519       # If we are sure the lock doesn't leave __lockdict without being
520       # exclusively held we can do this...
521       self.__lockdict[lockname].release()
522       self._del_owned(name=lockname)
523
524   def add(self, names, acquired=0, shared=0):
525     """Add a new set of elements to the set
526
527     Args:
528       names: names of the new elements to add
529       acquired: pre-acquire the new resource?
530       shared: is the pre-acquisition shared?
531
532     """
533
534     assert not self.__lock._is_owned(shared=1), (
535            "Cannot add new elements while sharing the set-lock")
536
537     # Support passing in a single resource to add rather than many
538     if isinstance(names, basestring):
539       names = [names]
540
541     # If we don't already own the set-level lock acquire it in an exclusive way
542     # we'll get it and note we need to release it later.
543     release_lock = False
544     if not self.__lock._is_owned():
545       release_lock = True
546       self.__lock.acquire()
547
548     try:
549       invalid_names = set(self.__names()).intersection(names)
550       if invalid_names:
551         # This must be an explicit raise, not an assert, because assert is
552         # turned off when using optimization, and this can happen because of
553         # concurrency even if the user doesn't want it.
554         raise errors.LockError("duplicate add() (%s)" % invalid_names)
555
556       for lockname in names:
557         lock = SharedLock()
558
559         if acquired:
560           lock.acquire(shared=shared)
561           # now the lock cannot be deleted, we have it!
562           try:
563             self._add_owned(name=lockname)
564           except:
565             # We shouldn't have problems adding the lock to the owners list,
566             # but if we did we'll try to release this lock and re-raise
567             # exception.  Of course something is going to be really wrong,
568             # after this.  On the other hand the lock hasn't been added to the
569             # __lockdict yet so no other threads should be pending on it. This
570             # release is just a safety measure.
571             lock.release()
572             raise
573
574         self.__lockdict[lockname] = lock
575
576     finally:
577       # Only release __lock if we were not holding it previously.
578       if release_lock:
579         self.__lock.release()
580
581     return True
582
583   def remove(self, names, blocking=1):
584     """Remove elements from the lock set.
585
586     You can either not hold anything in the lockset or already hold a superset
587     of the elements you want to delete, exclusively.
588
589     Args:
590       names: names of the resource to remove.
591       blocking: whether to block while trying to acquire or to operate in
592                 try-lock mode.  this locking mode is not supported yet unless
593                 you are already holding exclusively the locks.
594
595     Returns:
596       A list of lock which we removed. The list is always equal to the names
597       list if we were holding all the locks exclusively.
598
599     """
600     if not blocking and not self._is_owned():
601       # We don't have non-blocking mode for now
602       raise NotImplementedError
603
604     # Support passing in a single resource to remove rather than many
605     if isinstance(names, basestring):
606       names = [names]
607
608     # If we own any subset of this lock it must be a superset of what we want
609     # to delete. The ownership must also be exclusive, but that will be checked
610     # by the lock itself.
611     assert not self._is_owned() or self._list_owned().issuperset(names), (
612       "remove() on acquired lockset while not owning all elements")
613
614     removed = []
615
616     for lname in names:
617       # Calling delete() acquires the lock exclusively if we don't already own
618       # it, and causes all pending and subsequent lock acquires to fail. It's
619       # fine to call it out of order because delete() also implies release(),
620       # and the assertion above guarantees that if we either already hold
621       # everything we want to delete, or we hold none.
622       try:
623         self.__lockdict[lname].delete()
624         removed.append(lname)
625       except (KeyError, errors.LockError):
626         # This cannot happen if we were already holding it, verify:
627         assert not self._is_owned(), "remove failed while holding lockset"
628       else:
629         # If no LockError was raised we are the ones who deleted the lock.
630         # This means we can safely remove it from lockdict, as any further or
631         # pending delete() or acquire() will fail (and nobody can have the lock
632         # since before our call to delete()).
633         #
634         # This is done in an else clause because if the exception was thrown
635         # it's the job of the one who actually deleted it.
636         del self.__lockdict[lname]
637         # And let's remove it from our private list if we owned it.
638         if self._is_owned():
639           self._del_owned(name=lname)
640
641     return removed
642
643
644 # Locking levels, must be acquired in increasing order.
645 # Current rules are:
646 #   - at level LEVEL_CLUSTER resides the Big Ganeti Lock (BGL) which must be
647 #   acquired before performing any operation, either in shared or in exclusive
648 #   mode. acquiring the BGL in exclusive mode is discouraged and should be
649 #   avoided.
650 #   - at levels LEVEL_NODE and LEVEL_INSTANCE reside node and instance locks.
651 #   If you need more than one node, or more than one instance, acquire them at
652 #   the same time.
653 #  - level LEVEL_CONFIG contains the configuration lock, which you must acquire
654 #  before reading or changing the config file.
655 LEVEL_CLUSTER = 0
656 LEVEL_NODE = 1
657 LEVEL_INSTANCE = 2
658 LEVEL_CONFIG = 3
659
660 LEVELS = [LEVEL_CLUSTER,
661           LEVEL_NODE,
662           LEVEL_INSTANCE,
663           LEVEL_CONFIG]
664
665 # Lock levels which are modifiable
666 LEVELS_MOD = [LEVEL_NODE, LEVEL_INSTANCE]
667
668 # Constant for the big ganeti lock and config lock
669 BGL = 'BGL'
670 CONFIG = 'config'
671
672
673 class GanetiLockManager:
674   """The Ganeti Locking Library
675
676   The purpouse of this small library is to manage locking for ganeti clusters
677   in a central place, while at the same time doing dynamic checks against
678   possible deadlocks. It will also make it easier to transition to a different
679   lock type should we migrate away from python threads.
680
681   """
682   _instance = None
683
684   def __init__(self, nodes=None, instances=None):
685     """Constructs a new GanetiLockManager object.
686
687     There should be only a GanetiLockManager object at any time, so this
688     function raises an error if this is not the case.
689
690     Args:
691       nodes: list of node names
692       instances: list of instance names
693
694     """
695     assert self.__class__._instance is None, "double GanetiLockManager instance"
696     self.__class__._instance = self
697
698     # The keyring contains all the locks, at their level and in the correct
699     # locking order.
700     self.__keyring = {
701       LEVEL_CLUSTER: LockSet([BGL]),
702       LEVEL_NODE: LockSet(nodes),
703       LEVEL_INSTANCE: LockSet(instances),
704       LEVEL_CONFIG: LockSet([CONFIG]),
705     }
706
707   def _names(self, level):
708     """List the lock names at the given level.
709     Used for debugging/testing purposes.
710
711     Args:
712       level: the level whose list of locks to get
713
714     """
715     assert level in LEVELS, "Invalid locking level %s" % level
716     return self.__keyring[level]._names()
717
718   def _is_owned(self, level):
719     """Check whether we are owning locks at the given level
720
721     """
722     return self.__keyring[level]._is_owned()
723
724   def _list_owned(self, level):
725     """Get the set of owned locks at the given level
726
727     """
728     return self.__keyring[level]._list_owned()
729
730   def _upper_owned(self, level):
731     """Check that we don't own any lock at a level greater than the given one.
732
733     """
734     # This way of checking only works if LEVELS[i] = i, which we check for in
735     # the test cases.
736     return utils.any((self._is_owned(l) for l in LEVELS[level + 1:]))
737
738   def _BGL_owned(self):
739     """Check if the current thread owns the BGL.
740
741     Both an exclusive or a shared acquisition work.
742
743     """
744     return BGL in self.__keyring[LEVEL_CLUSTER]._list_owned()
745
746   def _contains_BGL(self, level, names):
747     """Check if acting on the given level and set of names will change the
748     status of the Big Ganeti Lock.
749
750     """
751     return level == LEVEL_CLUSTER and (names is None or BGL in names)
752
753   def acquire(self, level, names, blocking=1, shared=0):
754     """Acquire a set of resource locks, at the same level.
755
756     Args:
757       level: the level at which the locks shall be acquired.
758              It must be a memmber of LEVELS.
759       names: the names of the locks which shall be acquired.
760              (special lock names, or instance/node names)
761       shared: whether to acquire in shared mode. By default an exclusive lock
762               will be acquired.
763       blocking: whether to block while trying to acquire or to operate in
764                 try-lock mode.  this locking mode is not supported yet.
765
766     """
767     assert level in LEVELS, "Invalid locking level %s" % level
768
769     # Check that we are either acquiring the Big Ganeti Lock or we already own
770     # it. Some "legacy" opcodes need to be sure they are run non-concurrently
771     # so even if we've migrated we need to at least share the BGL to be
772     # compatible with them. Of course if we own the BGL exclusively there's no
773     # point in acquiring any other lock, unless perhaps we are half way through
774     # the migration of the current opcode.
775     assert (self._contains_BGL(level, names) or self._BGL_owned()), (
776             "You must own the Big Ganeti Lock before acquiring any other")
777
778     # Check we don't own locks at the same or upper levels.
779     assert not self._upper_owned(level), ("Cannot acquire locks at a level"
780            " while owning some at a greater one")
781
782     # Acquire the locks in the set.
783     return self.__keyring[level].acquire(names, shared=shared,
784                                          blocking=blocking)
785
786   def release(self, level, names=None):
787     """Release a set of resource locks, at the same level.
788
789     You must have acquired the locks, either in shared or in exclusive mode,
790     before releasing them.
791
792     Args:
793       level: the level at which the locks shall be released.
794              It must be a memmber of LEVELS.
795       names: the names of the locks which shall be released.
796              (defaults to all the locks acquired at that level).
797
798     """
799     assert level in LEVELS, "Invalid locking level %s" % level
800     assert (not self._contains_BGL(level, names) or
801             not self._upper_owned(LEVEL_CLUSTER)), (
802             "Cannot release the Big Ganeti Lock while holding something"
803             " at upper levels")
804
805     # Release will complain if we don't own the locks already
806     return self.__keyring[level].release(names)
807
808   def add(self, level, names, acquired=0, shared=0):
809     """Add locks at the specified level.
810
811     Args:
812       level: the level at which the locks shall be added.
813              It must be a memmber of LEVELS_MOD.
814       names: names of the locks to acquire
815       acquired: whether to acquire the newly added locks
816       shared: whether the acquisition will be shared
817     """
818     assert level in LEVELS_MOD, "Invalid or immutable level %s" % level
819     assert self._BGL_owned(), ("You must own the BGL before performing other"
820            " operations")
821     assert not self._upper_owned(level), ("Cannot add locks at a level"
822            " while owning some at a greater one")
823     return self.__keyring[level].add(names, acquired=acquired, shared=shared)
824
825   def remove(self, level, names, blocking=1):
826     """Remove locks from the specified level.
827
828     You must either already own the locks you are trying to remove exclusively
829     or not own any lock at an upper level.
830
831     Args:
832       level: the level at which the locks shall be removed.
833              It must be a memmber of LEVELS_MOD.
834       names: the names of the locks which shall be removed.
835              (special lock names, or instance/node names)
836       blocking: whether to block while trying to operate in try-lock mode.
837                 this locking mode is not supported yet.
838
839     """
840     assert level in LEVELS_MOD, "Invalid or immutable level %s" % level
841     assert self._BGL_owned(), ("You must own the BGL before performing other"
842            " operations")
843     # Check we either own the level or don't own anything from here up.
844     # LockSet.remove() will check the case in which we don't own all the needed
845     # resources, or we have a shared ownership.
846     assert self._is_owned(level) or not self._upper_owned(level), (
847            "Cannot remove locks at a level while not owning it or"
848            " owning some at a greater one")
849     return self.__keyring[level].remove(names, blocking=blocking)