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