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