/* * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.protocol.bgp.rib.impl; import static com.google.common.base.Verify.verify; import static java.util.Objects.requireNonNull; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.FluentFuture; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.MoreExecutors; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.LongAdder; import javax.annotation.Nonnull; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.NotThreadSafe; import org.opendaylight.controller.md.sal.binding.api.BindingTransactionChain; import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener; import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.controller.md.sal.binding.api.DataObjectModification; import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier; import org.opendaylight.controller.md.sal.binding.api.DataTreeModification; import org.opendaylight.controller.md.sal.binding.api.WriteTransaction; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.mdsal.common.api.CommitInfo; import org.opendaylight.protocol.bgp.openconfig.spi.BGPTableTypeRegistryConsumer; import org.opendaylight.protocol.bgp.parser.impl.message.update.CommunityUtil; import org.opendaylight.protocol.bgp.rib.impl.spi.RIB; import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContextRegistry; import org.opendaylight.protocol.bgp.rib.impl.spi.RibOutRefresh; import org.opendaylight.protocol.bgp.rib.impl.state.peer.PrefixesInstalledCounters; import org.opendaylight.protocol.bgp.rib.impl.state.peer.PrefixesReceivedCounters; import org.opendaylight.protocol.bgp.rib.spi.RIBSupport; import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRibRoutingPolicy; import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRouteEntryImportParameters; import org.opendaylight.protocol.bgp.route.targetcontrain.spi.ClientRouteTargetContrainCache; import org.opendaylight.protocol.bgp.route.targetcontrain.spi.RouteTargetMembeshipUtil; import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.types.rev151009.AfiSafiType; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.path.attributes.Attributes; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.path.attributes.attributes.Communities; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.PeerRole; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.Route; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.Peer; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.PeerKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.peer.AdjRibIn; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.peer.EffectiveRibIn; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.tables.Routes; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.Ipv4AddressFamily; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.Ipv6AddressFamily; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.MplsLabeledVpnSubsequentAddressFamily; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.RouteTarget; import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.yang.binding.ChildOf; import org.opendaylight.yangtools.yang.binding.ChoiceIn; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.Identifiable; import org.opendaylight.yangtools.yang.binding.Identifier; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.IdentifiableItem; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implementation of the BGP import policy. Listens on peer's Adj-RIB-In, inspects all inbound * routes in the context of the advertising peer's role and applies the inbound policy. * *

* Inbound policy is applied as follows: * *

* 1) if the peer is an eBGP peer, perform attribute replacement and filtering * 2) check if a route is admissible based on attributes attached to it, as well as the * advertising peer's role * 3) output admitting routes with edited attributes into /bgp-rib/rib/peer/effective-rib-in/tables/routes */ @NotThreadSafe final class EffectiveRibInWriter implements PrefixesReceivedCounters, PrefixesInstalledCounters, AutoCloseable, ClusteredDataTreeChangeListener { private static final Logger LOG = LoggerFactory.getLogger(EffectiveRibInWriter.class); static final NodeIdentifier TABLE_ROUTES = new NodeIdentifier(Routes.QNAME); private static final TablesKey IVP4_VPN_TABLE_KEY = new TablesKey(Ipv4AddressFamily.class, MplsLabeledVpnSubsequentAddressFamily.class); private static final TablesKey IVP6_VPN_TABLE_KEY = new TablesKey(Ipv6AddressFamily.class, MplsLabeledVpnSubsequentAddressFamily.class); private static final ImmutableList STALE_LLGR_COMMUNUTIES = ImmutableList.of( StaleCommunities.STALE_LLGR); private static final Attributes STALE_LLGR_ATTRIBUTES = new org.opendaylight.yang.gen.v1.urn.opendaylight.params .xml.ns.yang.bgp.message.rev180329.path.attributes.AttributesBuilder() .setCommunities(STALE_LLGR_COMMUNUTIES) .build(); private final RIBSupportContextRegistry registry; private final KeyedInstanceIdentifier peerIId; private final InstanceIdentifier effRibTables; private final DataBroker databroker; private final List rtMemberships; private final RibOutRefresh vpnTableRefresher; private final ClientRouteTargetContrainCache rtCache; private ListenerRegistration reg; private BindingTransactionChain chain; private final Map prefixesReceived; private final Map prefixesInstalled; private final BGPRibRoutingPolicy ribPolicies; private final BGPRouteEntryImportParameters peerImportParameters; private final BGPTableTypeRegistryConsumer tableTypeRegistry; @GuardedBy("this") private FluentFuture submitted; private boolean rtMembershipsUpdated; EffectiveRibInWriter( final BGPRouteEntryImportParameters peer, final RIB rib, final BindingTransactionChain chain, final KeyedInstanceIdentifier peerIId, final Set tables, final BGPTableTypeRegistryConsumer tableTypeRegistry, final List rtMemberships, final ClientRouteTargetContrainCache rtCache) { this.registry = requireNonNull(rib.getRibSupportContext()); this.chain = requireNonNull(chain); this.peerIId = requireNonNull(peerIId); this.effRibTables = this.peerIId.child(EffectiveRibIn.class); this.prefixesInstalled = buildPrefixesTables(tables); this.prefixesReceived = buildPrefixesTables(tables); this.ribPolicies = requireNonNull(rib.getRibPolicies()); this.databroker = requireNonNull(rib.getDataBroker()); this.tableTypeRegistry = requireNonNull(tableTypeRegistry); this.peerImportParameters = peer; this.rtMemberships = rtMemberships; this.rtCache = rtCache; this.vpnTableRefresher = rib; } public void init() { final DataTreeIdentifier treeId = new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL, this.peerIId.child(AdjRibIn.class).child(Tables.class)); LOG.debug("Registered Effective RIB on {}", this.peerIId); this.reg = requireNonNull(this.databroker).registerDataTreeChangeListener(treeId, this); } private static Map buildPrefixesTables(final Set tables) { final ImmutableMap.Builder b = ImmutableMap.builder(); tables.forEach(table -> b.put(table, new LongAdder())); return b.build(); } @Override public synchronized void onDataTreeChanged(@Nonnull final Collection> changes) { if (this.chain == null) { LOG.trace("Chain closed. Ignoring Changes : {}", changes); return; } LOG.trace("Data changed called to effective RIB. Change : {}", changes); if (!changes.isEmpty()) { processModifications(changes); } //Refresh VPN Table if RT Memberships were updated if (this.rtMembershipsUpdated) { this.vpnTableRefresher.refreshTable(IVP4_VPN_TABLE_KEY, this.peerImportParameters.getFromPeerId()); this.vpnTableRefresher.refreshTable(IVP6_VPN_TABLE_KEY, this.peerImportParameters.getFromPeerId()); this.rtMembershipsUpdated = false; } } @GuardedBy("this") @SuppressWarnings("unchecked") private void processModifications(final Collection> changes) { final WriteTransaction tx = this.chain.newWriteOnlyTransaction(); for (final DataTreeModification tc : changes) { final DataObjectModification table = tc.getRootNode(); final DataObjectModification.ModificationType modificationType = table.getModificationType(); switch (modificationType) { case DELETE: final Tables removeTable = table.getDataBefore(); final TablesKey tableKey = removeTable.key(); final KeyedInstanceIdentifier effectiveTablePath = this.effRibTables .child(Tables.class, tableKey); LOG.debug("Delete Effective Table {} modification type {}, ", effectiveTablePath, modificationType); tx.delete(LogicalDatastoreType.OPERATIONAL, effectiveTablePath); CountersUtil.decrement(this.prefixesInstalled.get(tableKey), tableKey); break; case SUBTREE_MODIFIED: final Tables before = table.getDataBefore(); final Tables after = table.getDataAfter(); final TablesKey tk = after.key(); LOG.debug("Process table {} type {}, dataAfter {}, dataBefore {}", tk, modificationType, after, before); final KeyedInstanceIdentifier tablePath = this.effRibTables.child(Tables.class, tk); final RIBSupport ribSupport = this.registry.getRIBSupport(tk); if (ribSupport == null) { break; } final DataObjectModification adjRibAttrsChanged = table.getModifiedChildContainer( org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329 .rib.tables.Attributes.class); if (adjRibAttrsChanged != null) { tx.put(LogicalDatastoreType.OPERATIONAL, tablePath.child(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp .rib.rev180329.rib.tables.Attributes.class), adjRibAttrsChanged.getDataAfter()); } final DataObjectModification routesChangesContainer = table.getModifiedChildContainer( ribSupport.routesCaseClass(), ribSupport.routesContainerClass()); if (routesChangesContainer == null) { break; } updateRoutes(tx, tk, ribSupport, tablePath, routesChangesContainer.getModifiedChildren()); break; case WRITE: writeTable(tx, table); break; default: LOG.warn("Ignoring unhandled root {}", table); break; } } final FluentFuture future = tx.commit(); this.submitted = future; future.addCallback(new FutureCallback() { @Override public void onSuccess(final CommitInfo result) { LOG.trace("Successful commit"); } @Override public void onFailure(final Throwable trw) { LOG.error("Failed commit", trw); } }, MoreExecutors.directExecutor()); } @SuppressWarnings("unchecked") private , S extends ChildOf, R extends Route & ChildOf & Identifiable, I extends Identifier> void updateRoutes( final WriteTransaction tx, final TablesKey tableKey, final RIBSupport ribSupport, final KeyedInstanceIdentifier tablePath, final Collection> routeChanges) { Class afiSafiType = null; for (final DataObjectModification routeChanged : routeChanges) { final PathArgument routeChangeId = routeChanged.getIdentifier(); verify(routeChangeId instanceof IdentifiableItem, "Route change %s has invalid identifier %s", routeChanged, routeChangeId); final I routeKey = ((IdentifiableItem) routeChangeId).getKey(); switch (routeChanged.getModificationType()) { case SUBTREE_MODIFIED: case WRITE: if (afiSafiType == null) { afiSafiType = tableTypeRegistry.getAfiSafiType(ribSupport.getTablesKey()).get(); } writeRoutes(tx, tableKey, afiSafiType, ribSupport, tablePath, routeKey, routeChanged.getDataAfter(), false); break; case DELETE: final InstanceIdentifier routeIID = ribSupport.createRouteIdentifier(tablePath, routeKey); deleteRoutes(routeIID, routeChanged.getDataBefore(), tx); break; } } } private , S extends ChildOf, R extends Route & ChildOf & Identifiable, I extends Identifier> void writeRoutes( final WriteTransaction tx, final TablesKey tk, final Class afiSafiType, final RIBSupport ribSupport, final KeyedInstanceIdentifier tablePath, final I routeKey, final R route, final boolean longLivedStale) { final InstanceIdentifier routeIID = ribSupport.createRouteIdentifier(tablePath, routeKey); CountersUtil.increment(this.prefixesReceived.get(tk), tk); final Attributes routeAttrs = route.getAttributes(); final Optional optEffAtt; // In case we want to add LLGR_STALE we do not process route through policies since it may be // considered as received with LLGR_STALE from peer which is not true. if (longLivedStale) { // LLGR procedures are in effect. If the route is tagged with NO_LLGR, it needs to be removed. final List effCommunities = routeAttrs.getCommunities(); if (effCommunities != null && effCommunities.contains(CommunityUtil.NO_LLGR)) { deleteRoutes(routeIID, route, tx); return; } optEffAtt = Optional.of(wrapLongLivedStale(routeAttrs)); } else { optEffAtt = this.ribPolicies.applyImportPolicies(this.peerImportParameters, routeAttrs, afiSafiType); } if (!optEffAtt.isPresent()) { deleteRoutes(routeIID, route, tx); return; } final Optional rtMembership = RouteTargetMembeshipUtil.getRT(route); if (rtMembership.isPresent()) { final RouteTarget rt = rtMembership.get(); if (PeerRole.Ebgp != this.peerImportParameters.getFromPeerRole()) { this.rtCache.cacheRoute(route); } this.rtMemberships.add(rt); this.rtMembershipsUpdated = true; } CountersUtil.increment(this.prefixesInstalled.get(tk), tk); tx.put(LogicalDatastoreType.OPERATIONAL, routeIID, route); tx.put(LogicalDatastoreType.OPERATIONAL, routeIID.child(Attributes.class), optEffAtt.get()); } private static Attributes wrapLongLivedStale(final Attributes attrs) { if (attrs == null) { return STALE_LLGR_ATTRIBUTES; } final List oldCommunities = attrs.getCommunities(); final List newCommunities; if (oldCommunities != null) { if (oldCommunities.contains(StaleCommunities.STALE_LLGR)) { return attrs; } newCommunities = StaleCommunities.create(oldCommunities); } else { newCommunities = STALE_LLGR_COMMUNUTIES; } return new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329 .path.attributes.AttributesBuilder(attrs).setCommunities(newCommunities).build(); } private void deleteRoutes(final InstanceIdentifier routeIID, final R route, final WriteTransaction tx) { deleteRT(route); tx.delete(LogicalDatastoreType.OPERATIONAL, routeIID); } private , S extends ChildOf, R extends Route & ChildOf & Identifiable, I extends Identifier> void writeTable( final WriteTransaction tx, final DataObjectModification table) { final Tables newTable = table.getDataAfter(); if (newTable == null) { final Tables oldTable = table.getDataBefore(); if (oldTable != null) { final TablesKey tableKey = oldTable.key(); final KeyedInstanceIdentifier tablePath = tablePath(tableKey); LOG.trace("Delete table at {}", tablePath); tx.delete(LogicalDatastoreType.OPERATIONAL, tablePath); final RIBSupport ribSupport = this.registry.getRIBSupport(tableKey); if (ribSupport != null) { final Routes oldRoutes = oldTable.getRoutes(); if (oldRoutes != null) { for (R route : ribSupport.extractAdjRibInRoutes(oldRoutes)) { deleteRT(route); } } } } return; } final TablesKey tableKey = newTable.key(); final KeyedInstanceIdentifier tablePath = tablePath(tableKey); // Create an empty table LOG.trace("Create Empty table at {}", tablePath); tx.put(LogicalDatastoreType.OPERATIONAL, tablePath, new TablesBuilder() .withKey(tableKey).setAfi(tableKey.getAfi()).setSafi(tableKey.getSafi()) .setAttributes(newTable.getAttributes()).build()); final RIBSupport ribSupport = this.registry.getRIBSupport(tableKey); if (ribSupport == null) { LOG.trace("No RIB support for {}", tableKey); return; } writeTableRoutes(tx, tableKey, ribSupport, tablePath, newTable); } private , S extends ChildOf, R extends Route & ChildOf & Identifiable, I extends Identifier> void writeTableRoutes( final WriteTransaction tx, final TablesKey tableKey, final RIBSupport ribSupport, final KeyedInstanceIdentifier tablePath, final Tables newTable) { final Routes routes = newTable.getRoutes(); if (routes != null) { final Class afiSafiType = tableTypeRegistry.getAfiSafiType(ribSupport.getTablesKey()) .get(); for (R route : ribSupport.extractAdjRibInRoutes(routes)) { writeRoutes(tx, tableKey, afiSafiType, ribSupport, tablePath, route.key(), route, false); } } } private KeyedInstanceIdentifier tablePath(TablesKey tableKey) { return this.effRibTables.child(Tables.class, tableKey); } @Override public synchronized void close() { if (this.reg != null) { this.reg.close(); this.reg = null; } if (this.submitted != null) { try { this.submitted.get(); } catch (final InterruptedException | ExecutionException throwable) { LOG.error("Write routes failed", throwable); } } if (this.chain != null) { this.chain.close(); this.chain = null; } this.prefixesReceived.values().forEach(LongAdder::reset); this.prefixesInstalled.values().forEach(LongAdder::reset); } @Override public long getPrefixedReceivedCount(final TablesKey tablesKey) { final LongAdder counter = this.prefixesReceived.get(tablesKey); if (counter == null) { return 0; } return counter.longValue(); } @Override public Set getTableKeys() { return ImmutableSet.copyOf(this.prefixesReceived.keySet()); } @Override public boolean isSupported(final TablesKey tablesKey) { return this.prefixesReceived.containsKey(tablesKey); } @Override public long getPrefixedInstalledCount(final TablesKey tablesKey) { final LongAdder counter = this.prefixesInstalled.get(tablesKey); if (counter == null) { return 0; } return counter.longValue(); } @Override public long getTotalPrefixesInstalled() { return this.prefixesInstalled.values().stream().mapToLong(LongAdder::longValue).sum(); } private void deleteRT(final Route route) { final Optional rtMembership = RouteTargetMembeshipUtil.getRT(route); if (rtMembership.isPresent()) { if (PeerRole.Ebgp != this.peerImportParameters.getFromPeerRole()) { this.rtCache.uncacheRoute(route); } this.rtMemberships.remove(rtMembership.get()); this.rtMembershipsUpdated = true; } } }