utils.log: Split formatter building into separate function
[ganeti-local] / lib / locking.py
index 4e99110..3575085 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2006, 2007 Google Inc.
+# Copyright (C) 2006, 2007, 2008, 2009, 2010 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 
 """Module implementing the Ganeti locking code."""
 
-# pylint: disable-msg=W0613,W0201
+# pylint: disable-msg=W0212
 
+# W0212 since e.g. LockSet methods use (a lot) the internals of
+# SharedLock
+
+import os
+import select
 import threading
+import errno
+import weakref
+import logging
+import heapq
+import operator
+
+from ganeti import errors
+from ganeti import utils
+from ganeti import compat
+from ganeti import query
+
+
+_EXCLUSIVE_TEXT = "exclusive"
+_SHARED_TEXT = "shared"
+_DELETED_TEXT = "deleted"
+
+_DEFAULT_PRIORITY = 0
+
+
+def ssynchronized(mylock, shared=0):
+  """Shared Synchronization decorator.
+
+  Calls the function holding the given lock, either in exclusive or shared
+  mode. It requires the passed lock to be a SharedLock (or support its
+  semantics).
+
+  @type mylock: lockable object or string
+  @param mylock: lock to acquire or class member name of the lock to acquire
+
+  """
+  def wrap(fn):
+    def sync_function(*args, **kwargs):
+      if isinstance(mylock, basestring):
+        assert args, "cannot ssynchronize on non-class method: self not found"
+        # args[0] is "self"
+        lock = getattr(args[0], mylock)
+      else:
+        lock = mylock
+      lock.acquire(shared=shared)
+      try:
+        return fn(*args, **kwargs)
+      finally:
+        lock.release()
+    return sync_function
+  return wrap
+
+
+class _SingleNotifyPipeConditionWaiter(object):
+  """Helper class for SingleNotifyPipeCondition
+
+  """
+  __slots__ = [
+    "_fd",
+    "_poller",
+    ]
+
+  def __init__(self, poller, fd):
+    """Constructor for _SingleNotifyPipeConditionWaiter
+
+    @type poller: select.poll
+    @param poller: Poller object
+    @type fd: int
+    @param fd: File descriptor to wait for
+
+    """
+    object.__init__(self)
+    self._poller = poller
+    self._fd = fd
+
+  def __call__(self, timeout):
+    """Wait for something to happen on the pipe.
+
+    @type timeout: float or None
+    @param timeout: Timeout for waiting (can be None)
+
+    """
+    running_timeout = utils.RunningTimeout(timeout, True)
+
+    while True:
+      remaining_time = running_timeout.Remaining()
+
+      if remaining_time is not None:
+        if remaining_time < 0.0:
+          break
+
+        # Our calculation uses seconds, poll() wants milliseconds
+        remaining_time *= 1000
+
+      try:
+        result = self._poller.poll(remaining_time)
+      except EnvironmentError, err:
+        if err.errno != errno.EINTR:
+          raise
+        result = None
+
+      # Check whether we were notified
+      if result and result[0][0] == self._fd:
+        break
+
+
+class _BaseCondition(object):
+  """Base class containing common code for conditions.
+
+  Some of this code is taken from python's threading module.
+
+  """
+  __slots__ = [
+    "_lock",
+    "acquire",
+    "release",
+    "_is_owned",
+    "_acquire_restore",
+    "_release_save",
+    ]
+
+  def __init__(self, lock):
+    """Constructor for _BaseCondition.
+
+    @type lock: threading.Lock
+    @param lock: condition base lock
+
+    """
+    object.__init__(self)
+
+    try:
+      self._release_save = lock._release_save
+    except AttributeError:
+      self._release_save = self._base_release_save
+    try:
+      self._acquire_restore = lock._acquire_restore
+    except AttributeError:
+      self._acquire_restore = self._base_acquire_restore
+    try:
+      self._is_owned = lock._is_owned
+    except AttributeError:
+      self._is_owned = self._base_is_owned
+
+    self._lock = lock
+
+    # Export the lock's acquire() and release() methods
+    self.acquire = lock.acquire
+    self.release = lock.release
+
+  def _base_is_owned(self):
+    """Check whether lock is owned by current thread.
+
+    """
+    if self._lock.acquire(0):
+      self._lock.release()
+      return False
+    return True
+
+  def _base_release_save(self):
+    self._lock.release()
+
+  def _base_acquire_restore(self, _):
+    self._lock.acquire()
+
+  def _check_owned(self):
+    """Raise an exception if the current thread doesn't own the lock.
+
+    """
+    if not self._is_owned():
+      raise RuntimeError("cannot work with un-aquired lock")
+
+
+class SingleNotifyPipeCondition(_BaseCondition):
+  """Condition which can only be notified once.
+
+  This condition class uses pipes and poll, internally, to be able to wait for
+  notification with a timeout, without resorting to polling. It is almost
+  compatible with Python's threading.Condition, with the following differences:
+    - notifyAll can only be called once, and no wait can happen after that
+    - notify is not supported, only notifyAll
+
+  """
+
+  __slots__ = [
+    "_poller",
+    "_read_fd",
+    "_write_fd",
+    "_nwaiters",
+    "_notified",
+    ]
+
+  _waiter_class = _SingleNotifyPipeConditionWaiter
+
+  def __init__(self, lock):
+    """Constructor for SingleNotifyPipeCondition
+
+    """
+    _BaseCondition.__init__(self, lock)
+    self._nwaiters = 0
+    self._notified = False
+    self._read_fd = None
+    self._write_fd = None
+    self._poller = None
+
+  def _check_unnotified(self):
+    """Throws an exception if already notified.
+
+    """
+    if self._notified:
+      raise RuntimeError("cannot use already notified condition")
+
+  def _Cleanup(self):
+    """Cleanup open file descriptors, if any.
+
+    """
+    if self._read_fd is not None:
+      os.close(self._read_fd)
+      self._read_fd = None
+
+    if self._write_fd is not None:
+      os.close(self._write_fd)
+      self._write_fd = None
+    self._poller = None
+
+  def wait(self, timeout=None):
+    """Wait for a notification.
+
+    @type timeout: float or None
+    @param timeout: Waiting timeout (can be None)
+
+    """
+    self._check_owned()
+    self._check_unnotified()
+
+    self._nwaiters += 1
+    try:
+      if self._poller is None:
+        (self._read_fd, self._write_fd) = os.pipe()
+        self._poller = select.poll()
+        self._poller.register(self._read_fd, select.POLLHUP)
+
+      wait_fn = self._waiter_class(self._poller, self._read_fd)
+      state = self._release_save()
+      try:
+        # Wait for notification
+        wait_fn(timeout)
+      finally:
+        # Re-acquire lock
+        self._acquire_restore(state)
+    finally:
+      self._nwaiters -= 1
+      if self._nwaiters == 0:
+        self._Cleanup()
+
+  def notifyAll(self): # pylint: disable-msg=C0103
+    """Close the writing side of the pipe to notify all waiters.
+
+    """
+    self._check_owned()
+    self._check_unnotified()
+    self._notified = True
+    if self._write_fd is not None:
+      os.close(self._write_fd)
+      self._write_fd = None
+
+
+class PipeCondition(_BaseCondition):
+  """Group-only non-polling condition with counters.
+
+  This condition class uses pipes and poll, internally, to be able to wait for
+  notification with a timeout, without resorting to polling. It is almost
+  compatible with Python's threading.Condition, but only supports notifyAll and
+  non-recursive locks. As an additional features it's able to report whether
+  there are any waiting threads.
+
+  """
+  __slots__ = [
+    "_waiters",
+    "_single_condition",
+    ]
+
+  _single_condition_class = SingleNotifyPipeCondition
+
+  def __init__(self, lock):
+    """Initializes this class.
+
+    """
+    _BaseCondition.__init__(self, lock)
+    self._waiters = set()
+    self._single_condition = self._single_condition_class(self._lock)
+
+  def wait(self, timeout=None):
+    """Wait for a notification.
+
+    @type timeout: float or None
+    @param timeout: Waiting timeout (can be None)
+
+    """
+    self._check_owned()
+
+    # Keep local reference to the pipe. It could be replaced by another thread
+    # notifying while we're waiting.
+    cond = self._single_condition
+
+    self._waiters.add(threading.currentThread())
+    try:
+      cond.wait(timeout)
+    finally:
+      self._check_owned()
+      self._waiters.remove(threading.currentThread())
+
+  def notifyAll(self): # pylint: disable-msg=C0103
+    """Notify all currently waiting threads.
+
+    """
+    self._check_owned()
+    self._single_condition.notifyAll()
+    self._single_condition = self._single_condition_class(self._lock)
+
+  def get_waiting(self):
+    """Returns a list of all waiting threads.
+
+    """
+    self._check_owned()
+
+    return self._waiters
+
+  def has_waiting(self):
+    """Returns whether there are active waiters.
+
+    """
+    self._check_owned()
+
+    return bool(self._waiters)
+
+
+class _PipeConditionWithMode(PipeCondition):
+  __slots__ = [
+    "shared",
+    ]
+
+  def __init__(self, lock, shared):
+    """Initializes this class.
+
+    """
+    self.shared = shared
+    PipeCondition.__init__(self, lock)
 
 
-class SharedLock:
+class SharedLock(object):
   """Implements a shared lock.
 
-  Multiple threads can acquire the lock in a shared way, calling
-  acquire_shared().  In order to acquire the lock in an exclusive way threads
-  can call acquire_exclusive().
+  Multiple threads can acquire the lock in a shared way by calling
+  C{acquire(shared=1)}. In order to acquire the lock in an exclusive way
+  threads can call C{acquire(shared=0)}.
 
-  The lock prevents starvation but does not guarantee that threads will acquire
-  the shared lock in the order they queued for it, just that they will
-  eventually do so.
+  Notes on data structures: C{__pending} contains a priority queue (heapq) of
+  all pending acquires: C{[(priority1: prioqueue1), (priority2: prioqueue2),
+  ...]}. Each per-priority queue contains a normal in-order list of conditions
+  to be notified when the lock can be acquired. Shared locks are grouped
+  together by priority and the condition for them is stored in
+  C{__pending_shared} if it already exists. C{__pending_by_prio} keeps
+  references for the per-priority queues indexed by priority for faster access.
+
+  @type name: string
+  @ivar name: the name of the lock
 
   """
