2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.protocol.bgp.rib.impl;
10 import static com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
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;
22 import java.util.Optional;
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;
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.
84 * Inbound policy is applied as follows:
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
92 final class EffectiveRibInWriter implements PrefixesReceivedCounters, PrefixesInstalledCounters,
93 AutoCloseable, ClusteredDataTreeChangeListener<Tables> {
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)
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;
123 private FluentFuture<? extends CommitInfo> submitted;
124 private boolean rtMembershipsUpdated;
126 EffectiveRibInWriter(
127 final BGPRouteEntryImportParameters peer,
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;
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);
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()));
164 public synchronized void onDataTreeChanged(@Nonnull final Collection<DataTreeModification<Tables>> changes) {
165 if (this.chain == null) {
166 LOG.trace("Chain closed. Ignoring Changes : {}", changes);
170 LOG.trace("Data changed called to effective RIB. Change : {}", changes);
171 if (!changes.isEmpty()) {
172 processModifications(changes);
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;
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) {
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);
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);
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) {
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());
225 final DataObjectModification routesChangesContainer = table.getModifiedChildContainer(
226 ribSupport.routesCaseClass(), ribSupport.routesContainerClass());
228 if (routesChangesContainer == null) {
231 updateRoutes(tx, tk, ribSupport, tablePath, routesChangesContainer.getModifiedChildren());
234 writeTable(tx, table);
237 LOG.warn("Ignoring unhandled root {}", table);
242 final FluentFuture<? extends CommitInfo> future = tx.commit();
243 this.submitted = future;
244 future.addCallback(new FutureCallback<CommitInfo>() {
246 public void onSuccess(final CommitInfo result) {
247 LOG.trace("Successful commit");
251 public void onFailure(final Throwable trw) {
252 LOG.error("Failed commit", trw);
254 }, MoreExecutors.directExecutor());
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();
270 switch (routeChanged.getModificationType()) {
271 case SUBTREE_MODIFIED:
273 writeRoutes(tx, tableKey, ribSupport, tablePath, routeKey, routeChanged.getDataAfter(), false);
276 final InstanceIdentifier<R> routeIID = ribSupport.createRouteIdentifier(tablePath, routeKey);
277 deleteRoutes(routeIID, routeChanged.getDataBefore(), tx);
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);
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);
302 optEffAtt = Optional.of(wrapLongLivedStale(routeAttrs));
304 optEffAtt = this.ribPolicies.applyImportPolicies(this.peerImportParameters, routeAttrs,
305 tableTypeRegistry.getAfiSafiType(ribSupport.getTablesKey()).get());
307 if (!optEffAtt.isPresent()) {
308 deleteRoutes(routeIID, route, tx);
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);
318 this.rtMemberships.add(rt);
319 this.rtMembershipsUpdated = true;
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());
326 private static Attributes wrapLongLivedStale(final Attributes attrs) {
328 return STALE_LLGR_ATTRIBUTES;
331 final List<Communities> oldCommunities = attrs.getCommunities();
332 final List<Communities> newCommunities;
333 if (oldCommunities != null) {
334 if (oldCommunities.contains(StaleCommunities.STALE_LLGR)) {
337 newCommunities = StaleCommunities.create(oldCommunities);
339 newCommunities = STALE_LLGR_COMMUNUTIES;
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();
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);
353 this.rtMemberships.remove(rtMembership.get());
354 this.rtMembershipsUpdated = true;
356 tx.delete(LogicalDatastoreType.OPERATIONAL, routeIID);
359 @SuppressWarnings("unchecked")
360 private void writeTable(final WriteTransaction tx, final DataObjectModification<Tables> table) {
361 final Tables newTable = table.getDataAfter();
362 if (newTable == null) {
365 final TablesKey tableKey = newTable.key();
366 final KeyedInstanceIdentifier<Tables, TablesKey> tablePath
367 = this.effRibTables.child(Tables.class, tableKey);
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());
377 final RIBSupport ribSupport = this.registry.getRIBSupport(tableKey);
378 final Routes routes = newTable.getRoutes();
379 if (ribSupport == null || routes == null) {
383 final DataObjectModification routesChangesContainer =
384 table.getModifiedChildContainer(ribSupport.routesCaseClass(), ribSupport.routesContainerClass());
386 if (routesChangesContainer == null) {
389 updateRoutes(tx, tableKey, ribSupport, tablePath, routesChangesContainer.getModifiedChildren());
393 public synchronized void close() {
394 if (this.reg != null) {
398 if (this.submitted != null) {
400 this.submitted.get();
401 } catch (final InterruptedException | ExecutionException throwable) {
402 LOG.error("Write routes failed", throwable);
405 if (this.chain != null) {
409 this.prefixesReceived.values().forEach(LongAdder::reset);
410 this.prefixesInstalled.values().forEach(LongAdder::reset);
414 public long getPrefixedReceivedCount(final TablesKey tablesKey) {
415 final LongAdder counter = this.prefixesReceived.get(tablesKey);
416 if (counter == null) {
419 return counter.longValue();
423 public Set<TablesKey> getTableKeys() {
424 return ImmutableSet.copyOf(this.prefixesReceived.keySet());
428 public boolean isSupported(final TablesKey tablesKey) {
429 return this.prefixesReceived.containsKey(tablesKey);
433 public long getPrefixedInstalledCount(final TablesKey tablesKey) {
434 final LongAdder counter = this.prefixesInstalled.get(tablesKey);
435 if (counter == null) {
438 return counter.longValue();
442 public long getTotalPrefixesInstalled() {
443 return this.prefixesInstalled.values().stream().mapToLong(LongAdder::longValue).sum();