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