-  def __init__(self):
-    """Construct a new Shared Lock"""
-    # we have two conditions, c_shr and c_exc, sharing the same lock.
+  __slots__ = [
+    "__weakref__",
+    "__deleted",
+    "__exc",
+    "__lock",
+    "__pending",
+    "__pending_by_prio",
+    "__pending_shared",
+    "__shr",
+    "name",
+    ]
+
+  __condition_class = _PipeConditionWithMode
+
+  def __init__(self, name, monitor=None):
+    """Construct a new SharedLock.
+
+    @param name: the name of the lock
+    @type monitor: L{LockMonitor}
+    @param monitor: Lock monitor with which to register
+
+    """
+    object.__init__(self)
+
+    self.name = name
+
+    # Internal lock
     self.__lock = threading.Lock()
-    self.__turn_shr = threading.Condition(self.__lock)
-    self.__turn_exc = threading.Condition(self.__lock)
 
-    # current lock holders
+    # Queue containing waiting acquires
+    self.__pending = []
+    self.__pending_by_prio = {}
+    self.__pending_shared = {}
+
+    # Current lock holders
     self.__shr = set()
     self.__exc = None
 
-    # lock waiters
-    self.__nwait_exc = 0
-    self.__nwait_shr = 0
+    # is this lock in the deleted state?
+    self.__deleted = False
+
+    # Register with lock monitor
+    if monitor:
+      monitor.RegisterLock(self)
+
+  def GetInfo(self, requested):
+    """Retrieves information for querying locks.
+
+    @type requested: set
+    @param requested: Requested information, see C{query.LQ_*}
+
+    """
+    self.__lock.acquire()
+    try:
+      # Note: to avoid unintentional race conditions, no references to
+      # modifiable objects should be returned unless they were created in this
+      # function.
+      mode = None
+      owner_names = None
+
+      if query.LQ_MODE in requested:
+        if self.__deleted:
+          mode = _DELETED_TEXT
+          assert not (self.__exc or self.__shr)
+        elif self.__exc:
+          mode = _EXCLUSIVE_TEXT
+        elif self.__shr:
+          mode = _SHARED_TEXT
+
+      # Current owner(s) are wanted
+      if query.LQ_OWNER in requested:
+        if self.__exc:
+          owner = [self.__exc]
+        else:
+          owner = self.__shr
+
+        if owner:
+          assert not self.__deleted
+          owner_names = [i.getName() for i in owner]
+
+      # Pending acquires are wanted
+      if query.LQ_PENDING in requested:
+        pending = []
+
+        # Sorting instead of copying and using heaq functions for simplicity
+        for (_, prioqueue) in sorted(self.__pending):
+          for cond in prioqueue:
+            if cond.shared:
+              pendmode = _SHARED_TEXT
+            else:
+              pendmode = _EXCLUSIVE_TEXT
+
+            # List of names will be sorted in L{query._GetLockPending}
+            pending.append((pendmode, [i.getName()
+                                       for i in cond.get_waiting()]))
+      else:
+        pending = None
+
+      return (self.name, mode, owner_names, pending)
+    finally:
+      self.__lock.release()
+
+  def __check_deleted(self):
+    """Raises an exception if the lock has been deleted.
+
+    """
+    if self.__deleted:
+      raise errors.LockError("Deleted lock %s" % self.name)
 
   def __is_sharer(self):
