/* * 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 java.util.Objects.requireNonNull; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.util.Collection; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.LongAdder; import javax.annotation.Nonnull; 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.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.protocol.bgp.rib.impl.spi.RIB; import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContextRegistry; 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.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev171207.path.attributes.Attributes; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.Route; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.bgp.rib.rib.Peer; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.bgp.rib.rib.PeerKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.bgp.rib.rib.peer.AdjRibIn; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.bgp.rib.rib.peer.EffectiveRibIn; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.rib.Tables; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.rib.TablesBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.rib.TablesKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.rib.tables.Routes; import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.Identifier; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; 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 { static final NodeIdentifier TABLE_ROUTES = new NodeIdentifier(Routes.QNAME); private static final Logger LOG = LoggerFactory.getLogger(EffectiveRibInWriter.class); private final AdjInTracker adjInTracker; private EffectiveRibInWriter(final BGPRouteEntryImportParameters peer, final RIB rib, final BindingTransactionChain chain, final KeyedInstanceIdentifier peerIId, final Set tables) { this.adjInTracker = new AdjInTracker(peer, rib, chain, peerIId, tables); } static EffectiveRibInWriter create(final BGPRouteEntryImportParameters peer, @Nonnull final RIB rib, @Nonnull final BindingTransactionChain chain, @Nonnull final KeyedInstanceIdentifier peerIId, @Nonnull final Set tables) { return new EffectiveRibInWriter(peer, rib, chain, peerIId, tables); } @Override public void close() { this.adjInTracker.close(); } @Override public long getPrefixedReceivedCount(final TablesKey tablesKey) { return this.adjInTracker.getPrefixedReceivedCount(tablesKey); } @Override public Set getTableKeys() { return this.adjInTracker.getTableKeys(); } @Override public boolean isSupported(final TablesKey tablesKey) { return this.adjInTracker.isSupported(tablesKey); } @Override public long getPrefixedInstalledCount(@Nonnull final TablesKey tablesKey) { return this.adjInTracker.getPrefixedInstalledCount(tablesKey); } @Override public long getTotalPrefixesInstalled() { return this.adjInTracker.getTotalPrefixesInstalled(); } private static final class AdjInTracker implements PrefixesReceivedCounters, PrefixesInstalledCounters, AutoCloseable, ClusteredDataTreeChangeListener { private final RIBSupportContextRegistry registry; private final KeyedInstanceIdentifier peerIId; private final InstanceIdentifier effRibTables; private final ListenerRegistration reg; private final BindingTransactionChain chain; private final Map prefixesReceived; private final Map prefixesInstalled; private final BGPRibRoutingPolicy ribPolicies; private final BGPRouteEntryImportParameters peerImportParameters; @SuppressWarnings("unchecked") AdjInTracker(final BGPRouteEntryImportParameters peer, final RIB rib, final BindingTransactionChain chain, final KeyedInstanceIdentifier peerIId, @Nonnull final Set tables) { 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.peerImportParameters = peer; 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(rib.getDataBroker()).registerDataTreeChangeListener(treeId, this); } private Map buildPrefixesTables(final Set tables) { final ImmutableMap.Builder b = ImmutableMap.builder(); tables.forEach(table -> b.put(table, new LongAdder())); return b.build(); } @Override @SuppressWarnings("unchecked") public void onDataTreeChanged(@Nonnull final Collection> changes) { LOG.trace("Data changed called to effective RIB. Change : {}", changes); WriteTransaction tx = null; for (final DataTreeModification tc : changes) { final DataObjectModification table = tc.getRootNode(); if (tx == null) { tx = this.chain.newWriteOnlyTransaction(); } final DataObjectModification.ModificationType modificationType = table.getModificationType(); switch (modificationType) { case DELETE: final Tables removeTable = table.getDataBefore(); final TablesKey tableKey = removeTable.getKey(); 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.getKey(); 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); tx.put(LogicalDatastoreType.OPERATIONAL, tablePath.child(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp .rib.rev171207.rib.tables.Attributes.class), after.getAttributes()); final DataObjectModification routesChangesContainer = table.getModifiedChildContainer(ribSupport.routesContainerClass()); if (routesChangesContainer == null) { return; } updateRoutes(tx, tk, ribSupport, tablePath, routesChangesContainer.getModifiedChildren()); break; case WRITE: writeTable(tx, table); break; default: LOG.warn("Ignoring unhandled root {}", table); break; } } if (tx != null) { tx.submit(); } } @SuppressWarnings("unchecked") private void updateRoutes( final WriteTransaction tx, final TablesKey tableKey, final RIBSupport ribSupport, final KeyedInstanceIdentifier tablePath, final Collection> routeChanges) { for (final DataObjectModification routeChanged : routeChanges) { final Identifier routeKey = ((InstanceIdentifier.IdentifiableItem) routeChanged.getIdentifier()).getKey(); switch (routeChanged.getModificationType()) { case SUBTREE_MODIFIED: case WRITE: writeRoutes(tx, tableKey, ribSupport, tablePath, routeKey, (Route) routeChanged.getDataAfter()); break; case DELETE: final InstanceIdentifier routeIID = ribSupport.createRouteIId(tablePath, routeKey); tx.delete(LogicalDatastoreType.OPERATIONAL, routeIID); break; } } } @SuppressWarnings("unchecked") private void writeRoutes(final WriteTransaction tx, final TablesKey tk, final RIBSupport ribSupport, final KeyedInstanceIdentifier tablePath, final Identifier routeKey, final Route route) { final InstanceIdentifier routeIID = ribSupport.createRouteIId(tablePath, routeKey); CountersUtil.increment(this.prefixesReceived.get(tk), tk); final Optional effAtt = this.ribPolicies .applyImportPolicies(this.peerImportParameters, route.getAttributes()); if (effAtt.isPresent()) { CountersUtil.increment(this.prefixesInstalled.get(tk), tk); tx.put(LogicalDatastoreType.OPERATIONAL, routeIID, route, true); tx.put(LogicalDatastoreType.OPERATIONAL, routeIID.child(Attributes.class), effAtt.get()); } else { tx.delete(LogicalDatastoreType.OPERATIONAL, routeIID); } } @SuppressWarnings("unchecked") private void writeTable(final WriteTransaction tx, final DataObjectModification table) { final Tables newTable = table.getDataAfter(); if (newTable == null) { return; } final TablesKey tableKey = newTable.getKey(); final KeyedInstanceIdentifier tablePath = this.effRibTables.child(Tables.class, tableKey); // Create an empty table LOG.trace("Create Empty table", tablePath); tx.put(LogicalDatastoreType.OPERATIONAL, tablePath, new TablesBuilder() .setAfi(tableKey.getAfi()).setSafi(tableKey.getSafi()) .setAttributes(newTable.getAttributes()).build()); final RIBSupport ribSupport = this.registry.getRIBSupport(tableKey); final Routes routes = newTable.getRoutes(); if (routes == null) { return; } final DataObjectModification routesChangesContainer = table.getModifiedChildContainer(ribSupport.routesContainerClass()); if (routesChangesContainer == null) { return; } updateRoutes(tx, tableKey, ribSupport, tablePath, routesChangesContainer.getModifiedChildren()); } @Override public synchronized void close() { this.reg.close(); 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(); } } }