PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /proc/self/root/opt/saltstack/salt/extras-3.10/pyroute2/ipdb/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //proc/self/root/opt/saltstack/salt/extras-3.10/pyroute2/ipdb/transactional.py |
''' ''' import logging import threading from pyroute2.common import Dotkeys, uuid32 from pyroute2.ipdb.exceptions import CommitException from pyroute2.ipdb.linkedset import LinkedSet # How long should we wait on EACH commit() checkpoint: for ipaddr, # ports etc. That's not total commit() timeout. SYNC_TIMEOUT = 5 log = logging.getLogger(__name__) class State(object): def __init__(self, lock=None): self.lock = lock or threading.Lock() self.flag = 0 def acquire(self): self.lock.acquire() self.flag += 1 def release(self): if self.flag < 1: raise RuntimeError('release unlocked state') self.flag -= 1 self.lock.release() def is_set(self): return self.flag def __enter__(self): self.acquire() return self def __exit__(self, exc_type, exc_value, traceback): self.release() def update(f): def decorated(self, *argv, **kwarg): if self._mode == 'snapshot': # short-circuit with self._write_lock: return f(self, True, *argv, **kwarg) elif self._mode == 'readonly': raise RuntimeError('can not change readonly object') with self._write_lock: direct = self._direct_state.is_set() if not direct: # 1. 'implicit': begin transaction, if there is none if self._mode == 'implicit': if not self.current_tx: self.begin() # 2. require open transaction for 'explicit' type elif self._mode == 'explicit': if not self.current_tx: raise TypeError('start a transaction first') # do not support other modes else: raise TypeError('transaction mode not supported') # now that the transaction _is_ open return f(self, direct, *argv, **kwarg) decorated.__doc__ = f.__doc__ return decorated def with_transaction(f): def decorated(self, direct, *argv, **kwarg): if direct: f(self, *argv, **kwarg) else: transaction = self.current_tx f(transaction, *argv, **kwarg) return self return update(decorated) class Transactional(Dotkeys): ''' Utility class that implements common transactional logic. ''' _fields = [] _virtual_fields = [] _fields_cmp = {} _linked_sets = [] _nested = [] def __init__(self, ipdb=None, mode=None, parent=None, uid=None): # if ipdb is not None: self.nl = ipdb.nl self.ipdb = ipdb else: self.nl = None self.ipdb = None # self._parent = None if parent is not None: self._mode = mode or parent._mode self._parent = parent elif ipdb is not None: self._mode = mode or ipdb.mode else: self._mode = mode or 'implicit' # self.nlmsg = None self.uid = uid or uuid32() self.last_error = None self._commit_hooks = [] self._sids = [] self._ts = threading.local() self._snapshots = {} self.global_tx = {} self._targets = {} self._local_targets = {} self._write_lock = threading.RLock() self._direct_state = State(self._write_lock) self._linked_sets = self._linked_sets or set() # for i in self._fields: Dotkeys.__setitem__(self, i, None) @property def ro(self): return self.pick(detached=False, readonly=True) def register_commit_hook(self, hook): ''' ''' self._commit_hooks.append(hook) def unregister_commit_hook(self, hook): ''' ''' with self._write_lock: for cb in tuple(self._commit_hooks): if hook == cb: self._commit_hooks.pop(self._commit_hooks.index(cb)) ## # Object serialization: dump, pick def dump(self, not_none=True): ''' ''' with self._write_lock: res = {} for key in self: if self[key] is not None and key[0] != '_': if isinstance(self[key], Transactional): res[key] = self[key].dump() elif isinstance(self[key], LinkedSet): res[key] = tuple(self[key]) else: res[key] = self[key] return res def pick(self, detached=True, uid=None, parent=None, readonly=False): ''' Get a snapshot of the object. Can be of two types: * detached=True -- (default) "true" snapshot * detached=False -- keep ip addr set updated from OS Please note, that "updated" doesn't mean "in sync". The reason behind this logic is that snapshots can be used as transactions. ''' with self._write_lock: res = self.__class__( ipdb=self.ipdb, mode='snapshot', parent=parent, uid=uid ) for key, value in self.items(): if self[key] is not None: if key in self._fields: res[key] = self[key] for key in self._linked_sets: res[key] = type(self[key])(self[key]) if not detached: self[key].connect(res[key]) if readonly: res._mode = 'readonly' return res ## # Context management: enter, exit def __enter__(self): if self._mode == 'readonly': return self elif self._mode not in ('implicit', 'explicit'): raise TypeError('context managers require a transactional mode') if not self.current_tx: self.begin() return self def __exit__(self, exc_type, exc_value, traceback): # apply transaction only if there was no error if self._mode == 'readonly': return elif exc_type is None: try: self.commit() except Exception as e: self.last_error = e raise ## # Implicit object transfomations def __repr__(self): res = {} for i in tuple(self): if self[i] is not None: res[i] = self[i] return res.__repr__() ## # Object ops: +, -, /, ... def __sub__(self, vs): # create result res = {} with self._direct_state: # simple keys for key in self: if key in self._fields: if (key not in vs) or (self[key] != vs[key]): res[key] = self[key] for key in self._linked_sets: diff = type(self[key])(self[key] - vs[key]) if diff: res[key] = diff else: res[key] = set() for key in self._nested: res[key] = self[key] - vs[key] return res def __floordiv__(self, vs): left = {} right = {} with self._direct_state: with vs._direct_state: for key in set(tuple(self.keys()) + tuple(vs.keys())): if self.get(key, None) != vs.get(key, None): left[key] = self.get(key) right[key] = vs.get(key) continue if key not in self: right[key] = vs[key] elif key not in vs: left[key] = self[key] for key in self._linked_sets: ldiff = type(self[key])(self[key] - vs[key]) rdiff = type(vs[key])(vs[key] - self[key]) if ldiff: left[key] = ldiff else: left[key] = set() if rdiff: right[key] = rdiff else: right[key] = set() for key in self._nested: left[key], right[key] = self[key] // vs[key] return left, right ## # Methods to be overloaded def detach(self): pass def load(self, data): pass def commit(self, *args, **kwarg): pass def last_snapshot_id(self): return self._sids[-1] def invalidate(self): # on failure, invalidate the interface and detach it # from the parent # 0. obtain lock on IPDB, to avoid deadlocks # ... all the DB updates will wait with self.ipdb.exclusive: # 1. drop the IPRoute() link self.nl = None # 2. clean up ipdb self.detach() # 3. invalidate the interface with self._direct_state: for i in tuple(self.keys()): del self[i] self['ipdb_scope'] = 'invalid' # 4. the rest self._mode = 'invalid' ## # Snapshot methods def revert(self, sid): with self._write_lock: assert sid in self._snapshots self.local_tx[sid] = self._snapshots[sid] self.global_tx[sid] = self._snapshots[sid] self.current_tx = self._snapshots[sid] self._sids.remove(sid) del self._snapshots[sid] return self def snapshot(self, sid=None): ''' Create new snapshot ''' if self._parent: raise RuntimeError("Can't init snapshot from a nested object") if (self.ipdb is not None) and self.ipdb._stop: raise RuntimeError("Can't create snapshots on released IPDB") t = self.pick(detached=True, uid=sid) self._snapshots[t.uid] = t self._sids.append(t.uid) for key, value in t.items(): if isinstance(value, Transactional): value.snapshot(sid=t.uid) return t.uid def last_snapshot(self): if not self._sids: raise TypeError('create a snapshot first') return self._snapshots[self._sids[-1]] ## # Current tx def _set_current_tx(self, tx): with self._write_lock: self._ts.current = tx def _get_current_tx(self): ''' The current active transaction (thread-local) ''' with self._write_lock: if not hasattr(self._ts, 'current'): self._ts.current = None return self._ts.current current_tx = property(_get_current_tx, _set_current_tx) ## # Local tx registry def _get_local_tx(self): with self._write_lock: if not hasattr(self._ts, 'tx'): self._ts.tx = {} return self._ts.tx local_tx = property(_get_local_tx) ## # Transaction ops: begin, review, drop def begin(self): ''' Start new transaction ''' if self._parent is not None: self._parent.begin() else: return self._begin() def _begin(self, tid=None): if (self.ipdb is not None) and self.ipdb._stop: raise RuntimeError("Can't start transaction on released IPDB") t = self.pick(detached=False, uid=tid) self.local_tx[t.uid] = t self.global_tx[t.uid] = t if self.current_tx is None: self.current_tx = t for key, value in t.items(): if isinstance(value, Transactional): # start transaction on a nested object value._begin(tid=t.uid) # link transaction to own one t[key] = value.global_tx[t.uid] return t.uid def review(self, tid=None): ''' Review the changes made in the transaction `tid` or in the current active transaction (thread-local) ''' if self.current_tx is None: raise TypeError('start a transaction first') tid = tid or self.current_tx.uid if self.get('ipdb_scope') == 'create': if self.current_tx is not None: prime = self.current_tx else: log.warning('the "create" scope without transaction') prime = self return dict( [(x[0], x[1]) for x in prime.items() if x[1] is not None] ) with self._write_lock: added = self.global_tx[tid] - self removed = self - self.global_tx[tid] for key in self._linked_sets: added['-%s' % (key)] = removed[key] added['+%s' % (key)] = added[key] del added[key] return added def drop(self, tid=None): ''' Drop a transaction. If tid is not specified, drop the current one. ''' with self._write_lock: if tid is None: tx = self.current_tx if tx is None: raise TypeError("no transaction") else: tx = self.global_tx[tid] if self.current_tx == tx: self.current_tx = None # detach linked sets for key in self._linked_sets: if tx[key] in self[key].links: self[key].disconnect(tx[key]) for key, value in self.items(): if isinstance(value, Transactional): try: value.drop(tx.uid) except KeyError: pass # finally -- delete the transaction del self.local_tx[tx.uid] del self.global_tx[tx.uid] ## # Property ops: set/get/delete @update def __setitem__(self, direct, key, value): if not direct: if self.get(key) == value: return # automatically set target on the active transaction, # which must be started prior to that call transaction = self.current_tx transaction[key] = value if value is not None: transaction._targets[key] = threading.Event() else: # set the item Dotkeys.__setitem__(self, key, value) # update on local targets with self._write_lock: if key in self._local_targets: func = self._fields_cmp.get(key, lambda x, y: x == y) if func(value, self._local_targets[key].value): self._local_targets[key].set() # cascade update on nested targets for tn in tuple(self.global_tx.values()): if (key in tn._targets) and (key in tn): if self._fields_cmp.get(key, lambda x, y: x == y)( value, tn[key] ): tn._targets[key].set() @update def __delitem__(self, direct, key): # firstly set targets self[key] = None # then continue with delete if not direct: transaction = self.current_tx if key in transaction: del transaction[key] else: Dotkeys.__delitem__(self, key) def option(self, key, value): self[key] = value return self def unset(self, key): del self[key] return self def wait_all_targets(self): for key, target in self._targets.items(): if key not in self._virtual_fields: target.wait(SYNC_TIMEOUT) if not target.is_set(): raise CommitException('target %s is not set' % key) def wait_target(self, key, timeout=SYNC_TIMEOUT): self._local_targets[key].wait(SYNC_TIMEOUT) with self._write_lock: return self._local_targets.pop(key).is_set() def set_target(self, key, value): with self._write_lock: self._local_targets[key] = threading.Event() self._local_targets[key].value = value if self.get(key) == value: self._local_targets[key].set() return self def mirror_target(self, key_from, key_to): with self._write_lock: self._local_targets[key_to] = self._local_targets[key_from] return self def set(self, key, value): self[key] = value return self