-    """Is the current thread sharing the lock at this time?"""
+    """Is the current thread sharing the lock at this time?
+
+    """
     return threading.currentThread() in self.__shr
 
   def __is_exclusive(self):
-    """Is the current thread holding the lock exclusively at this time?"""
+    """Is the current thread holding the lock exclusively at this time?
+
+    """
     return threading.currentThread() == self.__exc
 
   def __is_owned(self, shared=-1):
@@ -77,80 +528,199 @@ class SharedLock:
   def _is_owned(self, shared=-1):
     """Is the current thread somehow owning the lock at this time?
 
-    Args:
-      shared:
-        < 0: check for any type of ownership (default)
-        0: check for exclusive ownership
-        > 0: check for shared ownership
+    @param shared:
+        - < 0: check for any type of ownership (default)
+        - 0: check for exclusive ownership
+        - > 0: check for shared ownership
 
     """
     self.__lock.acquire()
     try:
-      result = self.__is_owned(shared)
+      return self.__is_owned(shared=shared)
     finally:
       self.__lock.release()
 
-    return result
-
-  def acquire(self, blocking=1, shared=0):
-    """Acquire a shared lock.
+  def _count_pending(self):
+    """Returns the number of pending acquires.
 
-    Args:
-      shared: whether to acquire in shared mode. By default an exclusive lock
-              will be acquired.
-      blocking: whether to block while trying to acquire or to operate in try-lock mode.
-                this locking mode is not supported yet.
+    @rtype: int
 
     """
-    if not blocking:
-      # We don't have non-blocking mode for now
-      raise NotImplementedError
+    self.__lock.acquire()
+    try:
+      return sum(len(prioqueue) for (_, prioqueue) in self.__pending)
+    finally:
+      self.__lock.release()
 
+  def _check_empty(self):
+    """Checks whether there are any pending acquires.
+
+    @rtype: bool
+
+    """
     self.__lock.acquire()
     try:
-      # We cannot acquire the lock if we already have it
-      assert not self.__is_owned(), "double acquire() on a non-recursive lock"
+      # Order is important: __find_first_pending_queue modifies __pending
+      return not (self.__find_first_pending_queue() or
+                  self.__pending or
+                  self.__pending_by_prio or
+                  self.__pending_shared)
+    finally:
+      self.__lock.release()
+
+  def __do_acquire(self, shared):
+    """Actually acquire the lock.
+
+    """
+    if shared:
+      self.__shr.add(threading.currentThread())
+    else:
+      self.__exc = threading.currentThread()
+
+  def __can_acquire(self, shared):
+    """Determine whether lock can be acquired.
+
+    """
+    if shared:
+      return self.__exc is None
+    else:
+      return len(self.__shr) == 0 and self.__exc is None
+
+  def __find_first_pending_queue(self):
+    """Tries to find the topmost queued entry with pending acquires.
+
+    Removes empty entries while going through the list.
+
+    """
+    while self.__pending:
+      (priority, prioqueue) = self.__pending[0]
+
+      if not prioqueue:
+        heapq.heappop(self.__pending)
+        del self.__pending_by_prio[priority]
+        assert priority not in self.__pending_shared
+        continue
+
+      if prioqueue:
+        return prioqueue
+
+    return None
+
+  def __is_on_top(self, cond):
+    """Checks whether the passed condition is on top of the queue.
+
+    The caller must make sure the queue isn't empty.
+
+    """
+    return cond == self.__find_first_pending_queue()[0]
+
+  def __acquire_unlocked(self, shared, timeout, priority):
+    """Acquire a shared lock.
+
+    @param shared: whether to acquire in shared mode; by default an
+        exclusive lock will be acquired
+    @param timeout: maximum waiting time before giving up
+    @type priority: integer
+    @param priority: Priority for acquiring lock
+
+    """
+    self.__check_deleted()
+
+    # We cannot acquire the lock if we already have it
+    assert not self.__is_owned(), ("double acquire() on a non-recursive lock"
+                                   " %s" % self.name)
+
+    # Remove empty entries from queue
+    self.__find_first_pending_queue()
+
+    # Check whether someone else holds the lock or there are pending acquires.
+    if not self.__pending and self.__can_acquire(shared):
+      # Apparently not, can acquire lock directly.
+      self.__do_acquire(shared)
+      return True
+
+    prioqueue = self.__pending_by_prio.get(priority, None)
+
+    if shared:
+      # Try to re-use condition for shared acquire
+      wait_condition = self.__pending_shared.get(priority, None)
+      assert (wait_condition is None or
+              (wait_condition.shared and wait_condition in prioqueue))
+    else:
+      wait_condition = None
+
+    if wait_condition is None:
+      if prioqueue is None:
+        assert priority not in self.__pending_by_prio
+
+        prioqueue = []
+        heapq.heappush(self.__pending, (priority, prioqueue))
+        self.__pending_by_prio[priority] = prioqueue
+
+      wait_condition = self.__condition_class(self.__lock, shared)
+      prioqueue.append(wait_condition)
 
       if shared:
-        self.__nwait_shr += 1
-        try:
-          # If there is an exclusive holder waiting we have to wait.  We'll
-          # only do this once, though, when we start waiting for the lock. Then
-          # we'll just wait while there are no exclusive holders.
-          if self.__nwait_exc > 0:
-            # TODO: if !blocking...
-            self.__turn_shr.wait()
+        # Keep reference for further shared acquires on same priority. This is
+        # better than trying to find it in the list of pending acquires.
+        assert priority not in self.__pending_shared
+        self.__pending_shared[priority] = wait_condition
 
-          while self.__exc is not None:
-            # TODO: if !blocking...
-            self.__turn_shr.wait()
+    try:
+      # Wait until we become the topmost acquire in the queue or the timeout
+      # expires.
+      # TODO: Decrease timeout with spurious notifications
+      while not (self.__is_on_top(wait_condition) and
+                 self.__can_acquire(shared)):
+        # Wait for notification
+        wait_condition.wait(timeout)
+        self.__check_deleted()
 
-          self.__shr.add(threading.currentThread())
-        finally:
-          self.__nwait_shr -= 1
+        # A lot of code assumes blocking acquires always succeed. Loop
+        # internally for that case.
+        if timeout is not None:
+          break
 
-      else:
-        self.__nwait_exc += 1
-        try:
-          # This is to save ourselves from a nasty race condition that could
-          # theoretically make the sharers starve.
-          if self.__nwait_shr > 0 or self.__nwait_exc > 1:
-            # TODO: if !blocking...
-              self.__turn_exc.wait()
+      if self.__is_on_top(wait_condition) and self.__can_acquire(shared):
+        self.__do_acquire(shared)
+        return True
+    finally:
+      # Remove condition from queue if there are no more waiters
+      if not wait_condition.has_waiting():
+        prioqueue.remove(wait_condition)
+        if wait_condition.shared:
+          del self.__pending_shared[priority]
+
+    return False
+
+  def acquire(self, shared=0, timeout=None, priority=None,
+              test_notify=None):
+    """Acquire a shared lock.
 
