diff --git a/lib/locking.py b/lib/locking.py
index c230be80315fdf2e50c5f3f125ff1e06e8e96061..7be15519ff0cebf28954391316fa0cb581c8ebb1 100644
--- a/lib/locking.py
+++ b/lib/locking.py
@@ -29,6 +29,25 @@ from ganeti import errors
 from ganeti import utils
 
 
+def ssynchronized(lock, 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).
+
+  """
+  def wrap(fn):
+    def sync_function(*args, **kwargs):
+      lock.acquire(shared=shared)
+      try:
+        return fn(*args, **kwargs)
+      finally:
+        lock.release()
+    return sync_function
+  return wrap
+
+
 class SharedLock:
   """Implements a shared lock.
 
diff --git a/test/ganeti.locking_unittest.py b/test/ganeti.locking_unittest.py
index e254e85b75620745448f0934aca3c8c099963e3b..41ec35444c1f7611ffe0e4e35a7ac9f9d05633e1 100755
--- a/test/ganeti.locking_unittest.py
+++ b/test/ganeti.locking_unittest.py
@@ -32,6 +32,11 @@ from ganeti import errors
 from threading import Thread
 
 
+# This is used to test the ssynchronize decorator.
+# Since it's passed as input to a decorator it must be declared as a global.
+_decoratorlock = locking.SharedLock()
+
+
 class TestSharedLock(unittest.TestCase):
   """SharedLock tests"""
 
@@ -230,6 +235,60 @@ class TestSharedLock(unittest.TestCase):
     self.assertEqual(self.done.get(True, 1), 'ERR')
 
 
+class TestSSynchronizedDecorator(unittest.TestCase):
+  """Shared Lock Synchronized decorator test"""
+
+  def setUp(self):
+    # helper threads use the 'done' queue to tell the master they finished.
+    self.done = Queue.Queue(0)
+
+  @locking.ssynchronized(_decoratorlock)
+  def _doItExclusive(self):
+    self.assert_(_decoratorlock._is_owned())
+    self.done.put('EXC')
+
+  @locking.ssynchronized(_decoratorlock, shared=1)
+  def _doItSharer(self):
+    self.assert_(_decoratorlock._is_owned(shared=1))
+    self.done.put('SHR')
+
+  def testDecoratedFunctions(self):
+    self._doItExclusive()
+    self.assert_(not _decoratorlock._is_owned())
+    self._doItSharer()
+    self.assert_(not _decoratorlock._is_owned())
+
+  def testSharersCanCoexist(self):
+    _decoratorlock.acquire(shared=1)
+    Thread(target=self._doItSharer).start()
+    self.assert_(self.done.get(True, 1))
+    _decoratorlock.release()
+
+  def testExclusiveBlocksExclusive(self):
+    _decoratorlock.acquire()
+    Thread(target=self._doItExclusive).start()
+    # give it a bit of time to check that it's not actually doing anything
+    self.assertRaises(Queue.Empty, self.done.get, True, 0.2)
+    _decoratorlock.release()
+    self.assert_(self.done.get(True, 1))
+
+  def testExclusiveBlocksSharer(self):
+    _decoratorlock.acquire()
+    Thread(target=self._doItSharer).start()
+    time.sleep(0.05)
+    self.assertRaises(Queue.Empty, self.done.get, True, 0.2)
+    _decoratorlock.release()
+    self.assert_(self.done.get(True, 1))
+
+  def testSharerBlocksExclusive(self):
+    _decoratorlock.acquire(shared=1)
+    Thread(target=self._doItExclusive).start()
+    time.sleep(0.05)
+    self.assertRaises(Queue.Empty, self.done.get, True, 0.2)
+    _decoratorlock.release()
+    self.assert_(self.done.get(True, 1))
+
+
 class TestLockSet(unittest.TestCase):
   """LockSet tests"""