diff options
Diffstat (limited to 'designate/common/decorators/lock.py')
-rw-r--r-- | designate/common/decorators/lock.py | 107 |
1 files changed, 107 insertions, 0 deletions
diff --git a/designate/common/decorators/lock.py b/designate/common/decorators/lock.py new file mode 100644 index 00000000..f633fa4d --- /dev/null +++ b/designate/common/decorators/lock.py @@ -0,0 +1,107 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import functools +import itertools +import threading + +from oslo_log import log as logging + +from designate import objects + +LOG = logging.getLogger(__name__) + + +class ZoneLockLocal(threading.local): + def __init__(self): + super(ZoneLockLocal, self).__init__() + self._held = set() + + def hold(self, name): + self._held.add(name) + + def release(self, name): + self._held.remove(name) + + def has_lock(self, name): + return name in self._held + + +def extract_zone_id(args, kwargs): + zone_id = None + + if 'zone_id' in kwargs: + zone_id = kwargs['zone_id'] + elif 'zone' in kwargs: + zone_id = kwargs['zone'].id + elif 'recordset' in kwargs: + zone_id = kwargs['recordset'].zone_id + elif 'record' in kwargs: + zone_id = kwargs['record'].zone_id + + if not zone_id: + for arg in itertools.chain(args, kwargs.values()): + if not isinstance(arg, objects.DesignateObject): + continue + if isinstance(arg, objects.Zone): + zone_id = arg.id + if zone_id: + break + elif isinstance(arg, (objects.RecordSet, + objects.Record, + objects.ZoneTransferRequest, + objects.ZoneTransferAccept)): + zone_id = arg.zone_id + if zone_id: + break + + if not zone_id and len(args) > 1: + arg = args[1] + if isinstance(arg, str): + zone_id = arg + elif isinstance(zone_id, objects.Zone): + zone_id = arg.id + + return zone_id + + +def synchronized_zone(new_zone=False): + """Ensures only a single operation is in progress for each zone + + A Decorator which ensures only a single operation can be happening + on a single zone at once, within the current designate-central instance + """ + def outer(f): + @functools.wraps(f) + def sync_wrapper(cls, *args, **kwargs): + if new_zone is True: + lock_name = 'create-new-zone' + else: + zone_id = extract_zone_id(args, kwargs) + if zone_id: + lock_name = 'zone-%s' % zone_id + else: + raise Exception('Failed to determine zone id for ' + 'synchronized operation') + + if cls.zone_lock_local.has_lock(lock_name): + return f(cls, *args, **kwargs) + + with cls.coordination.get_lock(lock_name): + try: + cls.zone_lock_local.hold(lock_name) + return f(cls, *args, **kwargs) + finally: + cls.zone_lock_local.release(lock_name) + + return sync_wrapper + return outer |