-          while len(self.__shr) > 0 or self.__exc is not None:
-            # TODO: if !blocking...
-            self.__turn_exc.wait()
+    @type shared: integer (0/1) used as a boolean
+    @param shared: whether to acquire in shared mode; by default an
+        exclusive lock will be acquired
+    @type timeout: float
+    @param timeout: maximum waiting time before giving up
+    @type priority: integer
+    @param priority: Priority for acquiring lock
+    @type test_notify: callable or None
+    @param test_notify: Special callback function for unittesting
 
-          self.__exc = threading.currentThread()
-        finally:
-          self.__nwait_exc -= 1
+    """
+    if priority is None:
+      priority = _DEFAULT_PRIORITY
+
+    self.__lock.acquire()
+    try:
+      # We already got the lock, notify now
+      if __debug__ and callable(test_notify):
+        test_notify()
 
+      return self.__acquire_unlocked(shared, timeout, priority)
     finally:
       self.__lock.release()
 
-    return True
-
   def release(self):
     """Release a Shared Lock.
 
@@ -160,34 +730,855 @@ class SharedLock:
     """
     self.__lock.acquire()
     try:
+      assert self.__is_exclusive() or self.__is_sharer(), \
+        "Cannot release non-owned lock"
+
       # Autodetect release type
       if self.__is_exclusive():
         self.__exc = None
+      else:
+        self.__shr.remove(threading.currentThread())
 
-        # An exclusive holder has just had the lock, time to put it in shared
-        # mode if there are shared holders waiting. Otherwise wake up the next
-        # exclusive holder.
-        if self.__nwait_shr > 0:
-          self.__turn_shr.notifyAll()
-        elif self.__nwait_exc > 0:
-         self.__turn_exc.notify()
+      # Notify topmost condition in queue
+      prioqueue = self.__find_first_pending_queue()
+      if prioqueue:
+        prioqueue[0].notifyAll()
 
-      elif self.__is_sharer():
-        self.__shr.remove(threading.currentThread())
+    finally:
+      self.__lock.release()
+
+  def delete(self, timeout=None, priority=None):
+    """Delete a Shared Lock.
+
+    This operation will declare the lock for removal. First the lock will be
+    acquired in exclusive mode if you don't already own it, then the lock
+    will be put in a state where any future and pending acquire() fail.
+
+    @type timeout: float
+    @param timeout: maximum waiting time before giving up
+    @type priority: integer
+    @param priority: Priority for acquiring lock
+
+    """
+    if priority is None:
+      priority = _DEFAULT_PRIORITY
 
-        # If there are shared holders waiting there *must* be an exclusive holder
-        # waiting as well; otherwise what were they waiting for?
-        assert (self.__nwait_shr == 0 or self.__nwait_exc > 0,
-                "Lock sharers waiting while no exclusive is queueing")
+    self.__lock.acquire()
+    try:
+      assert not self.__is_sharer(), "Cannot delete() a lock while sharing it"
+
+      self.__check_deleted()
+
+      # The caller is allowed to hold the lock exclusively already.
+      acquired = self.__is_exclusive()
 
-        # If there are no more shared holders and some exclusive holders are
-        # waiting let's wake one up.
-        if len(self.__shr) == 0 and self.__nwait_exc > 0:
-          self.__turn_exc.notify()
+      if not acquired:
+        acquired = self.__acquire_unlocked(0, timeout, priority)
+
+        assert self.__is_exclusive() and not self.__is_sharer(), \
+          "Lock wasn't acquired in exclusive mode"
+
+      if acquired:
+        self.__deleted = True
+        self.__exc = None
 
+        assert not (self.__exc or self.__shr), "Found owner during deletion"
+
+        # Notify all acquires. They'll throw an error.
+        for (_, prioqueue) in self.__pending:
+          for cond in prioqueue:
+            cond.notifyAll()
+
+        assert self.__deleted
+
+      return acquired
+    finally:
+      self.__lock.release()
+
+  def _release_save(self):
+    shared = self.__is_sharer()
+    self.release()
+    return shared
+
+  def _acquire_restore(self, shared):
+    self.acquire(shared=shared)
+
+
+# Whenever we want to acquire a full LockSet we pass None as the value
+# to acquire.  Hide this behind this nicely named constant.
+ALL_SET = None
+
+
+class _AcquireTimeout(Exception):
+  """Internal exception to abort an acquire on a timeout.
+
+  """
+
+
+class LockSet:
+  """Implements a set of locks.
+
+  This abstraction implements a set of shared locks for the same resource type,
+  distinguished by name. The user can lock a subset of the resources and the
+  LockSet will take care of acquiring the locks always in the same order, thus
+  preventing deadlock.
+
+  All the locks needed in the same set must be acquired together, though.
+
+  @type name: string
+  @ivar name: the name of the lockset
+
+  """
+  def __init__(self, members, name, monitor=None):
+    """Constructs a new LockSet.
+
+    @type members: list of strings
+    @param members: initial members of the set
+    @type monitor: L{LockMonitor}
+    @param monitor: Lock monitor with which to register member locks
+
+    """
+    assert members is not None, "members parameter is not a list"
+    self.name = name
+
+    # Lock monitor
+    self.__monitor = monitor
+
+    # Used internally to guarantee coherency.
+    self.__lock = SharedLock(name)
+
+    # The lockdict indexes the relationship name -> lock
+    # The order-of-locking is implied by the alphabetical order of names
+    self.__lockdict = {}
+
+    for mname in members:
+      self.__lockdict[mname] = SharedLock(self._GetLockName(mname),
+                                          monitor=monitor)
+
+    # The owner dict contains the set of locks each thread owns. For
+    # performance each thread can access its own key without a global lock on
+    # this structure. It is paramount though that *no* other type of access is
+    # done to this structure (eg. no looping over its keys). *_owner helper
+    # function are defined to guarantee access is correct, but in general never
+    # do anything different than __owners[threading.currentThread()], or there
+    # will be trouble.
+    self.__owners = {}
+
+  def _GetLockName(self, mname):
+    """Returns the name for a member lock.
+
+    """
+    return "%s/%s" % (self.name, mname)
+
+  def _is_owned(self):
+    """Is the current thread a current level owner?"""
+    return threading.currentThread() in self.__owners
+
+  def _add_owned(self, name=None):
+    """Note the current thread owns the given lock"""
+    if name is None:
+      if not self._is_owned():
+        self.__owners[threading.currentThread()] = set()
+    else:
+      if self._is_owned():
+        self.__owners[threading.currentThread()].add(name)
       else:
-        assert False, "Cannot release non-owned lock"
+        self.__owners[threading.currentThread()] = set([name])
+
+  def _del_owned(self, name=None):
+    """Note the current thread owns the given lock"""
+
+    assert not (name is None and self.__lock._is_owned()), \
+           "Cannot hold internal lock when deleting owner status"
+
+    if name is not None:
+      self.__owners[threading.currentThread()].remove(name)
+
+    # Only remove the key if we don't hold the set-lock as well
+    if (not self.__lock._is_owned() and
+        not self.__owners[threading.currentThread()]):
+      del self.__owners[threading.currentThread()]
+
+  def _list_owned(self):
+    """Get the set of resource names owned by the current thread"""
+    if self._is_owned():
+      return self.__owners[threading.currentThread()].copy()
+    else:
+      return set()
+
+  def _release_and_delete_owned(self):
+    """Release and delete all resources owned by the current thread"""
+    for lname in self._list_owned():
+      lock = self.__lockdict[lname]
+      if lock._is_owned():
+        lock.release()
+      self._del_owned(name=lname)
+
+  def __names(self):
+    """Return the current set of names.
 
