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