def load_netlink(self, msg): with self._direct_state: if self['ipdb_scope'] == 'locked': # do not touch locked interfaces return self['ipdb_scope'] = 'system' self.update(msg) # re-init metrics metrics = self.get('metrics', Metrics(parent=self)) with metrics._direct_state: for metric in tuple(metrics.keys()): del metrics[metric] self['metrics'] = metrics # merge key for (name, value) in msg['attrs']: norm = rtmsg.nla2name(name) # normalize RTAX if norm == 'metrics': with self['metrics']._direct_state: for (rtax, rtax_value) in value['attrs']: rtax_norm = rtmsg.metrics.nla2name(rtax) self['metrics'][rtax_norm] = rtax_value elif norm == 'multipath': self['multipath'] = NextHopSet() for v in value: nh = {} for name in [x[0] for x in rtmsg.nh.fields]: nh[name] = v[name] for (rta, rta_value) in v['attrs']: rta_norm = rtmsg.nla2name(rta) nh[rta_norm] = rta_value self['multipath'].add(nh) else: self[norm] = value if msg.get_attr('RTA_DST', None) is not None: dst = '%s/%s' % (msg.get_attr('RTA_DST'), msg['dst_len']) else: dst = 'default' self['dst'] = dst # finally, cleanup all not needed for item in self.cleanup: if item in self: del self[item] self.sync()
def load_netlink(self, msg): with self._direct_state: self._exists = True self.update(msg) # merge key for (name, value) in msg['attrs']: norm = rtmsg.nla2name(name) # normalize RTAX if norm == 'metrics': ret = self.get(norm, Metrics(parent=self)) with ret._direct_state: for (rtax, rtax_value) in value['attrs']: rtax_norm = rtmsg.metrics.nla2name(rtax) ret[rtax_norm] = rtax_value self[norm] = ret else: self[norm] = value if msg.get_attr('RTA_DST', None) is not None: dst = '%s/%s' % (msg.get_attr('RTA_DST'), msg['dst_len']) else: dst = 'default' self['dst'] = dst # finally, cleanup all not needed for item in self.cleanup: if item in self: del self[item] self.sync()
def __init__(self, ipdb, mode=None, parent=None, uid=None): Transactional.__init__(self, ipdb, mode, parent, uid) self._load_event = threading.Event() self._fields = [rtmsg.nla2name(i[0]) for i in rtmsg.nla_map] self._fields.append('flags') self._fields.append('src_len') self._fields.append('dst_len') self._fields.append('table') self._fields.append('removal') self._virtual_fields = ['ipdb_scope', 'ipdb_priority'] def make_set_value(self, key): def set_value(value): self[key] = value return self return set_value for key in self._fields + self._virtual_fields: setattr(self, 'set_%s' % key, make_set_value(self, key)) self._fields.extend(self._virtual_fields) self.cleanup = ('attrs', 'header', 'event') with self._direct_state: for i in self._fields: self[i] = None self['metrics'] = Metrics(parent=self) self['ipdb_priority'] = 0
class Route(RTNL_Object): table = 'routes' api = 'route' summary = ''' SELECT rt.f_target, rt.f_tflags, rt.f_RTA_TABLE, rt.f_RTA_DST, rt.f_dst_len, rt.f_RTA_GATEWAY, nh.f_RTA_GATEWAY FROM routes AS rt LEFT JOIN nh ON rt.f_route_id = nh.f_route_id AND rt.f_target = nh.f_target ''' summary_header = ('target', 'flags', 'table', 'dst', 'dst_len', 'gateway', 'nexthop') dump = ''' SELECT rs.f_target,rs.f_tflags,%s FROM routes AS rs LEFT JOIN nh AS nh ON rs.f_route_id = nh.f_route_id AND rs.f_target = nh.f_target ''' % ','.join(['%s' % x for x in _dump_rt + _dump_nh]) dump_header = (['target', 'tflags'] + [rtmsg.nla2name(x[5:]) for x in _dump_rt] + ['nh_%s' % nh.nla2name(x[5:]) for x in _dump_nh]) reverse_update = { 'table': 'routes', 'name': 'routes_f_tflags', 'field': 'f_tflags', 'sql': ''' UPDATE interfaces SET f_tflags = NEW.f_tflags WHERE (f_index = NEW.f_RTA_OIF OR f_index = NEW.f_RTA_IIF) AND f_target = NEW.f_target; ''' } def __init__(self, view, key, ctxid=None): self.event_map = {rtmsg: "load_rtnlmsg"} super(Route, self).__init__(view, key, rtmsg, ctxid) def complete_key(self, key): if isinstance(key, dict): ret_key = key else: ret_key = {'target': 'localhost'} if isinstance(key, basestring): ret_key['RTA_DST'], ret_key['dst_len'] = key.split('/') return super(Route, self).complete_key(ret_key)
def __init__(self, ipdb, mode=None, parent=None, uid=None): Transactional.__init__(self, ipdb, mode, parent, uid) self._exists = False self._load_event = threading.Event() self._fields = [rtmsg.nla2name(i[0]) for i in rtmsg.nla_map] self._fields.append('flags') self._fields.append('src_len') self._fields.append('dst_len') self._fields.append('table') self._fields.append('removal') self.cleanup = ('attrs', 'header', 'event') with self._direct_state: self['metrics'] = Metrics(parent=self)
def __init__(self, ipdb, mode=None): Transactional.__init__(self, ipdb, mode) self._exists = False self._load_event = threading.Event() self._fields = [rtmsg.nla2name(i[0]) for i in rtmsg.nla_map] self._fields.append('flags') self._fields.append('src_len') self._fields.append('dst_len') self._fields.append('table') self._fields.append('removal') self.cleanup = ('attrs', 'header', 'event')
def dump(cls, view): req = ''' SELECT main.f_target,main.f_tflags,%s FROM routes AS main LEFT JOIN nh AS nh ON main.f_route_id = nh.f_route_id AND main.f_target = nh.f_target ''' % ','.join( ['%s' % x for x in _dump_rt + _dump_nh + ['main.f_route_id']]) header = (['target', 'tflags'] + [rtmsg.nla2name(x[7:]) for x in _dump_rt] + ['nh_%s' % nh.nla2name(x[5:]) for x in _dump_nh] + ['metrics', 'encap']) yield header plch = view.ndb.schema.plch where, values = cls._dump_where(view) for record in view.ndb.schema.fetch(req + where, values): route_id = record[-1] record = list(record[:-1]) # # fetch metrics metrics = tuple( view.ndb.schema.fetch( ''' SELECT * FROM metrics WHERE f_route_id = %s ''' % (plch, ), (route_id, ))) if metrics: ret = {} names = view.ndb.schema.compiled['metrics']['norm_names'] for k, v in zip(names, metrics[0]): if v is not None and \ k not in ('target', 'route_id', 'tflags'): ret[k] = v record.append(json.dumps(ret)) else: record.append(None) # # fetch encap enc_mpls = tuple( view.ndb.schema.fetch( ''' SELECT * FROM enc_mpls WHERE f_route_id = %s ''' % (plch, ), (route_id, ))) if enc_mpls: record.append(enc_mpls[0][2]) else: record.append(None) yield record
def load_netlink(self, msg): with self._direct_state: self._exists = True self.update(msg) # merge key for (name, value) in msg['attrs']: norm = rtmsg.nla2name(name) self[norm] = value if msg.get_attr('RTA_DST', None) is not None: dst = '%s/%s' % (msg.get_attr('RTA_DST'), msg['dst_len']) else: dst = 'default' self['dst'] = dst # finally, cleanup all not needed for item in self.cleanup: if item in self: del self[item] self.sync()
class Route(RTNL_Object): table = 'routes' summary = ''' SELECT rt.f_target, rt.f_RTA_TABLE, rt.f_RTA_DST, rt.f_dst_len, rt.f_RTA_GATEWAY, nh.f_RTA_GATEWAY FROM routes AS rt LEFT JOIN nh ON rt.f_route_id = nh.f_route_id ''' summary_header = ('target', 'table', 'dst', 'dst_len', 'gateway', 'nexthop') dump = ''' SELECT rs.f_target,%s FROM routes AS rs LEFT JOIN nh AS nh ON rs.f_route_id = nh.f_route_id AND rs.f_target = nh.f_target ''' % ','.join(['%s' % x for x in _dump_rt + _dump_nh]) dump_header = (['target'] + [rtmsg.nla2name(x[5:]) for x in _dump_rt] + ['nh_%s' % nh.nla2name(x[5:]) for x in _dump_nh]) def __init__(self, schema, key): self.event_map = {rtmsg: "load_rtnlmsg"} super(Route, self).__init__(schema, key, rtmsg) def complete_key(self, key): if isinstance(key, dict): ret_key = key else: ret_key = {'target': 'localhost'} if isinstance(key, basestring): ret_key['RTA_DST'], ret_key['dst_len'] = key.split('/') return super(Route, self).complete_key(ret_key)
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]
class Route(RTNL_Object): table = 'routes' msg_class = rtmsg api = 'route' summary = ''' SELECT rt.f_target, rt.f_tflags, rt.f_RTA_TABLE, rt.f_RTA_DST, rt.f_dst_len, rt.f_RTA_GATEWAY, nh.f_RTA_GATEWAY FROM routes AS rt LEFT JOIN nh ON rt.f_route_id = nh.f_route_id AND rt.f_target = nh.f_target ''' table_alias = 'rt' summary_header = ('target', 'tflags', 'table', 'dst', 'dst_len', 'gateway', 'nexthop') dump = ''' SELECT rt.f_target,rt.f_tflags,%s FROM routes AS rt LEFT JOIN nh AS nh ON rt.f_route_id = nh.f_route_id AND rt.f_target = nh.f_target ''' % ','.join(['%s' % x for x in _dump_rt + _dump_nh]) dump_header = (['target', 'tflags'] + [rtmsg.nla2name(x[5:]) for x in _dump_rt] + ['nh_%s' % nh.nla2name(x[5:]) for x in _dump_nh]) reverse_update = {'table': 'routes', 'name': 'routes_f_tflags', 'field': 'f_tflags', 'sql': ''' UPDATE interfaces SET f_tflags = NEW.f_tflags WHERE (f_index = NEW.f_RTA_OIF OR f_index = NEW.f_RTA_IIF) AND f_target = NEW.f_target; '''} def __init__(self, *argv, **kwarg): kwarg['iclass'] = rtmsg self.event_map = {rtmsg: "load_rtnlmsg"} super(Route, self).__init__(*argv, **kwarg) def complete_key(self, key): if isinstance(key, dict): ret_key = key else: ret_key = {'target': 'localhost'} if isinstance(key, basestring): ret_key['RTA_DST'], ret_key['dst_len'] = key.split('/') return super(Route, self).complete_key(ret_key) def __setitem__(self, key, value): if self.state == 'system' and key in self.knorm: self._replace = type(self)(self.view, self.key) self.state.set('replace') if key in ('net_ns_fd', 'net_ns_pid'): self.state.set('setns') if value != self.get(key, None): self.changed.add(key) dict.__setitem__(self, key, value)
class Route(RTNL_Object): table = 'routes' msg_class = rtmsg api = 'route' summary = ''' SELECT rt.f_target, rt.f_tflags, rt.f_RTA_TABLE, rt.f_RTA_DST, rt.f_dst_len, rt.f_RTA_GATEWAY, nh.f_RTA_GATEWAY FROM routes AS rt LEFT JOIN nh ON rt.f_route_id = nh.f_route_id AND rt.f_target = nh.f_target ''' table_alias = 'rt' summary_header = ('target', 'tflags', 'table', 'dst', 'dst_len', 'gateway', 'nexthop') dump = ''' SELECT rt.f_target,rt.f_tflags,%s FROM routes AS rt LEFT JOIN nh AS nh ON rt.f_route_id = nh.f_route_id AND rt.f_target = nh.f_target ''' % ','.join(['%s' % x for x in _dump_rt + _dump_nh]) dump_header = (['target', 'tflags'] + [rtmsg.nla2name(x[5:]) for x in _dump_rt] + ['nh_%s' % nh.nla2name(x[5:]) for x in _dump_nh]) reverse_update = { 'table': 'routes', 'name': 'routes_f_tflags', 'field': 'f_tflags', 'sql': ''' UPDATE interfaces SET f_tflags = NEW.f_tflags WHERE (f_index = NEW.f_RTA_OIF OR f_index = NEW.f_RTA_IIF) AND f_target = NEW.f_target; ''' } _replace_on_key_change = True def __init__(self, *argv, **kwarg): kwarg['iclass'] = rtmsg self.event_map = {rtmsg: "load_rtnlmsg"} dict.__setitem__(self, 'multipath', []) super(Route, self).__init__(*argv, **kwarg) def complete_key(self, key): if isinstance(key, dict): ret_key = key else: ret_key = {'target': 'localhost'} if isinstance(key, basestring): ret_key['RTA_DST'], ret_key['dst_len'] = key.split('/') return super(Route, self).complete_key(ret_key) def make_req(self, prime): req = dict(prime) for key in self.changed: req[key] = self[key] if self['multipath']: req['multipath'] = self['multipath'] return req def __setitem__(self, key, value): super(Route, self).__setitem__(key, value) if key == 'multipath': self.changed.remove(key) def apply(self, rollback=False): if (self.get('table') == 255) and \ (self.get('family') == 10) and \ (self.get('proto') == 2): # skip automatic ipv6 routes with proto kernel return self else: return super(Route, self).apply(rollback) def load_sql(self, *argv, **kwarg): super(Route, self).load_sql(*argv, **kwarg) if not self.load_event.is_set(): return if 'nh_id' not in self and self.get('route_id') is not None: nhs = (self.schema.fetch( 'SELECT * FROM nh WHERE f_route_id = %s' % (self.schema.plch, ), (self['route_id'], ))) flush = False idx = 0 for nexthop in tuple(self['multipath']): if not isinstance(nexthop, NextHop): flush = True if not flush: try: spec = next(nhs) except StopIteration: flush = True for key, value in zip(nexthop.names, spec): if key in nexthop and value is None: continue else: nexthop.load_value(key, value) if flush: self['multipath'].pop(idx) continue idx += 1 for nexthop in nhs: key = {'route_id': self['route_id'], 'nh_id': nexthop[-1]} self['multipath'].append(NextHop(self.view, key))
class Route(RTNL_Object): table = 'routes' msg_class = rtmsg hidden_fields = ['route_id'] api = 'route' summary = ''' SELECT rt.f_target, rt.f_tflags, rt.f_RTA_TABLE, rt.f_RTA_DST, rt.f_dst_len, rt.f_RTA_GATEWAY, nh.f_RTA_GATEWAY FROM routes AS rt LEFT JOIN nh ON rt.f_route_id = nh.f_route_id AND rt.f_target = nh.f_target ''' table_alias = 'rt' summary_header = ('target', 'tflags', 'table', 'dst', 'dst_len', 'gateway', 'nexthop') dump = ''' SELECT rt.f_target,rt.f_tflags,%s FROM routes AS rt LEFT JOIN nh AS nh ON rt.f_route_id = nh.f_route_id AND rt.f_target = nh.f_target ''' % ','.join(['%s' % x for x in _dump_rt + _dump_nh]) dump_header = (['target', 'tflags'] + [rtmsg.nla2name(x[5:]) for x in _dump_rt] + ['nh_%s' % nh.nla2name(x[5:]) for x in _dump_nh]) _replace_on_key_change = True def mark_tflags(self, mark): plch = (self.schema.plch, ) * 4 self.schema.execute( ''' UPDATE interfaces SET f_tflags = %s WHERE (f_index = %s OR f_index = %s) AND f_target = %s ''' % plch, (mark, self['iif'], self['oif'], self['target'])) def __init__(self, *argv, **kwarg): kwarg['iclass'] = rtmsg self.event_map = {rtmsg: "load_rtnlmsg"} dict.__setitem__(self, 'multipath', []) dict.__setitem__(self, 'metrics', {}) super(Route, self).__init__(*argv, **kwarg) def complete_key(self, key): ret_key = {} if isinstance(key, basestring): ret_key['dst'] = key elif isinstance(key, (Record, tuple, list)): return super(Route, self).complete_key(key) elif isinstance(key, dict): ret_key.update(key) else: raise TypeError('unsupported key type') if 'target' not in ret_key: ret_key['target'] = 'localhost' table = ret_key.get('table', ret_key.get('RTA_TABLE', 254)) if 'table' not in ret_key: ret_key['table'] = table if isinstance(ret_key.get('dst_len'), basestring): ret_key['dst_len'] = int(ret_key['dst_len']) if isinstance(ret_key.get('dst'), basestring): if ret_key.get('dst') == 'default': ret_key['dst'] = '' ret_key['dst_len'] = 0 elif '/' in ret_key['dst']: ret_key['dst'], ret_key['dst_len'] = ret_key['dst'].split('/') return super(Route, self).complete_key(ret_key) @property def clean(self): clean = True for s in (self['metrics'], ) + tuple(self['multipath']): if hasattr(s, 'changed'): clean &= len(s.changed) == 0 return clean & super(Route, self).clean def make_req(self, prime): req = dict(prime) for key in self.changed: req[key] = self[key] if self['multipath']: req['multipath'] = self['multipath'] if self['metrics']: req['metrics'] = self['metrics'] if self.get('gateway'): req['gateway'] = self['gateway'] return req def __setitem__(self, key, value): if key in ('dst', 'src') and '/' in value: net, net_len = value.split('/') if net in ('0', '0.0.0.0'): net = '' super(Route, self).__setitem__(key, net) super(Route, self).__setitem__('%s_len' % key, int(net_len)) elif key == 'dst' and value == 'default': super(Route, self).__setitem__('dst', '') super(Route, self).__setitem__('dst_len', 0) elif key == 'route_id': raise ValueError('route_id is read only') elif key == 'multipath': super(Route, self).__setitem__('multipath', []) for mp in value: mp = dict(mp) if self.state == 'invalid': mp['create'] = True obj = NextHop(self, self.view, mp) obj.state.set(self.state.get()) self['multipath'].append(obj) if key in self.changed: self.changed.remove(key) elif key == 'metrics': value = dict(value) if self.state == 'invalid': value['create'] = True obj = Metrics(self, self.view, value) obj.state.set(self.state.get()) super(Route, self).__setitem__('metrics', obj) if key in self.changed: self.changed.remove(key) else: super(Route, self).__setitem__(key, value) def apply(self, rollback=False): if (self.get('table') == 255) and \ (self.get('family') == 10) and \ (self.get('proto') == 2): # skip automatic ipv6 routes with proto kernel return self else: return super(Route, self).apply(rollback) def load_sql(self, *argv, **kwarg): super(Route, self).load_sql(*argv, **kwarg) if not self.load_event.is_set(): return if 'nh_id' not in self and self.get('route_id') is not None: nhs = (self.schema.fetch( 'SELECT * FROM nh WHERE f_route_id = %s' % (self.schema.plch, ), (self['route_id'], ))) metrics = (self.schema.fetch( 'SELECT * FROM metrics WHERE f_route_id = %s' % (self.schema.plch, ), (self['route_id'], ))) if len(tuple(metrics)): self['metrics'] = Metrics(self, self.view, {'route_id': self['route_id']}) flush = False idx = 0 for nexthop in tuple(self['multipath']): if not isinstance(nexthop, NextHop): flush = True if not flush: try: spec = next(nhs) except StopIteration: flush = True for key, value in zip(nexthop.names, spec): if key in nexthop and value is None: continue else: nexthop.load_value(key, value) if flush: self['multipath'].pop(idx) continue idx += 1 for nexthop in nhs: key = {'route_id': self['route_id'], 'nh_id': nexthop[-1]} self['multipath'].append(NextHop(self, self.view, key))
class Route(Transactional): ''' Persistent transactional route object ''' _fields = [rtmsg.nla2name(i[0]) for i in rtmsg.nla_map] _fields.append('flags') _fields.append('src_len') _fields.append('dst_len') _fields.append('table') _fields.append('removal') _virtual_fields = ['ipdb_scope', 'ipdb_priority'] _fields.extend(_virtual_fields) _linked_sets = [ 'multipath', ] cleanup = ('attrs', 'header', 'event', 'cacheinfo') def __init__(self, ipdb, mode=None, parent=None, uid=None): Transactional.__init__(self, ipdb, mode, parent, uid) self._load_event = threading.Event() with self._direct_state: for i in self._fields: self[i] = None self['metrics'] = Metrics(parent=self) self['multipath'] = NextHopSet() self['ipdb_priority'] = 0 def add_nh(self, prime): with self._write_lock: tx = self.get_tx() with tx._direct_state: tx['multipath'].add(prime) def del_nh(self, prime): with self._write_lock: tx = self.get_tx() with tx._direct_state: tx['multipath'].remove(prime) def load_netlink(self, msg): with self._direct_state: if self['ipdb_scope'] == 'locked': # do not touch locked interfaces return self['ipdb_scope'] = 'system' self.update(msg) # re-init metrics metrics = self.get('metrics', Metrics(parent=self)) with metrics._direct_state: for metric in tuple(metrics.keys()): del metrics[metric] self['metrics'] = metrics # merge key for (name, value) in msg['attrs']: norm = rtmsg.nla2name(name) # normalize RTAX if norm == 'metrics': with self['metrics']._direct_state: for (rtax, rtax_value) in value['attrs']: rtax_norm = rtmsg.metrics.nla2name(rtax) self['metrics'][rtax_norm] = rtax_value elif norm == 'multipath': self['multipath'] = NextHopSet() for v in value: nh = {} for name in [x[0] for x in rtmsg.nh.fields]: nh[name] = v[name] for (rta, rta_value) in v['attrs']: rta_norm = rtmsg.nla2name(rta) nh[rta_norm] = rta_value self['multipath'].add(nh) else: self[norm] = value if msg.get_attr('RTA_DST', None) is not None: dst = '%s/%s' % (msg.get_attr('RTA_DST'), msg['dst_len']) else: dst = 'default' self['dst'] = dst # finally, cleanup all not needed for item in self.cleanup: if item in self: del self[item] self.sync() def sync(self): self._load_event.set() def reload(self): # do NOT call get_routes() here, it can cause race condition # self._load_event.wait() return self def commit(self, tid=None, transaction=None, rollback=False): self._load_event.clear() error = None drop = True if tid: transaction = self._transactions[tid] else: if transaction: drop = False else: transaction = self.last() # create a new route if self['ipdb_scope'] != 'system': try: self.ipdb.update_routes( self.nl.route('add', **IPRouteRequest(transaction))) except Exception: self.nl = None self.ipdb.routes.remove(self) raise # work on existing route snapshot = self.pick() try: # route set request = IPRouteRequest(transaction - snapshot) if any([request[x] not in (None, {'attrs': []}) for x in request]): self.ipdb.update_routes( self.nl.route('set', **IPRouteRequest(transaction))) # route removal if (transaction['ipdb_scope'] in ('shadow', 'remove')) or\ ((transaction['ipdb_scope'] == 'create') and rollback): if transaction['ipdb_scope'] == 'shadow': self.set_item('ipdb_scope', 'locked') self.ipdb.update_routes( self.nl.route('delete', **IPRouteRequest(snapshot))) if transaction['ipdb_scope'] == 'shadow': self.set_item('ipdb_scope', 'shadow') except Exception as e: if not rollback: ret = self.commit(transaction=snapshot, rollback=True) if isinstance(ret, Exception): error = ret else: error = e else: if drop: self.drop() x = RuntimeError() x.cause = e raise x if drop and not rollback: self.drop() if error is not None: error.transaction = transaction raise error if not rollback: with self._direct_state: self['multipath'] = transaction['multipath'] self.reload() return self def remove(self): self['ipdb_scope'] = 'remove' return self def shadow(self): self['ipdb_scope'] = 'shadow' return self
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 = [] # FIXME: should support encap_types other than MPLS try: for l in value.get_attr('MPLS_IPTUNNEL_DST'): ret.append(str(l['label'])) if ret: self['encap']['labels'] = '/'.join(ret) except AttributeError: pass 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]
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 = [] _gctime = None cleanup = ('attrs', 'header', 'event', 'cacheinfo') _fields_cmp = { 'src': _normalize_ipnet, 'dst': _normalize_ipnet, 'gateway': _normalize_ipaddr, 'prefsrc': _normalize_ipaddr } 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 ('oif', 'gateway', 'newdst'): 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', '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 = [] # FIXME: should support encap_types other than MPLS try: for l in value.get_attr('MPLS_IPTUNNEL_DST'): ret.append(str(l['label'])) if ret: self['encap']['labels'] = '/'.join(ret) except AttributeError: pass 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 = self.ipdb.txdrop devop = 'set' cleanup = [] # FIXME -- make a debug object debug = {'traceback': None, 'next_stage': None} notx = True if tid or transaction: notx = False if tid: transaction = self.global_tx[tid] else: transaction = transaction or self.current_tx # ignore global rollbacks on invalid routes if self['ipdb_scope'] == 'create' and commit_phase > 1: return # 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 ('oif', 'gateway', '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: # 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) # delete old record, if required if (old_key != new_key) and (devop == 'set'): self.nl.route('del', **dict(old_key._asdict())) 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' # success, so it's safe to drop the transaction drop = True except Exception as e: error = e # prepare postmortem debug['traceback'] = traceback.format_exc() debug['error_stack'] = [] debug['next_stage'] = None if commit_phase == 1: try: self.commit(transaction=snapshot, commit_phase=2, commit_mask=commit_mask) except Exception as i_e: debug['next_stage'] = i_e error = RuntimeError() if drop and notx: self.drop(transaction.uid) if error is not None: error.debug = debug raise error self.ipdb.routes.gc() return self def remove(self): self['ipdb_scope'] = 'remove' return self def shadow(self): self['ipdb_scope'] = 'shadow' return self def detach(self): if self.get('family') == AF_MPLS: table = 'mpls' else: table = self.get('table', 254) del self.ipdb.routes.tables[table][self.make_key(self)]
def load_netlink(self, msg): with self._direct_state: if self['ipdb_scope'] == 'locked': # do not touch locked interfaces return self['ipdb_scope'] = 'system' # IPv6 multipath via several devices (not networks) is a very # special case, since we get only the first hop notification. Ask # the kernel guys why. I've got no idea. # # So load all the rest flags = msg.get('header', {}).get('flags', 0) family = msg.get('family', 0) clean_mp = True table = msg.get_attr('RTA_TABLE') or msg.get('table') dst = msg.get_attr('RTA_DST') # # It MAY be a multipath hop # if family == AF_INET6 and not msg.get_attr('RTA_MULTIPATH'): # # It is a notification about the route created # if flags == NLM_F_CREATE: # # This routine can significantly slow down the IPDB # instance, but I see no way around. Some are born # to endless night. # clean_mp = False msgs = self.nl.route('show', table=table, dst=dst, family=family) for nhmsg in msgs: nh = type(self)(ipdb=self.ipdb, parent=self) nh.load_netlink(nhmsg) with nh._direct_state: del nh['dst'] del nh['ipdb_scope'] del nh['ipdb_priority'] del nh['multipath'] del nh['metrics'] self.add_nh(nh) # # it IS a multipath hop loaded during IPDB init # elif flags == NLM_F_MULTI and self.get('dst'): nh = type(self)(ipdb=self.ipdb, parent=self) nh.load_netlink(msg) with nh._direct_state: del nh['dst'] del nh['ipdb_scope'] del nh['ipdb_priority'] del nh['multipath'] del nh['metrics'] self.add_nh(nh) return for (key, value) in msg.items(): self[key] = value # cleanup multipath NH if clean_mp: for nh in self['multipath']: self.del_nh(nh) 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: # WIP: should support encap_types other than MPLS if value.get_attr('MPLS_IPTUNNEL_DST'): ret = [] for dst in value.get_attr('MPLS_IPTUNNEL_DST'): ret.append(str(dst['label'])) if ret: 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 load_netlink(self, msg): with self._direct_state: if self['ipdb_scope'] == 'locked': # do not touch locked interfaces return self['ipdb_scope'] = 'system' # IPv6 multipath via several devices (not networks) is a very # special case, since we get only the first hop notification. Ask # the kernel guys why. I've got no idea. # # So load all the rest flags = msg.get('header', {}).get('flags', 0) family = msg.get('family', 0) clean_mp = True table = msg.get_attr('RTA_TABLE') or msg.get('table') dst = msg.get_attr('RTA_DST') # # It MAY be a multipath hop # if family == AF_INET6 and not msg.get_attr('RTA_MULTIPATH'): # # It is a notification about the route created # if flags == NLM_F_CREATE: # # This routine can significantly slow down the IPDB # instance, but I see no way around. Some are born # to endless night. # clean_mp = False msgs = self.nl.route('show', table=table, dst=dst, family=family) for nhmsg in msgs: nh = type(self)(ipdb=self.ipdb, parent=self) nh.load_netlink(nhmsg) with nh._direct_state: del nh['dst'] del nh['ipdb_scope'] del nh['ipdb_priority'] del nh['multipath'] del nh['metrics'] self.add_nh(nh) # # it IS a multipath hop loaded during IPDB init # elif flags == NLM_F_MULTI and self.get('dst'): nh = type(self)(ipdb=self.ipdb, parent=self) nh.load_netlink(msg) with nh._direct_state: del nh['dst'] del nh['ipdb_scope'] del nh['ipdb_priority'] del nh['multipath'] del nh['metrics'] self.add_nh(nh) return for (key, value) in msg.items(): self[key] = value # cleanup multipath NH if clean_mp: for nh in self['multipath']: self.del_nh(nh) 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 = [] # FIXME: should support encap_types other than MPLS try: for l in value.get_attr('MPLS_IPTUNNEL_DST'): ret.append(str(l['label'])) if ret: self['encap']['labels'] = '/'.join(ret) except AttributeError: pass 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]