+    Only call this function while holding __lock and don't iterate on the
+    result after releasing the lock.
+
+    """
+    return self.__lockdict.keys()
+
+  def _names(self):
+    """Return a copy of the current set of elements.
+
+    Used only for debugging purposes.
+
+    """
+    # If we don't already own the set-level lock acquired
+    # we'll get it and note we need to release it later.
+    release_lock = False
+    if not self.__lock._is_owned():
+      release_lock = True
+      self.__lock.acquire(shared=1)
+    try:
+      result = self.__names()
     finally:
+      if release_lock:
+        self.__lock.release()
+    return set(result)
+
+  def acquire(self, names, timeout=None, shared=0, priority=None,
+              test_notify=None):
+    """Acquire a set of resource locks.
+
+    @type names: list of strings (or string)
+    @param names: the names of the locks which shall be acquired
+        (special lock names, or instance/node names)
+    @type shared: integer (0/1) used as a boolean
+    @param shared: whether to acquire in shared mode; by default an
+        exclusive lock will be acquired
+    @type timeout: float or None
+    @param timeout: Maximum time to acquire all locks
+    @type priority: integer
+    @param priority: Priority for acquiring locks
+    @type test_notify: callable or None
+    @param test_notify: Special callback function for unittesting
+
+    @return: Set of all locks successfully acquired or None in case of timeout
+
+    @raise errors.LockError: when any lock we try to acquire has
+        been deleted before we succeed. In this case none of the
+        locks requested will be acquired.
+
+    """
+    assert timeout is None or timeout >= 0.0
+
+    # Check we don't already own locks at this level
+    assert not self._is_owned(), ("Cannot acquire locks in the same set twice"
+                                  " (lockset %s)" % self.name)
+
+    if priority is None:
+      priority = _DEFAULT_PRIORITY
+
+    # We need to keep track of how long we spent waiting for a lock. The
+    # timeout passed to this function is over all lock acquires.
+    running_timeout = utils.RunningTimeout(timeout, False)
+
+    try:
+      if names is not None:
+        # Support passing in a single resource to acquire rather than many
+        if isinstance(names, basestring):
+          names = [names]
+
+        return self.__acquire_inner(names, False, shared, priority,
+                                    running_timeout.Remaining, test_notify)
+
+      else:
+        # If no names are given acquire the whole set by not letting new names
+        # being added before we release, and getting the current list of names.
+        # Some of them may then be deleted later, but we'll cope with this.
+        #
+        # We'd like to acquire this lock in a shared way, as it's nice if
+        # everybody else can use the instances at the same time. If we are
+        # acquiring them exclusively though they won't be able to do this
+        # anyway, though, so we'll get the list lock exclusively as well in
+        # order to be able to do add() on the set while owning it.
+        if not self.__lock.acquire(shared=shared, priority=priority,
+                                   timeout=running_timeout.Remaining()):
+          raise _AcquireTimeout()
+        try:
+          # note we own the set-lock
+          self._add_owned()
+
+          return self.__acquire_inner(self.__names(), True, shared, priority,
+                                      running_timeout.Remaining, test_notify)
+        except:
+          # We shouldn't have problems adding the lock to the owners list, but
+          # if we did we'll try to release this lock and re-raise exception.
+          # Of course something is going to be really wrong, after this.
+          self.__lock.release()
+          self._del_owned()
+          raise
+
+    except _AcquireTimeout:
+      return None
+
+  def __acquire_inner(self, names, want_all, shared, priority,
+                      timeout_fn, test_notify):
+    """Inner logic for acquiring a number of locks.
+
+    @param names: Names of the locks to be acquired
+    @param want_all: Whether all locks in the set should be acquired
+    @param shared: Whether to acquire in shared mode
+    @param timeout_fn: Function returning remaining timeout
+    @param priority: Priority for acquiring locks
+    @param test_notify: Special callback function for unittesting
+
+    """
+    acquire_list = []
+
+    # First we look the locks up on __lockdict. We have no way of being sure
+    # they will still be there after, but this makes it a lot faster should
+    # just one of them be the already wrong. Using a sorted sequence to prevent
+    # deadlocks.
+    for lname in sorted(utils.UniqueSequence(names)):
+      try:
+        lock = self.__lockdict[lname] # raises KeyError if lock is not there
+      except KeyError:
+        if want_all:
+          # We are acquiring all the set, it doesn't matter if this particular
+          # element is not there anymore.
+          continue
+
+        raise errors.LockError("Non-existing lock %s in set %s (it may have"
+                               " been removed)" % (lname, self.name))
+
+      acquire_list.append((lname, lock))
+
+    # This will hold the locknames we effectively acquired.
+    acquired = set()
+
+    try:
+      # Now acquire_list contains a sorted list of resources and locks we
+      # want.  In order to get them we loop on this (private) list and
+      # acquire() them.  We gave no real guarantee they will still exist till
+      # this is done but .acquire() itself is safe and will alert us if the
+      # lock gets deleted.
+      for (lname, lock) in acquire_list:
+        if __debug__ and callable(test_notify):
+          test_notify_fn = lambda: test_notify(lname)
+        else:
+          test_notify_fn = None
+
+        timeout = timeout_fn()
+
+        try:
+          # raises LockError if the lock was deleted
+          acq_success = lock.acquire(shared=shared, timeout=timeout,
+                                     priority=priority,
+                                     test_notify=test_notify_fn)
+        except errors.LockError:
+          if want_all:
+            # We are acquiring all the set, it doesn't matter if this
+            # particular element is not there anymore.
+            continue
+
+          raise errors.LockError("Non-existing lock %s in set %s (it may"
+                                 " have been removed)" % (lname, self.name))
+
+        if not acq_success:
+          # Couldn't get lock or timeout occurred
+          if timeout is None:
+            # This shouldn't happen as SharedLock.acquire(timeout=None) is
+            # blocking.
+            raise errors.LockError("Failed to get lock %s (set %s)" %
+                                   (lname, self.name))
+
+          raise _AcquireTimeout()
+
+        try:
+          # now the lock cannot be deleted, we have it!
+          self._add_owned(name=lname)
+          acquired.add(lname)
+
+        except:
+          # We shouldn't have problems adding the lock to the owners list, but
+          # if we did we'll try to release this lock and re-raise exception.
+          # Of course something is going to be really wrong after this.
+          if lock._is_owned():
+            lock.release()
+          raise
+
+    except:
+      # Release all owned locks
+      self._release_and_delete_owned()
+      raise
+
+    return acquired
+
+  def release(self, names=None):
+    """Release a set of resource locks, at the same level.
+
+    You must have acquired the locks, either in shared or in exclusive mode,
+    before releasing them.
+
+    @type names: list of strings, or None
+    @param names: the names of the locks which shall be released
+        (defaults to all the locks acquired at that level).
+
+    """
+    assert self._is_owned(), ("release() on lock set %s while not owner" %
+                              self.name)
+
+    # Support passing in a single resource to release rather than many
+    if isinstance(names, basestring):
+      names = [names]
+
+    if names is None:
+      names = self._list_owned()
+    else:
+      names = set(names)
+      assert self._list_owned().issuperset(names), (
+               "release() on unheld resources %s (set %s)" %
+               (names.difference(self._list_owned()), self.name))
+
+    # First of all let's release the "all elements" lock, if set.
+    # After this 'add' can work again
+    if self.__lock._is_owned():
       self.__lock.release()
+      self._del_owned()
+
+    for lockname in names:
+      # If we are sure the lock doesn't leave __lockdict without being
+      # exclusively held we can do this...
+      self.__lockdict[lockname].release()
+      self._del_owned(name=lockname)
+
+  def add(self, names, acquired=0, shared=0):
+    """Add a new set of elements to the set
+
+    @type names: list of strings
+    @param names: names of the new elements to add
+    @type acquired: integer (0/1) used as a boolean
+    @param acquired: pre-acquire the new resource?
+    @type shared: integer (0/1) used as a boolean
+    @param shared: is the pre-acquisition shared?
+
+    """
+    # Check we don't already own locks at this level
+    assert not self._is_owned() or self.__lock._is_owned(shared=0), \
+      ("Cannot add locks if the set %s is only partially owned, or shared" %
+       self.name)
+
+    # Support passing in a single resource to add rather than many
+    if isinstance(names, basestring):
+      names = [names]
+
+    # If we don't already own the set-level lock acquired in an exclusive way
+    # we'll get it and note we need to release it later.
+    release_lock = False
+    if not self.__lock._is_owned():
+      release_lock = True
+      self.__lock.acquire()
+
+    try:
+      invalid_names = set(self.__names()).intersection(names)
+      if invalid_names:
+        # This must be an explicit raise, not an assert, because assert is
+        # turned off when using optimization, and this can happen because of
+        # concurrency even if the user doesn't want it.
+        raise errors.LockError("duplicate add(%s) on lockset %s" %
+                               (invalid_names, self.name))
+
+      for lockname in names:
+        lock = SharedLock(self._GetLockName(lockname), monitor=self.__monitor)
+
+        if acquired:
+          # No need for priority or timeout here as this lock has just been
+          # created
+          lock.acquire(shared=shared)
+          # now the lock cannot be deleted, we have it!
+          try:
+            self._add_owned(name=lockname)
+          except:
+            # We shouldn't have problems adding the lock to the owners list,
+            # but if we did we'll try to release this lock and re-raise
+            # exception.  Of course something is going to be really wrong,
+            # after this.  On the other hand the lock hasn't been added to the
+            # __lockdict yet so no other threads should be pending on it. This
+            # release is just a safety measure.
+            lock.release()
+            raise
+
+        self.__lockdict[lockname] = lock
+
+    finally:
+      # Only release __lock if we were not holding it previously.
+      if release_lock:
+        self.__lock.release()
+
+    return True
+
+  def remove(self, names):
+    """Remove elements from the lock set.
+
+    You can either not hold anything in the lockset or already hold a superset
+    of the elements you want to delete, exclusively.
+
+    @type names: list of strings
+    @param names: names of the resource to remove.
+
+    @return: a list of locks which we removed; the list is always
+        equal to the names list if we were holding all the locks
+        exclusively
+
+    """
+    # Support passing in a single resource to remove rather than many
+    if isinstance(names, basestring):
+      names = [names]
+
+    # If we own any subset of this lock it must be a superset of what we want
+    # to delete. The ownership must also be exclusive, but that will be checked
+    # by the lock itself.
+    assert not self._is_owned() or self._list_owned().issuperset(names), (
+      "remove() on acquired lockset %s while not owning all elements" %
+      self.name)
+
+    removed = []
+
+    for lname in names:
+      # Calling delete() acquires the lock exclusively if we don't already own
+      # it, and causes all pending and subsequent lock acquires to fail. It's
+      # fine to call it out of order because delete() also implies release(),
+      # and the assertion above guarantees that if we either already hold
+      # everything we want to delete, or we hold none.
+      try:
+        self.__lockdict[lname].delete()
+        removed.append(lname)
+      except (KeyError, errors.LockError):
+        # This cannot happen if we were already holding it, verify:
+        assert not self._is_owned(), ("remove failed while holding lockset %s"
+                                      % self.name)
+      else:
+        # If no LockError was raised we are the ones who deleted the lock.
+        # This means we can safely remove it from lockdict, as any further or
+        # pending delete() or acquire() will fail (and nobody can have the lock
+        # since before our call to delete()).
+        #
+        # This is done in an else clause because if the exception was thrown
+        # it's the job of the one who actually deleted it.
+        del self.__lockdict[lname]
+        # And let's remove it from our private list if we owned it.
+        if self._is_owned():
+          self._del_owned(name=lname)
+
+    return removed
+
+
+# Locking levels, must be acquired in increasing order.
+# Current rules are:
+#   - at level LEVEL_CLUSTER resides the Big Ganeti Lock (BGL) which must be
+#   acquired before performing any operation, either in shared or in exclusive
+#   mode. acquiring the BGL in exclusive mode is discouraged and should be
+#   avoided.
+#   - at levels LEVEL_NODE and LEVEL_INSTANCE reside node and instance locks.
+#   If you need more than one node, or more than one instance, acquire them at
+#   the same time.
+LEVEL_CLUSTER = 0
+LEVEL_INSTANCE = 1
+LEVEL_NODEGROUP = 2
+LEVEL_NODE = 3
+
+LEVELS = [LEVEL_CLUSTER,
+          LEVEL_INSTANCE,
+          LEVEL_NODEGROUP,
+          LEVEL_NODE]
+
+# Lock levels which are modifiable
+LEVELS_MOD = [LEVEL_NODE, LEVEL_NODEGROUP, LEVEL_INSTANCE]
+
+LEVEL_NAMES = {
+  LEVEL_CLUSTER: "cluster",
+  LEVEL_INSTANCE: "instance",
+  LEVEL_NODEGROUP: "nodegroup",
+  LEVEL_NODE: "node",
+  }
+
+# Constant for the big ganeti lock
+BGL = 'BGL'
+
+
+class GanetiLockManager:
+  """The Ganeti Locking Library
+
+  The purpose of this small library is to manage locking for ganeti clusters
+  in a central place, while at the same time doing dynamic checks against
+  possible deadlocks. It will also make it easier to transition to a different
+  lock type should we migrate away from python threads.
+
+  """
+  _instance = None
+
+  def __init__(self, nodes, nodegroups, instances):
+    """Constructs a new GanetiLockManager object.
+
+    There should be only a GanetiLockManager object at any time, so this
+    function raises an error if this is not the case.
+
+    @param nodes: list of node names
+    @param nodegroups: list of nodegroup uuids
+    @param instances: list of instance names
+
+    """
+    assert self.__class__._instance is None, \
+           "double GanetiLockManager instance"
+
+    self.__class__._instance = self
+
+    self._monitor = LockMonitor()
+
+    # The keyring contains all the locks, at their level and in the correct
+    # locking order.
+    self.__keyring = {
+      LEVEL_CLUSTER: LockSet([BGL], "BGL", monitor=self._monitor),
+      LEVEL_NODE: LockSet(nodes, "nodes", monitor=self._monitor),
+      LEVEL_NODEGROUP: LockSet(nodegroups, "nodegroups", monitor=self._monitor),
+      LEVEL_INSTANCE: LockSet(instances, "instances",
+                              monitor=self._monitor),
+      }
+
+  def QueryLocks(self, fields):
+    """Queries information from all locks.
+
+    See L{LockMonitor.QueryLocks}.
+
+    """
+    return self._monitor.QueryLocks(fields)
+
+  def OldStyleQueryLocks(self, fields):
+    """Queries information from all locks, returning old-style data.
+
+    See L{LockMonitor.OldStyleQueryLocks}.
+
+    """
+    return self._monitor.OldStyleQueryLocks(fields)
+
+  def _names(self, level):
+    """List the lock names at the given level.
+
+    This can be used for debugging/testing purposes.
+
+    @param level: the level whose list of locks to get
+
+    """
+    assert level in LEVELS, "Invalid locking level %s" % level
+    return self.__keyring[level]._names()
+
+  def _is_owned(self, level):
+    """Check whether we are owning locks at the given level
+
+    """
+    return self.__keyring[level]._is_owned()
+
+  is_owned = _is_owned
+
+  def _list_owned(self, level):
+    """Get the set of owned locks at the given level
+
+    """
+    return self.__keyring[level]._list_owned()
+
+  def _upper_owned(self, level):
+    """Check that we don't own any lock at a level greater than the given one.
+
+    """
+    # This way of checking only works if LEVELS[i] = i, which we check for in
+    # the test cases.
+    return compat.any((self._is_owned(l) for l in LEVELS[level + 1:]))
+
+  def _BGL_owned(self): # pylint: disable-msg=C0103
+    """Check if the current thread owns the BGL.
+
+    Both an exclusive or a shared acquisition work.
+
+    """
+    return BGL in self.__keyring[LEVEL_CLUSTER]._list_owned()
+
+  @staticmethod
+  def _contains_BGL(level, names): # pylint: disable-msg=C0103
+    """Check if the level contains the BGL.
+
+    Check if acting on the given level and set of names will change
+    the status of the Big Ganeti Lock.
+
+    """
+    return level == LEVEL_CLUSTER and (names is None or BGL in names)
+
+  def acquire(self, level, names, timeout=None, shared=0, priority=None):
+    """Acquire a set of resource locks, at the same level.
+
+    @type level: member of locking.LEVELS
+    @param level: the level at which the locks shall be acquired
+    @type names: list of strings (or string)
+    @param names: the names of the locks which shall be acquired
+        (special lock names, or instance/node names)
+    @type shared: integer (0/1) used as a boolean
+    @param shared: whether to acquire in shared mode; by default
+        an exclusive lock will be acquired
+    @type timeout: float
+    @param timeout: Maximum time to acquire all locks
+    @type priority: integer
+    @param priority: Priority for acquiring lock
+
+    """
+    assert level in LEVELS, "Invalid locking level %s" % level
+
+    # Check that we are either acquiring the Big Ganeti Lock or we already own
+    # it. Some "legacy" opcodes need to be sure they are run non-concurrently
+    # so even if we've migrated we need to at least share the BGL to be
+    # compatible with them. Of course if we own the BGL exclusively there's no
+    # point in acquiring any other lock, unless perhaps we are half way through
+    # the migration of the current opcode.
+    assert (self._contains_BGL(level, names) or self._BGL_owned()), (
+            "You must own the Big Ganeti Lock before acquiring any other")
+
+    # Check we don't own locks at the same or upper levels.
+    assert not self._upper_owned(level), ("Cannot acquire locks at a level"
+           " while owning some at a greater one")
+
+    # Acquire the locks in the set.
+    return self.__keyring[level].acquire(names, shared=shared, timeout=timeout,
+                                         priority=priority)
+
+  def release(self, level, names=None):
+    """Release a set of resource locks, at the same level.
+
+    You must have acquired the locks, either in shared or in exclusive
+    mode, before releasing them.
+
+    @type level: member of locking.LEVELS
+    @param level: the level at which the locks shall be released
+    @type names: list of strings, or None
+    @param names: the names of the locks which shall be released
+        (defaults to all the locks acquired at that level)
+
+    """
+    assert level in LEVELS, "Invalid locking level %s" % level
+    assert (not self._contains_BGL(level, names) or
+            not self._upper_owned(LEVEL_CLUSTER)), (
+            "Cannot release the Big Ganeti Lock while holding something"
+            " at upper levels (%r)" %
+            (utils.CommaJoin(["%s=%r" % (LEVEL_NAMES[i], self._list_owned(i))
+                              for i in self.__keyring.keys()]), ))
+
+    # Release will complain if we don't own the locks already
+    return self.__keyring[level].release(names)
+
+  def add(self, level, names, acquired=0, shared=0):
+    """Add locks at the specified level.
+
+    @type level: member of locking.LEVELS_MOD
+    @param level: the level at which the locks shall be added
+    @type names: list of strings
+    @param names: names of the locks to acquire
+    @type acquired: integer (0/1) used as a boolean
+    @param acquired: whether to acquire the newly added locks
+    @type shared: integer (0/1) used as a boolean
+    @param shared: whether the acquisition will be shared
+
+    """
+    assert level in LEVELS_MOD, "Invalid or immutable level %s" % level
+    assert self._BGL_owned(), ("You must own the BGL before performing other"
+           " operations")
+    assert not self._upper_owned(level), ("Cannot add locks at a level"
+           " while owning some at a greater one")
+    return self.__keyring[level].add(names, acquired=acquired, shared=shared)
+
+  def remove(self, level, names):
+    """Remove locks from the specified level.
+
+    You must either already own the locks you are trying to remove
+    exclusively or not own any lock at an upper level.
+
+    @type level: member of locking.LEVELS_MOD
+    @param level: the level at which the locks shall be removed
+    @type names: list of strings
+    @param names: the names of the locks which shall be removed
+        (special lock names, or instance/node names)
+
+    """
+    assert level in LEVELS_MOD, "Invalid or immutable level %s" % level
+    assert self._BGL_owned(), ("You must own the BGL before performing other"
+           " operations")
+    # Check we either own the level or don't own anything from here
+    # up. LockSet.remove() will check the case in which we don't own
+    # all the needed resources, or we have a shared ownership.
+    assert self._is_owned(level) or not self._upper_owned(level), (
+           "Cannot remove locks at a level while not owning it or"
+           " owning some at a greater one")
+    return self.__keyring[level].remove(names)
+
+
+class LockMonitor(object):
+  _LOCK_ATTR = "_lock"
+
+  def __init__(self):
+    """Initializes this class.
+
+    """
+    self._lock = SharedLock("LockMonitor")
+
+    # Tracked locks. Weak references are used to avoid issues with circular
+    # references and deletion.
+    self._locks = weakref.WeakKeyDictionary()
+
+  @ssynchronized(_LOCK_ATTR)
+  def RegisterLock(self, lock):
+    """Registers a new lock.
+
+    """
+    logging.debug("Registering lock %s", lock.name)
+    assert lock not in self._locks, "Duplicate lock registration"
+    assert not compat.any(lock.name == i.name for i in self._locks.keys()), \
+           "Found duplicate lock name"
+    self._locks[lock] = None
+
+  @ssynchronized(_LOCK_ATTR)
+  def _GetLockInfo(self, requested):
+    """Get information from all locks while the monitor lock is held.
+
+    """
+    return [lock.GetInfo(requested) for lock in self._locks.keys()]
+
+  def _Query(self, fields):
+    """Queries information from all locks.
+
+    @type fields: list of strings
+    @param fields: List of fields to return
+
+    """
+    qobj = query.Query(query.LOCK_FIELDS, fields)
+
+    # Get all data and sort by name
+    lockinfo = utils.NiceSort(self._GetLockInfo(qobj.RequestedData()),
+                              key=operator.itemgetter(0))
+
+    return (qobj, query.LockQueryData(lockinfo))
+
+  def QueryLocks(self, fields):
+    """Queries information from all locks.
+
+    @type fields: list of strings
+    @param fields: List of fields to return
+
+    """
+    (qobj, ctx) = self._Query(fields)
+
+    # Prepare query response
+    return query.GetQueryResponse(qobj, ctx)
+
+  def OldStyleQueryLocks(self, fields):
+    """Queries information from all locks, returning old-style data.
+
+    @type fields: list of strings
+    @param fields: List of fields to return
+
+    """
+    (qobj, ctx) = self._Query(fields)
 
+    return qobj.OldStyleQuery(ctx)