EVOLUTION-MANAGER
Edit File: route.py
import types import logging import threading from collections import namedtuple from socket import AF_UNSPEC from socket import AF_INET6 from socket import AF_INET from pyroute2.common import AF_MPLS from pyroute2.common import basestring from pyroute2.netlink import rtnl from pyroute2.netlink import nlmsg from pyroute2.netlink import nlmsg_base from pyroute2.netlink.rtnl import rt_type from pyroute2.netlink.rtnl import rt_proto from pyroute2.netlink.rtnl import encap_type from pyroute2.netlink.rtnl.rtmsg import rtmsg from pyroute2.netlink.rtnl.req import IPRouteRequest from pyroute2.ipdb.exceptions import CommitException from pyroute2.ipdb.transactional import Transactional from pyroute2.ipdb.transactional import with_transaction from pyroute2.ipdb.transactional import SYNC_TIMEOUT from pyroute2.ipdb.linkedset import LinkedSet log = logging.getLogger(__name__) groups = rtnl.RTNLGRP_IPV4_ROUTE |\ rtnl.RTNLGRP_IPV6_ROUTE |\ rtnl.RTNLGRP_MPLS_ROUTE class Metrics(Transactional): _fields = [rtmsg.metrics.nla2name(i[0]) for i in rtmsg.metrics.nla_map] class Encap(Transactional): _fields = ['type', 'labels'] class Via(Transactional): _fields = ['family', 'addr'] class NextHopSet(LinkedSet): def __init__(self, prime=None): super(NextHopSet, self).__init__() prime = prime or [] for v in prime: self.add(v) def __sub__(self, vs): ret = type(self)() sub = set(self.raw.keys()) - set(vs.raw.keys()) for v in sub: ret.add(self[v], raw=self.raw[v]) return ret def __make_nh(self, prime): if isinstance(prime, BaseRoute): return prime.make_nh_key(prime) elif isinstance(prime, dict): if prime.get('family', None) == AF_MPLS: return MPLSRoute.make_nh_key(prime) else: return Route.make_nh_key(prime) elif isinstance(prime, tuple): return prime else: raise TypeError("unknown prime type %s" % type(prime)) def __getitem__(self, key): return self.raw[key] def __iter__(self): def NHIterator(): for x in tuple(self.raw.values()): yield x return NHIterator() def add(self, prime, raw=None, cascade=False): key = self.__make_nh(prime) r = key._required l = key._fields skey = key[:r] + (None, ) * (len(l) - r) if skey in self.raw: del self.raw[skey] return super(NextHopSet, self).add(key, raw=prime) def remove(self, prime, raw=None, cascade=False): key = self.__make_nh(prime) try: super(NextHopSet, self).remove(key) except KeyError as e: r = key._required l = key._fields skey = key[:r] + (None, ) * (len(l) - r) for rkey in tuple(self.raw.keys()): if skey == rkey[:r] + (None, ) * (len(l) - r): break else: raise e super(NextHopSet, self).remove(rkey) class WatchdogMPLSKey(dict): def __init__(self, route): dict.__init__(self) self['oif'] = route['oif'] self['dst'] = [{'ttl': 0, 'bos': 1, 'tc': 0, 'label': route['dst']}] class WatchdogKey(dict): ''' Construct from a route a dictionary that could be used as a match for IPDB watchdogs. ''' def __init__(self, route): dict.__init__(self, [x for x in IPRouteRequest(route).items() if x[0] in ('dst', 'dst_len', 'src', 'src_len', 'oif', 'iif', 'gateway', 'table') and x[1]]) # Universal route key RouteKey = namedtuple('RouteKey', ('dst', 'priority')) RouteKey._required = 1 # number of required fields (should go first) # IP multipath NH key IPNHKey = namedtuple('IPNHKey', ('gateway', 'encap', 'oif')) IPNHKey._required = 2 # MPLS multipath NH key MPLSNHKey = namedtuple('MPLSNHKey', ('newdst', 'via', 'oif')) MPLSNHKey._required = 2 class BaseRoute(Transactional): ''' Persistent transactional route object ''' _fields = [rtmsg.nla2name(i[0]) for i in rtmsg.nla_map] for key, _ in rtmsg.fields: _fields.append(key) _fields.append('removal') _virtual_fields = ['ipdb_scope', 'ipdb_priority'] _fields.extend(_virtual_fields) _linked_sets = ['multipath', ] _nested = [] cleanup = ('attrs', 'header', 'event', 'cacheinfo') def __init__(self, ipdb, mode=None, parent=None, uid=None): Transactional.__init__(self, ipdb, mode, parent, uid) with self._direct_state: self['ipdb_priority'] = 0 @with_transaction def add_nh(self, prime): with self._write_lock: # if the multipath chain is empty, copy the current # nexthop as the first in the multipath if not self['multipath']: first = {} for key in [x for x in self._fields if x not in ('family', 'dst_len', 'src_len', 'tos', 'table', 'proto', 'scope', 'type', 'flags', 'dst', 'src', 'ipdb_scope', 'metrics', 'encap', 'via', 'multipath')]: if self[key]: first[key] = self[key] if first: if self['family']: first['family'] = self['family'] for key in ('encap', 'via', 'metrics'): if self[key] and any(self[key].values()): first[key] = self[key] self[key] = None self['multipath'].add(first) # cleanup key fields for key in ('oif', 'iif', 'gateway', 'newdst'): self[key] = None # add the prime as NH if self['family'] == AF_MPLS: prime['family'] = AF_MPLS self['multipath'].add(prime) @with_transaction def del_nh(self, prime): with self._write_lock: if not self['multipath']: raise KeyError('attempt to delete nexthop from ' 'non-multipath route') nh = dict(prime) if self['family'] == AF_MPLS: nh['family'] = AF_MPLS self['multipath'].remove(nh) def load_netlink(self, msg): with self._direct_state: if self['ipdb_scope'] == 'locked': # do not touch locked interfaces return self['ipdb_scope'] = 'system' for (key, value) in msg.items(): self[key] = value # cleanup multipath NH for nh in self['multipath']: self.del_nh(nh) # merge NLA for cell in msg['attrs']: # # Parse on demand # norm = rtmsg.nla2name(cell[0]) if norm in self.cleanup: continue value = cell[1] # normalize RTAX if norm == 'metrics': with self['metrics']._direct_state: for metric in tuple(self['metrics'].keys()): del self['metrics'][metric] for (rtax, rtax_value) in value['attrs']: rtax_norm = rtmsg.metrics.nla2name(rtax) self['metrics'][rtax_norm] = rtax_value elif norm == 'multipath': for record in value: nh = type(self)(ipdb=self.ipdb, parent=self) nh.load_netlink(record) with nh._direct_state: del nh['dst'] del nh['ipdb_scope'] del nh['ipdb_priority'] del nh['multipath'] del nh['metrics'] self['multipath'].add(nh) elif norm == 'encap': with self['encap']._direct_state: ret = [] for l in value.get_attr('MPLS_IPTUNNEL_DST'): ret.append(str(l['label'])) self['encap']['labels'] = '/'.join(ret) elif norm == 'via': with self['via']._direct_state: self['via'] = value elif norm == 'newdst': self['newdst'] = [x['label'] for x in value] else: self[norm] = value if msg.get('family', 0) == AF_MPLS: dst = msg.get_attr('RTA_DST') if dst: dst = dst[0]['label'] else: if msg.get_attr('RTA_DST'): dst = '%s/%s' % (msg.get_attr('RTA_DST'), msg['dst_len']) else: dst = 'default' self['dst'] = dst # fix RTA_ENCAP_TYPE if needed if msg.get_attr('RTA_ENCAP'): if self['encap_type'] is not None: with self['encap']._direct_state: self['encap']['type'] = self['encap_type'] self['encap_type'] = None # or drop encap, if there is no RTA_ENCAP in msg elif self['encap'] is not None: self['encap_type'] = None with self['encap']._direct_state: self['encap'] = {} # drop metrics, if there is no RTA_METRICS in msg if not msg.get_attr('RTA_METRICS') and self['metrics'] is not None: with self['metrics']._direct_state: self['metrics'] = {} # same for via if not msg.get_attr('RTA_VIA') and self['via'] is not None: with self['via']._direct_state: self['via'] = {} # one hop -> multihop transition if not msg.get_attr('RTA_GATEWAY') and self['gateway'] is not None: self['gateway'] = None if 'oif' not in msg and \ not msg.get_attr('RTA_OIF') and \ self['oif'] is not None: self['oif'] = None # finally, cleanup all not needed for item in self.cleanup: if item in self: del self[item] def commit(self, tid=None, transaction=None, commit_phase=1, commit_mask=0xff): if not commit_phase & commit_mask: return self error = None drop = True devop = 'set' cleanup = [] if tid: transaction = self.global_tx[tid] else: if transaction: drop = False else: transaction = self.current_tx # create a new route if self['ipdb_scope'] != 'system': devop = 'add' # work on an existing route snapshot = self.pick() added, removed = transaction // snapshot added.pop('ipdb_scope', None) removed.pop('ipdb_scope', None) try: # route set if self['family'] != AF_MPLS: cleanup = [any(snapshot['metrics'].values()) and not any(added.get('metrics', {}).values()), any(snapshot['encap'].values()) and not any(added.get('encap', {}).values())] if any(added.values()) or \ any(cleanup) or \ removed.get('multipath', None) or \ devop == 'add': # prepare multipath target sync wlist = [] if transaction['multipath']: mplen = len(transaction['multipath']) if mplen == 1: # set up local targets for nh in transaction['multipath']: for key in ('gateway', 'oif', 'newdst'): if nh.get(key, None): self.set_target(key, nh[key]) wlist.append(key) mpt = None else: def mpcheck(mpset): return len(mpset) == mplen mpt = self['multipath'].set_target(mpcheck, True) else: mpt = None # prepare the anchor key to catch *possible* route update old_key = self.make_key(self) new_key = self.make_key(transaction) if old_key != new_key: # delete old record if devop == 'set': self.nl.route('del', **dict(old_key._asdict())) # assume we can not move routes between tables (yet ;) if self['family'] == AF_MPLS: route_index = self.ipdb.routes.tables['mpls'].idx else: route_index = (self.ipdb .routes .tables[self['table'] or 254] .idx) # re-link the route record if new_key in route_index: raise CommitException('route idx conflict') else: route_index[new_key] = {'key': new_key, 'route': self} # wipe the old key, if needed if old_key in route_index: del route_index[old_key] self.nl.route(devop, **transaction) transaction.wait_all_targets() for key in ('metrics', 'via'): if transaction[key] and transaction[key]._targets: transaction[key].wait_all_targets() if mpt is not None: mpt.wait(SYNC_TIMEOUT) if not mpt.is_set(): raise CommitException('multipath target is not set') self['multipath'].clear_target(mpt) for key in wlist: self.wait_target(key) # route removal if (transaction['ipdb_scope'] in ('shadow', 'remove')) or\ ((transaction['ipdb_scope'] == 'create') and commit_phase == 2): if transaction['ipdb_scope'] == 'shadow': with self._direct_state: self['ipdb_scope'] = 'locked' # create watchdog wd = self.ipdb.watchdog('RTM_DELROUTE', **self.wd_key(snapshot)) for route in self.nl.route('delete', **snapshot): self.ipdb.routes.load_netlink(route) wd.wait() if transaction['ipdb_scope'] == 'shadow': with self._direct_state: self['ipdb_scope'] = 'shadow' except Exception as e: if devop == 'add': error = e self.nl = None with self._direct_state: self['ipdb_scope'] = 'invalid' if self['family'] == AF_MPLS: route_index = self.ipdb.routes.tables['mpls'].idx else: route_index = (self.ipdb .routes .tables[self['table'] or 254] .idx) route_key = self.make_key(self) del route_index[route_key] elif commit_phase == 1: ret = self.commit(transaction=snapshot, commit_phase=2, commit_mask=commit_mask) if isinstance(ret, Exception): error = ret else: error = e else: if drop: self.drop(transaction.uid) x = RuntimeError() x.cause = e raise x if drop and commit_phase == 1: self.drop(transaction.uid) if error is not None: error.transaction = transaction raise error return self def remove(self): self['ipdb_scope'] = 'remove' return self def shadow(self): self['ipdb_scope'] = 'shadow' return self class Route(BaseRoute): _nested = ['encap', 'metrics'] wd_key = WatchdogKey @classmethod def make_encap(cls, encap): ''' Normalize encap object ''' labels = encap.get('labels', None) if isinstance(labels, (list, tuple, set)): labels = '/'.join(map(lambda x: str(x['label']) if isinstance(x, dict) else str(x), labels)) if not isinstance(labels, basestring): raise TypeError('labels struct not supported') return {'type': encap.get('type', 'mpls'), 'labels': labels} @classmethod def make_nh_key(cls, msg): ''' Construct from a netlink message a multipath nexthop key ''' values = [] if isinstance(msg, nlmsg_base): for field in IPNHKey._fields: v = msg.get_attr(msg.name2nla(field)) if field == 'encap': # 1. encap type if msg.get_attr('RTA_ENCAP_TYPE') != 1: # FIXME values.append(None) continue # 2. encap_type == 'mpls' v = '/'.join([str(x['label']) for x in v.get_attr('MPLS_IPTUNNEL_DST')]) elif v is None: v = msg.get(field, None) values.append(v) elif isinstance(msg, dict): for field in IPNHKey._fields: v = msg.get(field, None) if field == 'encap' and v and v['labels']: v = v['labels'] elif (field == 'encap') and \ (len(msg.get('multipath', []) or []) == 1): v = (tuple(msg['multipath'].raw.values())[0] .get('encap', {}) .get('labels', None)) elif field == 'encap': v = None elif (field == 'gateway') and \ (len(msg.get('multipath', []) or []) == 1) and \ not v: v = (tuple(msg['multipath'].raw.values())[0] .get('gateway', None)) if field == 'encap' and isinstance(v, (list, tuple, set)): v = '/'.join(map(lambda x: str(x['label']) if isinstance(x, dict) else str(x), v)) values.append(v) else: raise TypeError('prime not supported: %s' % type(msg)) return IPNHKey(*values) @classmethod def make_key(cls, msg): ''' Construct from a netlink message a key that can be used to locate the route in the table ''' values = [] if isinstance(msg, nlmsg_base): for field in RouteKey._fields: v = msg.get_attr(msg.name2nla(field)) if field in ('src', 'dst'): if v is not None: v = '%s/%s' % (v, msg['%s_len' % field]) elif field == 'dst': v = 'default' elif v is None: v = msg.get(field, None) values.append(v) elif isinstance(msg, dict): for field in RouteKey._fields: values.append(msg.get(field, None)) else: raise TypeError('prime not supported: %s' % type(msg)) return RouteKey(*values) def __setitem__(self, key, value): ret = value if (key in ('encap', 'metrics')) and isinstance(value, dict): # transactionals attach as is if type(value) in (Encap, Metrics): with self._direct_state: return Transactional.__setitem__(self, key, value) # check, if it exists already ret = Transactional.__getitem__(self, key) # it doesn't # (plain dict can be safely discarded) if (type(ret) == dict) or not ret: # bake transactionals in place if key == 'encap': ret = Encap(parent=self) elif key == 'metrics': ret = Metrics(parent=self) # attach transactional to the route with self._direct_state: Transactional.__setitem__(self, key, ret) # begin() works only if the transactional is attached if any(value.values()): if self._mode in ('implicit', 'explicit'): ret._begin(tid=self.current_tx.uid) [ret.__setitem__(k, v) for k, v in value.items() if v is not None] # corresponding transactional exists else: # set fields for k in ret: ret[k] = value.get(k, None) return elif key == 'multipath': cur = Transactional.__getitem__(self, key) if isinstance(cur, NextHopSet): # load entries vs = NextHopSet(value) for key in vs - cur: cur.add(key) for key in cur - vs: cur.remove(key) else: # drop any result of `update()` Transactional.__setitem__(self, key, NextHopSet(value)) return elif key == 'encap_type' and not isinstance(value, int): ret = encap_type.get(value, value) elif key == 'type' and not isinstance(value, int): ret = rt_type.get(value, value) elif key == 'proto' and not isinstance(value, int): ret = rt_proto.get(value, value) Transactional.__setitem__(self, key, ret) def __getitem__(self, key): ret = Transactional.__getitem__(self, key) if (key in ('encap', 'metrics', 'multipath')) and (ret is None): with self._direct_state: self[key] = [] if key == 'multipath' else {} ret = self[key] return ret class MPLSRoute(BaseRoute): wd_key = WatchdogMPLSKey _nested = ['via'] @classmethod def make_nh_key(cls, msg): ''' Construct from a netlink message a multipath nexthop key ''' return MPLSNHKey(newdst=tuple(msg['newdst']), via=msg.get('via', {}).get('addr', None), oif=msg.get('oif', None)) @classmethod def make_key(cls, msg): ''' Construct from a netlink message a key that can be used to locate the route in the table ''' ret = None if isinstance(msg, nlmsg): ret = msg.get_attr('RTA_DST') elif isinstance(msg, dict): ret = msg.get('dst', None) else: raise TypeError('prime not supported') if isinstance(ret, list): ret = ret[0]['label'] return ret def __setitem__(self, key, value): if key == 'via' and isinstance(value, dict): # replace with a new transactional if type(value) == Via: with self._direct_state: return BaseRoute.__setitem__(self, key, value) # or load the dict ret = BaseRoute.__getitem__(self, key) if not isinstance(ret, Via): ret = Via(parent=self) # attach new transactional -- replace any # non-Via object (may be a result of update()) with self._direct_state: BaseRoute.__setitem__(self, key, ret) # load value into the new object if any(value.values()): if self._mode in ('implicit', 'explicit'): ret._begin(tid=self.current_tx.uid) [ret.__setitem__(k, v) for k, v in value.items() if v is not None] else: # load value into existing object for k in ret: ret[k] = value.get(k, None) return elif key == 'multipath': cur = BaseRoute.__getitem__(self, key) if isinstance(cur, NextHopSet): # load entries vs = NextHopSet(value) for key in vs - cur: cur.add(key) for key in cur - vs: cur.remove(key) else: BaseRoute.__setitem__(self, key, NextHopSet(value)) else: BaseRoute.__setitem__(self, key, value) def __getitem__(self, key): with self._direct_state: ret = BaseRoute.__getitem__(self, key) if key == 'multipath' and ret is None: self[key] = [] ret = self[key] elif key == 'via' and ret is None: self[key] = {} ret = self[key] return ret class RoutingTable(object): route_class = Route def __init__(self, ipdb, prime=None): self.ipdb = ipdb self.lock = threading.Lock() self.idx = {} self.kdx = {} def __nogc__(self): return self.filter(lambda x: x['route']['ipdb_scope'] != 'gc') def __repr__(self): return repr([x['route'] for x in self.__nogc__()]) def __len__(self): return len(self.keys()) def __iter__(self): for record in self.__nogc__(): yield record['route'] def gc(self): for route in self.filter({'ipdb_scope': 'gc'}): try: self.ipdb.nl.route('get', **route['route']) with route['route']._direct_state: route['route']['ipdb_scope'] = 'system' except: del self.idx[route['key']] def keys(self, key='dst'): with self.lock: return [x['route'][key] for x in self.__nogc__()] def filter(self, target, oneshot=False): # if isinstance(target, types.FunctionType): return filter(target, [x for x in self.idx.values()]) if isinstance(target, basestring): target = {'dst': target} if not isinstance(target, dict): raise TypeError('target type not supported: %s' % type(target)) ret = [] for record in self.idx.values(): for key, value in target.items(): if (key not in record['route']) or \ (value != record['route'][key]): break else: ret.append(record) if oneshot: return ret return ret def describe(self, target, forward=False): # match the route by index -- a bit meaningless, # but for compatibility if isinstance(target, int): keys = tuple(self.idx.keys()) return self.idx[keys[target]] # match the route by key if isinstance(target, (tuple, list)): try: # full match return self.idx[RouteKey(*target)] except KeyError: # w/o iif and oif # when a route is just created, there can be no oif and # iif specified, if they weren't provided explicitly, # and in that case there will be the key w/o oif and # iif r = RouteKey._required l = RouteKey._fields return self.idx[RouteKey(*(target[:r] + (None, ) * (len(l) - r)))] if isinstance(target, nlmsg): return self.idx[Route.make_key(target)] # match the route by filter ret = self.filter(target, oneshot=True) if ret: return ret[0] if not forward: raise KeyError('record not found') # match the route by dict spec if not isinstance(target, dict): raise TypeError('lookups can be done only with dict targets') # split masks if target.get('dst', '').find('/') >= 0: dst = target['dst'].split('/') target['dst'] = dst[0] target['dst_len'] = int(dst[1]) if target.get('src', '').find('/') >= 0: src = target['src'].split('/') target['src'] = src[0] target['src_len'] = int(src[1]) # load and return the route, if exists route = Route(self.ipdb) ret = self.ipdb.nl.get_routes(**target) if not ret: raise KeyError('record not found') route.load_netlink(ret[0]) return {'route': route, 'key': None} def __delitem__(self, key): with self.lock: item = self.describe(key, forward=False) del self.idx[self.route_class.make_key(item['route'])] def load(self, msg): key = self.route_class.make_key(msg) self[key] = msg return key def __setitem__(self, key, value): with self.lock: try: record = self.describe(key, forward=False) except KeyError: record = {'route': self.route_class(self.ipdb), 'key': None} if isinstance(value, nlmsg): record['route'].load_netlink(value) elif isinstance(value, self.route_class): record['route'] = value elif isinstance(value, dict): with record['route']._direct_state: record['route'].update(value) key = self.route_class.make_key(record['route']) if record['key'] is None: self.idx[key] = {'route': record['route'], 'key': key} else: self.idx[key] = record if record['key'] != key: del self.idx[record['key']] record['key'] = key def __getitem__(self, key): with self.lock: return self.describe(key, forward=False)['route'] def __contains__(self, key): try: with self.lock: self.describe(key, forward=False) return True except KeyError: return False class MPLSTable(RoutingTable): route_class = MPLSRoute def keys(self): return self.idx.keys() def describe(self, target, forward=False): # match by key if isinstance(target, int): return self.idx[target] # match by rtmsg if isinstance(target, rtmsg): return self.idx[self.route_class.make_key(target)] raise KeyError('record not found') class RoutingTableSet(object): def __init__(self, ipdb): self.ipdb = ipdb self.ignore_rtables = ipdb._ignore_rtables or [] self.tables = {254: RoutingTable(self.ipdb)} self._event_map = {'RTM_NEWROUTE': self.load_netlink, 'RTM_DELROUTE': self.load_netlink, 'RTM_DELLINK': self.gc_mark} def _register(self): for msg in self.ipdb.nl.get_routes(family=AF_INET): self.load_netlink(msg) for msg in self.ipdb.nl.get_routes(family=AF_INET6): self.load_netlink(msg) for msg in self.ipdb.nl.get_routes(family=AF_MPLS): self.load_netlink(msg) def add(self, spec=None, **kwarg): ''' Create a route from a dictionary ''' spec = dict(spec or kwarg) if 'dst' not in spec: raise ValueError('dst not specified') multipath = spec.pop('multipath', []) if spec.get('family', 0) == AF_MPLS: table = 'mpls' if table not in self.tables: self.tables[table] = MPLSTable(self.ipdb) route = MPLSRoute(self.ipdb) else: table = spec.get('table', 254) if table not in self.tables: self.tables[table] = RoutingTable(self.ipdb) route = Route(self.ipdb) route.update(spec) with route._direct_state: route['ipdb_scope'] = 'create' for nh in multipath: if 'encap' in nh: nh['encap'] = route.make_encap(nh['encap']) if table == 'mpls': nh['family'] = AF_MPLS route.add_nh(nh) route.begin() for (key, value) in spec.items(): if key == 'encap': route[key] = route.make_encap(value) else: route[key] = value self.tables[table][route.make_key(route)] = route return route def load_netlink(self, msg): ''' Loads an existing route from a rtmsg ''' if not isinstance(msg, rtmsg): return if msg['family'] == AF_MPLS: table = 'mpls' else: table = msg['table'] if table == 252: table = msg.get_attr('RTA_TABLE') if table in self.ignore_rtables: return if msg['family'] == AF_INET6: # do not manage some reserved IETF ranges # http://www.iana.org/assignments/ # ipv6-address-space/ipv6-address-space.xhtml reserved = (('ff00', 8), # multicast ('fec0', 10), # deprecated ('fe80', 10)) # link-scoped unicast dst = msg.get_attr('RTA_DST') or '' for v in reserved: if dst[:4] == v[0] and msg['dst_len'] >= v[1]: return # RTM_DELROUTE if msg['event'] == 'RTM_DELROUTE': try: # locate the record record = self.tables[table][msg] # delete the record if record['ipdb_scope'] not in ('locked', 'shadow'): del self.tables[table][msg] with record._direct_state: record['ipdb_scope'] = 'detached' except Exception as e: # just ignore this failure for now log.debug("delroute failed for %s", e) return # RTM_NEWROUTE if table not in self.tables: if table == 'mpls': self.tables[table] = MPLSTable(self.ipdb) else: self.tables[table] = RoutingTable(self.ipdb) key = self.tables[table].load(msg) return self.tables[table][key] def gc_mark(self, msg): ### # mark route records for GC after link delete # if msg['family'] != 0: return for record in self.filter({'oif': msg['index']}): with record['route']._direct_state: record['route']['ipdb_scope'] = 'gc' for record in self.filter({'iif': msg['index']}): with record['route']._direct_state: record['route']['ipdb_scope'] = 'gc' def gc(self): for table in self.tables.keys(): self.tables[table].gc() def remove(self, route, table=None): if isinstance(route, Route): table = route.get('table', 254) or 254 route = route.get('dst', 'default') else: table = table or 254 self.tables[table][route].remove() def filter(self, target): ret = [] for table in self.tables.values(): if table is not None: ret.extend(table.filter(target)) return ret def describe(self, spec, table=254): return self.tables[table].describe(spec) def get(self, dst, table=None): table = table or 254 return self.tables[table][dst] def keys(self, table=254, family=AF_UNSPEC): return [x['dst'] for x in self.tables[table] if (x.get('family') == family) or (family == AF_UNSPEC)] def has_key(self, key, table=254): return key in self.tables[table] def __contains__(self, key): return key in self.tables[254] def __getitem__(self, key): return self.get(key) def __setitem__(self, key, value): if key != value['dst']: raise ValueError("dst doesn't match key") return self.add(value) def __delitem__(self, key): return self.remove(key) def __repr__(self): return repr(self.tables[254]) spec = [{'name': 'routes', 'class': RoutingTableSet, 'kwarg': {}}]