Prepare EffRibInWriter for LLGR_STALE handling
[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 com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.collect.ImmutableList;
14 import com.google.common.collect.ImmutableMap;
15 import com.google.common.collect.ImmutableSet;
16 import com.google.common.util.concurrent.FluentFuture;
17 import com.google.common.util.concurrent.FutureCallback;
18 import com.google.common.util.concurrent.MoreExecutors;
19 import java.util.Collection;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Optional;
23 import java.util.Set;
24 import java.util.concurrent.ExecutionException;
25 import java.util.concurrent.atomic.LongAdder;
26 import javax.annotation.Nonnull;
27 import javax.annotation.concurrent.GuardedBy;
28 import javax.annotation.concurrent.NotThreadSafe;
29 import org.opendaylight.controller.md.sal.binding.api.BindingTransactionChain;
30 import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener;
31 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
32 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
33 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
34 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
35 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
36 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
37 import org.opendaylight.mdsal.common.api.CommitInfo;
38 import org.opendaylight.protocol.bgp.openconfig.spi.BGPTableTypeRegistryConsumer;
39 import org.opendaylight.protocol.bgp.parser.impl.message.update.CommunityUtil;
40 import org.opendaylight.protocol.bgp.rib.impl.spi.RIB;
41 import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContextRegistry;
42 import org.opendaylight.protocol.bgp.rib.impl.spi.RibOutRefresh;
43 import org.opendaylight.protocol.bgp.rib.impl.state.peer.PrefixesInstalledCounters;
44 import org.opendaylight.protocol.bgp.rib.impl.state.peer.PrefixesReceivedCounters;
45 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
46 import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRibRoutingPolicy;
47 import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRouteEntryImportParameters;
48 import org.opendaylight.protocol.bgp.route.targetcontrain.spi.ClientRouteTargetContrainCache;
49 import org.opendaylight.protocol.bgp.route.targetcontrain.spi.RouteTargetMembeshipUtil;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.path.attributes.Attributes;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.path.attributes.attributes.Communities;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.PeerRole;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.Route;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.Peer;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.PeerKey;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.peer.AdjRibIn;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.peer.EffectiveRibIn;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesBuilder;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesKey;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.tables.Routes;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.Ipv4AddressFamily;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.Ipv6AddressFamily;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.MplsLabeledVpnSubsequentAddressFamily;
65 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.RouteTarget;
66 import org.opendaylight.yangtools.concepts.ListenerRegistration;
67 import org.opendaylight.yangtools.yang.binding.ChildOf;
68 import org.opendaylight.yangtools.yang.binding.ChoiceIn;
69 import org.opendaylight.yangtools.yang.binding.DataObject;
70 import org.opendaylight.yangtools.yang.binding.Identifiable;
71 import org.opendaylight.yangtools.yang.binding.Identifier;
72 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
73 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.IdentifiableItem;
74 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument;
75 import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
76 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
77 import org.slf4j.Logger;
78 import org.slf4j.LoggerFactory;
79
80 /**
81  * Implementation of the BGP import policy. Listens on peer's Adj-RIB-In, inspects all inbound
82  * routes in the context of the advertising peer's role and applies the inbound policy.
83  * <p>
84  * Inbound policy is applied as follows:
85  * <p>
86  * 1) if the peer is an eBGP peer, perform attribute replacement and filtering
87  * 2) check if a route is admissible based on attributes attached to it, as well as the
88  * advertising peer's role
89  * 3) output admitting routes with edited attributes into /bgp-rib/rib/peer/effective-rib-in/tables/routes
90  */
91 @NotThreadSafe
92 final class EffectiveRibInWriter implements PrefixesReceivedCounters, PrefixesInstalledCounters,
93         AutoCloseable, ClusteredDataTreeChangeListener<Tables> {
94
95     private static final Logger LOG = LoggerFactory.getLogger(EffectiveRibInWriter.class);
96     static final NodeIdentifier TABLE_ROUTES = new NodeIdentifier(Routes.QNAME);
97     private static final TablesKey IVP4_VPN_TABLE_KEY = new TablesKey(Ipv4AddressFamily.class,
98             MplsLabeledVpnSubsequentAddressFamily.class);
99     private static final TablesKey IVP6_VPN_TABLE_KEY = new TablesKey(Ipv6AddressFamily.class,
100             MplsLabeledVpnSubsequentAddressFamily.class);
101     private static final ImmutableList<Communities> STALE_LLGR_COMMUNUTIES = ImmutableList.of(
102         StaleCommunities.STALE_LLGR);
103     private static final Attributes STALE_LLGR_ATTRIBUTES = new org.opendaylight.yang.gen.v1.urn.opendaylight.params
104             .xml.ns.yang.bgp.message.rev180329.path.attributes.AttributesBuilder()
105             .setCommunities(STALE_LLGR_COMMUNUTIES)
106             .build();
107
108     private final RIBSupportContextRegistry registry;
109     private final KeyedInstanceIdentifier<Peer, PeerKey> peerIId;
110     private final InstanceIdentifier<EffectiveRibIn> effRibTables;
111     private final DataBroker databroker;
112     private final List<RouteTarget> rtMemberships;
113     private final RibOutRefresh vpnTableRefresher;
114     private final ClientRouteTargetContrainCache rtCache;
115     private ListenerRegistration<?> reg;
116     private BindingTransactionChain chain;
117     private final Map<TablesKey, LongAdder> prefixesReceived;
118     private final Map<TablesKey, LongAdder> prefixesInstalled;
119     private final BGPRibRoutingPolicy ribPolicies;
120     private final BGPRouteEntryImportParameters peerImportParameters;
121     private final BGPTableTypeRegistryConsumer tableTypeRegistry;
122     @GuardedBy("this")
123     private FluentFuture<? extends CommitInfo> submitted;
124     private boolean rtMembershipsUpdated;
125
126     EffectiveRibInWriter(
127             final BGPRouteEntryImportParameters peer,
128             final RIB rib,
129             final BindingTransactionChain chain,
130             final KeyedInstanceIdentifier<Peer, PeerKey> peerIId,
131             final Set<TablesKey> tables,
132             final BGPTableTypeRegistryConsumer tableTypeRegistry,
133             final List<RouteTarget> rtMemberships,
134             final ClientRouteTargetContrainCache rtCache) {
135         this.registry = requireNonNull(rib.getRibSupportContext());
136         this.chain = requireNonNull(chain);
137         this.peerIId = requireNonNull(peerIId);
138         this.effRibTables = this.peerIId.child(EffectiveRibIn.class);
139         this.prefixesInstalled = buildPrefixesTables(tables);
140         this.prefixesReceived = buildPrefixesTables(tables);
141         this.ribPolicies = requireNonNull(rib.getRibPolicies());
142         this.databroker = requireNonNull(rib.getDataBroker());
143         this.tableTypeRegistry = requireNonNull(tableTypeRegistry);
144         this.peerImportParameters = peer;
145         this.rtMemberships = rtMemberships;
146         this.rtCache = rtCache;
147         this.vpnTableRefresher = rib;
148     }
149
150     public void init() {
151         final DataTreeIdentifier<Tables> treeId = new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL,
152                 this.peerIId.child(AdjRibIn.class).child(Tables.class));
153         LOG.debug("Registered Effective RIB on {}", this.peerIId);
154         this.reg = requireNonNull(this.databroker).registerDataTreeChangeListener(treeId, this);
155     }
156
157     private static Map<TablesKey, LongAdder> buildPrefixesTables(final Set<TablesKey> tables) {
158         final ImmutableMap.Builder<TablesKey, LongAdder> b = ImmutableMap.builder();
159         tables.forEach(table -> b.put(table, new LongAdder()));
160         return b.build();
161     }
162
163     @Override
164     public synchronized void onDataTreeChanged(@Nonnull final Collection<DataTreeModification<Tables>> changes) {
165         if (this.chain == null) {
166             LOG.trace("Chain closed. Ignoring Changes : {}", changes);
167             return;
168         }
169
170         LOG.trace("Data changed called to effective RIB. Change : {}", changes);
171         if (!changes.isEmpty()) {
172             processModifications(changes);
173         }
174
175         //Refresh VPN Table if RT Memberships were updated
176         if (this.rtMembershipsUpdated) {
177             this.vpnTableRefresher.refreshTable(IVP4_VPN_TABLE_KEY, this.peerImportParameters.getFromPeerId());
178             this.vpnTableRefresher.refreshTable(IVP6_VPN_TABLE_KEY, this.peerImportParameters.getFromPeerId());
179             this.rtMembershipsUpdated = false;
180         }
181     }
182
183     @GuardedBy("this")
184     @SuppressWarnings("unchecked")
185     private void processModifications(final Collection<DataTreeModification<Tables>> changes) {
186         final WriteTransaction tx = this.chain.newWriteOnlyTransaction();
187         for (final DataTreeModification<Tables> tc : changes) {
188             final DataObjectModification<Tables> table = tc.getRootNode();
189             final DataObjectModification.ModificationType modificationType = table.getModificationType();
190             switch (modificationType) {
191                 case DELETE:
192                     final Tables removeTable = table.getDataBefore();
193                     final TablesKey tableKey = removeTable.key();
194                     final KeyedInstanceIdentifier<Tables, TablesKey> effectiveTablePath
195                             = this.effRibTables.child(Tables.class, tableKey);
196                     LOG.debug("Delete Effective Table {} modification type {}, "
197                             , effectiveTablePath, modificationType);
198                     tx.delete(LogicalDatastoreType.OPERATIONAL, effectiveTablePath);
199                     CountersUtil.decrement(this.prefixesInstalled.get(tableKey), tableKey);
200                     break;
201                 case SUBTREE_MODIFIED:
202                     final Tables before = table.getDataBefore();
203                     final Tables after = table.getDataAfter();
204                     final TablesKey tk = after.key();
205                     LOG.debug("Process table {} type {}, dataAfter {}, dataBefore {}",
206                             tk, modificationType, after, before);
207
208                     final KeyedInstanceIdentifier<Tables, TablesKey> tablePath
209                             = this.effRibTables.child(Tables.class, tk);
210                     final RIBSupport ribSupport = this.registry.getRIBSupport(tk);
211                     if (ribSupport == null) {
212                         break;
213                     }
214
215                     final DataObjectModification<org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp
216                         .rib.rev180329.rib.tables.Attributes> adjRibAttrsChanged = table.getModifiedChildContainer(
217                             org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329
218                                 .rib.tables.Attributes.class);
219                     if (adjRibAttrsChanged != null) {
220                         tx.put(LogicalDatastoreType.OPERATIONAL,
221                             tablePath.child(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp
222                                 .rib.rev180329.rib.tables.Attributes.class), adjRibAttrsChanged.getDataAfter());
223                     }
224
225                     final DataObjectModification routesChangesContainer = table.getModifiedChildContainer(
226                         ribSupport.routesCaseClass(), ribSupport.routesContainerClass());
227
228                     if (routesChangesContainer == null) {
229                         break;
230                     }
231                     updateRoutes(tx, tk, ribSupport, tablePath, routesChangesContainer.getModifiedChildren());
232                     break;
233                 case WRITE:
234                     writeTable(tx, table);
235                     break;
236                 default:
237                     LOG.warn("Ignoring unhandled root {}", table);
238                     break;
239             }
240         }
241
242         final FluentFuture<? extends CommitInfo> future = tx.commit();
243         this.submitted = future;
244         future.addCallback(new FutureCallback<CommitInfo>() {
245             @Override
246             public void onSuccess(final CommitInfo result) {
247                 LOG.trace("Successful commit");
248             }
249
250             @Override
251             public void onFailure(final Throwable trw) {
252                 LOG.error("Failed commit", trw);
253             }
254         }, MoreExecutors.directExecutor());
255     }
256
257     @SuppressWarnings("unchecked")
258     private <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>,
259         R extends Route & ChildOf<? super S> & Identifiable<I>, I extends Identifier<R>> void updateRoutes(
260             final WriteTransaction tx,
261             final TablesKey tableKey, final RIBSupport<C, S, R, I> ribSupport,
262             final KeyedInstanceIdentifier<Tables, TablesKey> tablePath,
263             final Collection<DataObjectModification<R>> routeChanges) {
264         for (final DataObjectModification<R> routeChanged : routeChanges) {
265             final PathArgument routeChangeId = routeChanged.getIdentifier();
266             verify(routeChangeId instanceof IdentifiableItem, "Route change %s has invalid identifier %s",
267                 routeChanged, routeChangeId);
268             final I routeKey = ((IdentifiableItem<R, I>) routeChangeId).getKey();
269
270             switch (routeChanged.getModificationType()) {
271                 case SUBTREE_MODIFIED:
272                 case WRITE:
273                     writeRoutes(tx, tableKey, ribSupport, tablePath, routeKey, routeChanged.getDataAfter(), false);
274                     break;
275                 case DELETE:
276                     final InstanceIdentifier<R> routeIID = ribSupport.createRouteIdentifier(tablePath, routeKey);
277                     deleteRoutes(routeIID, routeChanged.getDataBefore(), tx);
278                     break;
279             }
280         }
281     }
282
283     private <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>,
284             R extends Route & ChildOf<? super S> & Identifiable<I>, I extends Identifier<R>> void writeRoutes(
285             final WriteTransaction tx, final TablesKey tk, final RIBSupport<C, S, R, I> ribSupport,
286             final KeyedInstanceIdentifier<Tables, TablesKey> tablePath, final I routeKey,
287             final R route, final boolean longLivedStale) {
288         final InstanceIdentifier<R> routeIID = ribSupport.createRouteIdentifier(tablePath, routeKey);
289         CountersUtil.increment(this.prefixesReceived.get(tk), tk);
290
291         final Attributes routeAttrs = route.getAttributes();
292         final Optional<Attributes> optEffAtt;
293         // In case we want to add LLGR_STALE we do not process route through policies since it may be
294         // considered as received with LLGR_STALE from peer which is not true.
295         if (longLivedStale) {
296             // LLGR procedures are in effect. If the route is tagged with NO_LLGR, it needs to be removed.
297             final List<Communities> effCommunities = routeAttrs.getCommunities();
298             if (effCommunities != null && effCommunities.contains(CommunityUtil.NO_LLGR)) {
299                 deleteRoutes(routeIID, route, tx);
300                 return;
301             }
302             optEffAtt = Optional.of(wrapLongLivedStale(routeAttrs));
303         } else {
304             optEffAtt = this.ribPolicies.applyImportPolicies(this.peerImportParameters, routeAttrs,
305                     tableTypeRegistry.getAfiSafiType(ribSupport.getTablesKey()).get());
306         }
307         if (!optEffAtt.isPresent()) {
308             deleteRoutes(routeIID, route, tx);
309             return;
310         }
311
312         final Optional<RouteTarget> rtMembership = RouteTargetMembeshipUtil.getRT(route);
313         if (rtMembership.isPresent()) {
314             final RouteTarget rt = rtMembership.get();
315             if (PeerRole.Ebgp != this.peerImportParameters.getFromPeerRole()) {
316                 this.rtCache.cacheRoute(route);
317             }
318             this.rtMemberships.add(rt);
319             this.rtMembershipsUpdated = true;
320         }
321         CountersUtil.increment(this.prefixesInstalled.get(tk), tk);
322         tx.put(LogicalDatastoreType.OPERATIONAL, routeIID, route);
323         tx.put(LogicalDatastoreType.OPERATIONAL, routeIID.child(Attributes.class), optEffAtt.get());
324     }
325
326     private static Attributes wrapLongLivedStale(final Attributes attrs) {
327         if (attrs == null) {
328             return STALE_LLGR_ATTRIBUTES;
329         }
330
331         final List<Communities> oldCommunities = attrs.getCommunities();
332         final List<Communities> newCommunities;
333         if (oldCommunities != null) {
334             if (oldCommunities.contains(StaleCommunities.STALE_LLGR)) {
335                 return attrs;
336             }
337             newCommunities = StaleCommunities.create(oldCommunities);
338         } else {
339             newCommunities = STALE_LLGR_COMMUNUTIES;
340         }
341
342         return new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329
343                 .path.attributes.AttributesBuilder(attrs).setCommunities(newCommunities).build();
344     }
345
346     private <R extends Route> void deleteRoutes(final InstanceIdentifier<R> routeIID,
347             final R route, final WriteTransaction tx) {
348         final Optional<RouteTarget> rtMembership = RouteTargetMembeshipUtil.getRT(route);
349         if (rtMembership.isPresent()) {
350             if(PeerRole.Ebgp != this.peerImportParameters.getFromPeerRole()) {
351                 this.rtCache.uncacheRoute(route);
352             }
353             this.rtMemberships.remove(rtMembership.get());
354             this.rtMembershipsUpdated = true;
355         }
356         tx.delete(LogicalDatastoreType.OPERATIONAL, routeIID);
357     }
358
359     @SuppressWarnings("unchecked")
360     private void writeTable(final WriteTransaction tx, final DataObjectModification<Tables> table) {
361         final Tables newTable = table.getDataAfter();
362         if (newTable == null) {
363             return;
364         }
365         final TablesKey tableKey = newTable.key();
366         final KeyedInstanceIdentifier<Tables, TablesKey> tablePath
367                 = this.effRibTables.child(Tables.class, tableKey);
368
369         // Create an empty table
370         LOG.trace("Create Empty table at {}", tablePath);
371         if (table.getDataBefore() == null) {
372             tx.put(LogicalDatastoreType.OPERATIONAL, tablePath, new TablesBuilder()
373                     .withKey(tableKey).setAfi(tableKey.getAfi()).setSafi(tableKey.getSafi())
374                     .setAttributes(newTable.getAttributes()).build());
375         }
376
377         final RIBSupport ribSupport = this.registry.getRIBSupport(tableKey);
378         final Routes routes = newTable.getRoutes();
379         if (ribSupport == null || routes == null) {
380             return;
381         }
382
383         final DataObjectModification routesChangesContainer =
384                 table.getModifiedChildContainer(ribSupport.routesCaseClass(), ribSupport.routesContainerClass());
385
386         if (routesChangesContainer == null) {
387             return;
388         }
389         updateRoutes(tx, tableKey, ribSupport, tablePath, routesChangesContainer.getModifiedChildren());
390     }
391
392     @Override
393     public synchronized void close() {
394         if (this.reg != null) {
395             this.reg.close();
396             this.reg = null;
397         }
398         if (this.submitted != null) {
399             try {
400                 this.submitted.get();
401             } catch (final InterruptedException | ExecutionException throwable) {
402                 LOG.error("Write routes failed", throwable);
403             }
404         }
405         if (this.chain != null) {
406             this.chain.close();
407             this.chain = null;
408         }
409         this.prefixesReceived.values().forEach(LongAdder::reset);
410         this.prefixesInstalled.values().forEach(LongAdder::reset);
411     }
412
413     @Override
414     public long getPrefixedReceivedCount(final TablesKey tablesKey) {
415         final LongAdder counter = this.prefixesReceived.get(tablesKey);
416         if (counter == null) {
417             return 0;
418         }
419         return counter.longValue();
420     }
421
422     @Override
423     public Set<TablesKey> getTableKeys() {
424         return ImmutableSet.copyOf(this.prefixesReceived.keySet());
425     }
426
427     @Override
428     public boolean isSupported(final TablesKey tablesKey) {
429         return this.prefixesReceived.containsKey(tablesKey);
430     }
431
432     @Override
433     public long getPrefixedInstalledCount(final TablesKey tablesKey) {
434         final LongAdder counter = this.prefixesInstalled.get(tablesKey);
435         if (counter == null) {
436             return 0;
437         }
438         return counter.longValue();
439     }
440
441     @Override
442     public long getTotalPrefixesInstalled() {
443         return this.prefixesInstalled.values().stream().mapToLong(LongAdder::longValue).sum();
444     }
445 }