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