Keep internal API contract between RIB and BGPPeer/ApplicationPeer
[bgpcep.git] / bgp / rib-impl / src / main / java / org / opendaylight / protocol / bgp / rib / impl / EffectiveRibInWriter.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.protocol.bgp.rib.impl;
9
10 import com.google.common.base.Optional;
11 import com.google.common.base.Preconditions;
12 import com.google.common.base.Verify;
13 import java.util.Collection;
14 import java.util.HashSet;
15 import java.util.Map;
16 import java.util.Set;
17 import java.util.concurrent.ConcurrentHashMap;
18 import javax.annotation.Nonnull;
19 import javax.annotation.concurrent.NotThreadSafe;
20 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
21 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener;
22 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService;
23 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeIdentifier;
24 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
25 import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
26 import org.opendaylight.protocol.bgp.rib.impl.spi.AbstractImportPolicy;
27 import org.opendaylight.protocol.bgp.rib.impl.spi.ImportPolicyPeerTracker;
28 import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContext;
29 import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContextRegistry;
30 import org.opendaylight.protocol.bgp.rib.impl.stats.peer.route.PerTableTypeRouteCounter;
31 import org.opendaylight.protocol.bgp.rib.spi.IdentifierUtils;
32 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.PeerRole;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.peer.AdjRibIn;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.peer.EffectiveRibIn;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.Tables;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.TablesKey;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.tables.Routes;
39 import org.opendaylight.yangtools.concepts.ListenerRegistration;
40 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
41 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
42 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
43 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
44 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
45 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
46 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
47 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
48 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
49 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 /**
54  * Implementation of the BGP import policy. Listens on peer's Adj-RIB-In, inspects all inbound
55  * routes in the context of the advertising peer's role and applies the inbound policy.
56  *
57  * Inbound policy is applied as follows:
58  *
59  * 1) if the peer is an eBGP peer, perform attribute replacement and filtering
60  * 2) check if a route is admissible based on attributes attached to it, as well as the
61  *    advertising peer's role
62  * 3) output admitting routes with edited attributes into /bgp-rib/rib/peer/effective-rib-in/tables/routes
63  *
64  */
65 @NotThreadSafe
66 final class EffectiveRibInWriter implements AutoCloseable {
67     private static final Logger LOG = LoggerFactory.getLogger(EffectiveRibInWriter.class);
68     protected static final NodeIdentifier TABLE_ROUTES = new NodeIdentifier(Routes.QNAME);
69
70     private final class AdjInTracker implements AutoCloseable, DOMDataTreeChangeListener {
71         private final RIBSupportContextRegistry registry;
72         private final YangInstanceIdentifier peerIId;
73         private final YangInstanceIdentifier effRibTables;
74         private final ListenerRegistration<?> reg;
75         private final DOMTransactionChain chain;
76         private final PerTableTypeRouteCounter effectiveRibInRouteCounters;
77         private final PerTableTypeRouteCounter adjRibInRouteCounters;
78         private final Map<TablesKey, Set<YangInstanceIdentifier>> effectiveRibInRouteMap = new ConcurrentHashMap<>();
79         private final Map<TablesKey, Set<YangInstanceIdentifier>> adjRibInRouteMap = new ConcurrentHashMap<>();
80
81         AdjInTracker(final DOMDataTreeChangeService service, final RIBSupportContextRegistry registry, final DOMTransactionChain chain, final YangInstanceIdentifier peerIId,
82                 @Nonnull final PerTableTypeRouteCounter effectiveRibInRouteCounters, @Nonnull final PerTableTypeRouteCounter adjRibInRouteCounters) {
83             this.registry = Preconditions.checkNotNull(registry);
84             this.chain = Preconditions.checkNotNull(chain);
85             this.peerIId = Preconditions.checkNotNull(peerIId);
86             this.effRibTables = this.peerIId.node(EffectiveRibIn.QNAME).node(Tables.QNAME);
87             this.effectiveRibInRouteCounters = Preconditions.checkNotNull(effectiveRibInRouteCounters);
88             this.adjRibInRouteCounters = Preconditions.checkNotNull(adjRibInRouteCounters);
89
90             final DOMDataTreeIdentifier treeId = new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, this.peerIId.node(AdjRibIn.QNAME).node(Tables.QNAME));
91             LOG.debug("Registered Effective RIB on {}", this.peerIId);
92             this.reg = service.registerDataTreeChangeListener(treeId, this);
93         }
94
95         /**
96          * @deprecated Should always pass in route counter
97          * @param service
98          * @param registry
99          * @param chain
100          * @param peerIId
101          */
102         @Deprecated
103         AdjInTracker(final DOMDataTreeChangeService service, final RIBSupportContextRegistry registry, final DOMTransactionChain chain, final YangInstanceIdentifier peerIId) {
104             this(service, registry, chain, peerIId, new PerTableTypeRouteCounter("effective-rib-in"), new PerTableTypeRouteCounter("adj-rib-in"));
105         }
106
107         private void updateRoute(@Nonnull final PerTableTypeRouteCounter counter, @Nonnull final Map<TablesKey, Set<YangInstanceIdentifier>> routeMap,
108                 @Nonnull final TablesKey tablesKey, @Nonnull final YangInstanceIdentifier routeId) {
109             routeMap.putIfAbsent(tablesKey, new HashSet<>());
110             routeMap.get(tablesKey).add(routeId);
111
112             updateRouteCounter(counter, routeMap,tablesKey);
113         }
114
115         private void deleteRoute(@Nonnull final PerTableTypeRouteCounter counter, @Nonnull final Map<TablesKey, Set<YangInstanceIdentifier>> routeMap,
116                 @Nonnull final TablesKey tablesKey, @Nonnull final YangInstanceIdentifier routeId) {
117             if (routeMap.containsKey(tablesKey)) {
118                 routeMap.get(tablesKey).remove(routeId);
119             }
120
121             updateRouteCounter(counter, routeMap,tablesKey);
122         }
123
124         private void deleteRoute(@Nonnull final PerTableTypeRouteCounter counter, @Nonnull final Map<TablesKey, Set<YangInstanceIdentifier>> routeMap,
125                 @Nonnull final TablesKey tablesKey) {
126             routeMap.remove(tablesKey);
127
128             updateRouteCounter(counter, routeMap,tablesKey);
129         }
130
131         private void updateRouteCounter(@Nonnull final PerTableTypeRouteCounter counter, @Nonnull final Map<TablesKey, Set<YangInstanceIdentifier>> routeMap,
132                 @Nonnull final TablesKey tablesKey) {
133             counter.getCounterOrSetDefault(tablesKey)
134             .setCount(routeMap.getOrDefault(tablesKey, new HashSet<>()).size());
135         }
136
137         private void processRoute(final DOMDataWriteTransaction tx, final RIBSupport ribSupport, final AbstractImportPolicy policy, final YangInstanceIdentifier routesPath, final DataTreeCandidateNode route) {
138             LOG.debug("Process route {}", route.getIdentifier());
139             final YangInstanceIdentifier routeId = ribSupport.routePath(routesPath, route.getIdentifier());
140             final TablesKey tablesKey = new TablesKey(ribSupport.getAfi(), ribSupport.getSafi());
141             switch (route.getModificationType()) {
142             case DELETE:
143             case DISAPPEARED:
144                 tx.delete(LogicalDatastoreType.OPERATIONAL, routeId);
145                 LOG.debug("Route deleted. routeId={}", routeId);
146
147                 deleteRoute(this.adjRibInRouteCounters, this.adjRibInRouteMap, tablesKey, routeId);
148                 deleteRoute(this.effectiveRibInRouteCounters, this.effectiveRibInRouteMap, tablesKey, routeId);
149                 break;
150             case UNMODIFIED:
151                 // No-op
152                 break;
153             case APPEARED:
154             case SUBTREE_MODIFIED:
155             case WRITE:
156                 tx.put(LogicalDatastoreType.OPERATIONAL, routeId, route.getDataAfter().get());
157                 // count adj-rib-in route first
158                 updateRoute(this.adjRibInRouteCounters, this.adjRibInRouteMap, tablesKey, routeId);
159                 updateRoute(this.effectiveRibInRouteCounters, this.effectiveRibInRouteMap, tablesKey, routeId);
160                 // Lookup per-table attributes from RIBSupport
161                 final ContainerNode advertisedAttrs = (ContainerNode) NormalizedNodes.findNode(route.getDataAfter(), ribSupport.routeAttributesIdentifier()).orNull();
162                 final ContainerNode effectiveAttrs;
163
164                 if (advertisedAttrs != null) {
165                     effectiveAttrs = policy.effectiveAttributes(advertisedAttrs);
166                 } else {
167                     effectiveAttrs = null;
168                 }
169
170                 LOG.debug("Route {} effective attributes {} towards {}", route.getIdentifier(), effectiveAttrs, routeId);
171
172                 if (effectiveAttrs != null) {
173                     tx.put(LogicalDatastoreType.OPERATIONAL, routeId.node(ribSupport.routeAttributesIdentifier()), effectiveAttrs);
174
175                     updateRoute(this.effectiveRibInRouteCounters, this.effectiveRibInRouteMap, tablesKey, routeId);
176                 } else {
177                     LOG.warn("Route {} advertised empty attributes", routeId);
178                     tx.delete(LogicalDatastoreType.OPERATIONAL,  routeId);
179
180                     deleteRoute(this.effectiveRibInRouteCounters, this.effectiveRibInRouteMap, tablesKey, routeId);
181                 }
182                 break;
183             default:
184                 LOG.warn("Ignoring unhandled route {}", route);
185                 break;
186             }
187         }
188
189         private void processTableChildren(final DOMDataWriteTransaction tx, final RIBSupport ribSupport, final YangInstanceIdentifier tablePath, final Collection<DataTreeCandidateNode> children) {
190             for (final DataTreeCandidateNode child : children) {
191                 final PathArgument childIdentifier = child.getIdentifier();
192                 final Optional<NormalizedNode<?, ?>> childDataAfter = child.getDataAfter();
193                 final TablesKey tablesKey = new TablesKey(ribSupport.getAfi(), ribSupport.getSafi());
194                 LOG.debug("Process table {} type {}, dataAfter {}, dataBefore {}", childIdentifier, child
195                         .getModificationType(), childDataAfter, child.getDataBefore());
196                 final YangInstanceIdentifier childPath = tablePath.node(childIdentifier);
197                 switch (child.getModificationType()) {
198                 case DELETE:
199                 case DISAPPEARED:
200                     tx.delete(LogicalDatastoreType.OPERATIONAL, childPath);
201                     LOG.debug("Route deleted. routeId={}", childPath);
202
203                     deleteRoute(this.adjRibInRouteCounters, this.adjRibInRouteMap, tablesKey, childPath);
204                     deleteRoute(this.effectiveRibInRouteCounters, this.effectiveRibInRouteMap, tablesKey, childPath);
205                     break;
206                 case UNMODIFIED:
207                     // No-op
208                     break;
209                 case SUBTREE_MODIFIED:
210                     processModifiedRouteTables(child, childIdentifier,tx, ribSupport, EffectiveRibInWriter.this.importPolicy, childPath, childDataAfter);
211                     break;
212                 case APPEARED:
213                 case WRITE:
214                     writeRouteTables(child, childIdentifier,tx, ribSupport, EffectiveRibInWriter.this.importPolicy, childPath, childDataAfter);
215
216                     break;
217                 default:
218                     LOG.warn("Ignoring unhandled child {}", child);
219                     break;
220                 }
221             }
222         }
223
224         private void processModifiedRouteTables(final DataTreeCandidateNode child, final PathArgument childIdentifier, final DOMDataWriteTransaction tx,
225                 final RIBSupport ribSupport, final AbstractImportPolicy policy, final YangInstanceIdentifier childPath, final Optional<NormalizedNode<?, ?>> childDataAfter) {
226             if (TABLE_ROUTES.equals(childIdentifier)) {
227                 for (final DataTreeCandidateNode route : ribSupport.changedRoutes(child)) {
228                     processRoute(tx, ribSupport, policy, childPath, route);
229                 }
230             } else {
231                 tx.put(LogicalDatastoreType.OPERATIONAL, childPath, childDataAfter.get());
232             }
233         }
234
235         private void writeRouteTables(final DataTreeCandidateNode child, final PathArgument childIdentifier, final DOMDataWriteTransaction tx, final RIBSupport ribSupport, final AbstractImportPolicy policy, final YangInstanceIdentifier childPath, final Optional<NormalizedNode<?, ?>> childDataAfter) {
236             if (TABLE_ROUTES.equals(childIdentifier)) {
237                 final Collection<DataTreeCandidateNode> changedRoutes = ribSupport.changedRoutes(child);
238                 if (!changedRoutes.isEmpty()) {
239                     tx.put(LogicalDatastoreType.OPERATIONAL, childPath, childDataAfter.get());
240                     // Routes are special, as they may end up being filtered. The previous put conveniently
241                     // ensured that we have them in at target, so a subsequent delete will not fail :)
242                     for (final DataTreeCandidateNode route : changedRoutes) {
243                         processRoute(tx, ribSupport, policy, childPath, route);
244                     }
245                 }
246             }
247         }
248
249         private RIBSupportContext getRibSupport(final NodeIdentifierWithPredicates tableKey) {
250             return this.registry.getRIBSupportContext(tableKey);
251         }
252
253         private YangInstanceIdentifier effectiveTablePath(final NodeIdentifierWithPredicates tableKey) {
254             return this.effRibTables.node(tableKey);
255         }
256
257         private void modifyTable(final DOMDataWriteTransaction tx, final NodeIdentifierWithPredicates tableKey, final DataTreeCandidateNode table) {
258             final RIBSupportContext ribSupport = getRibSupport(tableKey);
259             final YangInstanceIdentifier tablePath = effectiveTablePath(tableKey);
260
261             processTableChildren(tx, ribSupport.getRibSupport(), tablePath, table.getChildNodes());
262         }
263
264         private void writeTable(final DOMDataWriteTransaction tx, final NodeIdentifierWithPredicates tableKey, final DataTreeCandidateNode table) {
265             final RIBSupportContext ribSupport = getRibSupport(tableKey);
266             final YangInstanceIdentifier tablePath = effectiveTablePath(tableKey);
267
268             // Create an empty table
269             LOG.trace("Create Empty table", tablePath);
270             ribSupport.createEmptyTableStructure(tx, tablePath);
271
272             processTableChildren(tx, ribSupport.getRibSupport(), tablePath, table.getChildNodes());
273         }
274
275         @Override
276         public void onDataTreeChanged(@Nonnull final Collection<DataTreeCandidate> changes) {
277             LOG.trace("Data changed called to effective RIB. Change : {}", changes);
278
279             // we have a lot of transactions created for 'nothing' because a lot of changes
280             // are skipped, so ensure we only create one transaction when we really need it
281             DOMDataWriteTransaction tx = null;
282             for (final DataTreeCandidate tc : changes) {
283                 final YangInstanceIdentifier rootPath = tc.getRootPath();
284
285                 final DataTreeCandidateNode root = tc.getRootNode();
286                 for (final DataTreeCandidateNode table : root.getChildNodes()) {
287                     if (tx == null) {
288                         tx = this.chain.newWriteOnlyTransaction();
289                     }
290                     changeDataTree(tx, rootPath, root, table);
291                 }
292             }
293             if (tx != null) {
294                 tx.submit();
295             }
296         }
297
298         private void changeDataTree(final DOMDataWriteTransaction tx, final YangInstanceIdentifier rootPath,
299                 final DataTreeCandidateNode root, final DataTreeCandidateNode table) {
300             final PathArgument lastArg = table.getIdentifier();
301             Verify.verify(lastArg instanceof NodeIdentifierWithPredicates, "Unexpected type %s in path %s", lastArg.getClass(), rootPath);
302             final NodeIdentifierWithPredicates tableKey = (NodeIdentifierWithPredicates) lastArg;
303             final RIBSupport ribSupport = getRibSupport(tableKey).getRibSupport();
304             final ModificationType modificationType = root.getModificationType();
305             switch (modificationType) {
306             case DELETE:
307             case DISAPPEARED:
308                 final YangInstanceIdentifier effectiveTablePath = effectiveTablePath(tableKey);
309                 LOG.debug("Delete Effective Table {} modification type {}, ", effectiveTablePath, modificationType);
310
311                 // delete the corresponding effective table
312                 tx.delete(LogicalDatastoreType.OPERATIONAL, effectiveTablePath);
313
314                 deleteRoute(this.adjRibInRouteCounters, this.adjRibInRouteMap, new TablesKey(ribSupport.getAfi(), ribSupport.getSafi()));
315                 deleteRoute(this.effectiveRibInRouteCounters, this.effectiveRibInRouteMap, new TablesKey(ribSupport.getAfi(), ribSupport.getSafi()));
316                 break;
317             case SUBTREE_MODIFIED:
318                 modifyTable(tx, tableKey, table);
319                 break;
320             case UNMODIFIED:
321                 LOG.info("Ignoring spurious notification on {} data {}", rootPath, table);
322                 break;
323             case APPEARED:
324             case WRITE:
325                 writeTable(tx, tableKey, table);
326                 break;
327             default:
328                 LOG.warn("Ignoring unhandled root {}", root);
329                 break;
330             }
331         }
332
333         @Override
334         public void close() {
335             this.reg.close();
336         }
337     }
338
339     private final AdjInTracker adjInTracker;
340     private final AbstractImportPolicy importPolicy;
341
342     @Deprecated
343     static EffectiveRibInWriter create(@Nonnull final DOMDataTreeChangeService service, @Nonnull final DOMTransactionChain chain,
344             @Nonnull final YangInstanceIdentifier peerIId, @Nonnull final ImportPolicyPeerTracker importPolicyPeerTracker, @Nonnull final RIBSupportContextRegistry registry, final PeerRole peerRole) {
345         return new EffectiveRibInWriter(service, chain, peerIId, importPolicyPeerTracker, registry, peerRole);
346     }
347
348     static EffectiveRibInWriter create(@Nonnull final DOMDataTreeChangeService service, @Nonnull final DOMTransactionChain chain,
349             @Nonnull final YangInstanceIdentifier peerIId, @Nonnull final ImportPolicyPeerTracker importPolicyPeerTracker, @Nonnull final RIBSupportContextRegistry registry, final PeerRole peerRole,
350             @Nonnull final PerTableTypeRouteCounter effectiveRouteCounters, @Nonnull final PerTableTypeRouteCounter adjRibInRouteCounters) {
351         return new EffectiveRibInWriter(service, chain, peerIId, importPolicyPeerTracker, registry, peerRole, effectiveRouteCounters, adjRibInRouteCounters);
352     }
353
354     @Deprecated
355     private EffectiveRibInWriter(final DOMDataTreeChangeService service, final DOMTransactionChain chain, final YangInstanceIdentifier peerIId,
356             final ImportPolicyPeerTracker importPolicyPeerTracker, final RIBSupportContextRegistry registry, final PeerRole peerRole) {
357         importPolicyPeerTracker.peerRoleChanged(peerIId, peerRole);
358         this.importPolicy = importPolicyPeerTracker.policyFor(IdentifierUtils.peerId((NodeIdentifierWithPredicates) peerIId.getLastPathArgument()));
359         this.adjInTracker = new AdjInTracker(service, registry, chain, peerIId);
360     }
361
362     private EffectiveRibInWriter(final DOMDataTreeChangeService service, final DOMTransactionChain chain, final YangInstanceIdentifier peerIId,
363             final ImportPolicyPeerTracker importPolicyPeerTracker, final RIBSupportContextRegistry registry, final PeerRole peerRole,
364             @Nonnull final PerTableTypeRouteCounter effectiveRouteCounters, @Nonnull final PerTableTypeRouteCounter adjRibInRouteCounters) {
365         importPolicyPeerTracker.peerRoleChanged(peerIId, peerRole);
366         this.importPolicy = importPolicyPeerTracker.policyFor(IdentifierUtils.peerId((NodeIdentifierWithPredicates) peerIId.getLastPathArgument()));
367         this.adjInTracker = new AdjInTracker(service, registry, chain, peerIId, effectiveRouteCounters, adjRibInRouteCounters);
368     }
369
370     @Override
371     public void close() {
372         this.adjInTracker.close();
373     }
374 }