/* * 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.primitives.UnsignedInteger; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.MoreExecutors; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; 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.mode.api.PathSelectionMode; import org.opendaylight.protocol.bgp.mode.api.RouteEntry; import org.opendaylight.protocol.bgp.rib.impl.state.rib.TotalPathsCounter; import org.opendaylight.protocol.bgp.rib.impl.state.rib.TotalPrefixesCounter; import org.opendaylight.protocol.bgp.rib.spi.BGPPeerTracker; import org.opendaylight.protocol.bgp.rib.spi.RIBSupport; import org.opendaylight.protocol.bgp.rib.spi.RouterIds; import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRibRoutingPolicy; import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.types.rev151009.AfiSafiType; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.AsNumber; 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; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.RibKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.LocRib; 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.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.TablesKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.tables.Attributes; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.tables.AttributesBuilder; import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @NotThreadSafe final class LocRibWriter implements AutoCloseable, TotalPrefixesCounter, TotalPathsCounter, ClusteredDataTreeChangeListener { private static final Logger LOG = LoggerFactory.getLogger(LocRibWriter.class); private final Map routeEntries = new HashMap<>(); private final Long ourAs; private final RIBSupport ribSupport; private final DataBroker dataBroker; private final PathSelectionMode pathSelectionMode; private final LongAdder totalPathsCounter = new LongAdder(); private final LongAdder totalPrefixesCounter = new LongAdder(); private final RouteEntryDependenciesContainerImpl entryDep; private final BGPPeerTracker peerTracker; private final KeyedInstanceIdentifier ribIId; private final TablesKey tk; private final KeyedInstanceIdentifier locRibTableIID; private BindingTransactionChain chain; @GuardedBy("this") private ListenerRegistration reg; private LocRibWriter(final RIBSupport ribSupport, final BindingTransactionChain chain, final KeyedInstanceIdentifier ribIId, final Long ourAs, final DataBroker dataBroker, final BGPRibRoutingPolicy ribPolicies, final BGPPeerTracker peerTracker, final TablesKey tablesKey, final Class afiSafiType, final PathSelectionMode pathSelectionMode) { this.chain = requireNonNull(chain); this.ribIId = requireNonNull(ribIId); this.tk = requireNonNull(tablesKey); this.locRibTableIID = ribIId.child(LocRib.class).child(Tables.class, this.tk); this.ourAs = requireNonNull(ourAs); this.dataBroker = requireNonNull(dataBroker); this.ribSupport = requireNonNull(ribSupport); this.peerTracker = peerTracker; this.pathSelectionMode = pathSelectionMode; this.entryDep = new RouteEntryDependenciesContainerImpl(this.ribSupport, ribPolicies, tablesKey, afiSafiType, this.locRibTableIID); init(); } public static LocRibWriter create( @Nonnull final RIBSupport ribSupport, @Nonnull final TablesKey tablesKey, @Nonnull final Class afiSafiType, @Nonnull final BindingTransactionChain chain, @Nonnull final KeyedInstanceIdentifier ribIId, @Nonnull final AsNumber ourAs, @Nonnull final DataBroker dataBroker, final BGPRibRoutingPolicy ribPolicies, @Nonnull final BGPPeerTracker peerTracker, @Nonnull final PathSelectionMode pathSelectionStrategy) { return new LocRibWriter(ribSupport, chain, ribIId, ourAs.getValue(), dataBroker, ribPolicies, peerTracker, tablesKey, afiSafiType, pathSelectionStrategy); } @SuppressWarnings("unchecked") private synchronized void init() { final WriteTransaction tx = this.chain.newWriteOnlyTransaction(); tx.merge(LogicalDatastoreType.OPERATIONAL, this.locRibTableIID.builder().child(Attributes.class).build(), new AttributesBuilder().setUptodate(true).build()); tx.commit().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()); final InstanceIdentifier tableId = this.ribIId.builder().child(Peer.class) .child(EffectiveRibIn.class).child(Tables.class, this.tk).build(); this.reg = this.dataBroker.registerDataTreeChangeListener( new DataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, tableId), this); } /** * Re-initialize this LocRibWriter with new transaction chain. * * @param newChain new transaction chain */ synchronized void restart(@Nonnull final BindingTransactionChain newChain) { requireNonNull(newChain); close(); this.chain = newChain; init(); } @Override public synchronized void close() { if (this.reg != null) { this.reg.close(); this.reg = null; } if (this.chain != null) { this.chain.close(); this.chain = null; } } @Nonnull private RouteEntry createEntry(final String routeId) { final RouteEntry ret = this.pathSelectionMode.createRouteEntry(); this.routeEntries.put(routeId, ret); this.totalPrefixesCounter.increment(); LOG.trace("Created new entry for {}", routeId); return ret; } /** * We use two-stage processing here in hopes that we avoid duplicate * calculations when multiple peers have changed a particular entry. * * @param changes on supported table */ @Override public synchronized void onDataTreeChanged(final Collection> changes) { if (this.chain == null) { LOG.trace("Chain closed, ignoring received data change {} to LocRib {}", changes, this); return; } LOG.trace("Received data change {} to LocRib {}", changes, this); final WriteTransaction tx = this.chain.newWriteOnlyTransaction(); try { final Map toUpdate = update(tx, changes); if (!toUpdate.isEmpty()) { walkThrough(tx, toUpdate.entrySet()); } } catch (final Exception e) { LOG.error("Failed to completely propagate updates {}, state is undefined", changes, e); } finally { tx.commit().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 Map update(final WriteTransaction tx, final Collection> changes) { final Map ret = new HashMap<>(); for (final DataTreeModification tc : changes) { final DataObjectModification table = tc.getRootNode(); final DataTreeIdentifier rootPath = tc.getRootPath(); final KeyedInstanceIdentifier peerKIid = (KeyedInstanceIdentifier) rootPath.getRootIdentifier().firstIdentifierOf(Peer.class); final UnsignedInteger peerUuid = RouterIds.routerIdForPeerId(peerKIid.getKey().getPeerId()); /* Initialize Peer with routes under loc rib */ if (!this.routeEntries.isEmpty() && table.getDataBefore() == null) { final org.opendaylight.protocol.bgp.rib.spi.Peer peer = this.peerTracker.getPeer(peerKIid.getKey().getPeerId()); if (peer != null && peer.supportsTable(this.entryDep.getLocalTablesKey())) { LOG.debug("Peer {} table has been created, inserting existent routes", peer.getPeerId()); this.routeEntries.forEach((key, value) -> value.initializeBestPaths(this.entryDep, new RouteEntryInfoImpl(peer, key), tx)); } } /* Process new routes from Peer */ updateNodes(table, peerUuid, tx, ret); } return ret; } @SuppressWarnings("unchecked") private void updateNodes( final DataObjectModification table, final UnsignedInteger peerUuid, final WriteTransaction tx, final Map routes ) { final DataObjectModification attUpdate = table.getModifiedChildContainer(Attributes.class); if (attUpdate != null && attUpdate.getDataAfter() != null) { final Attributes newAttValue = attUpdate.getDataAfter(); LOG.trace("Uptodate found for {}", newAttValue); tx.put(LogicalDatastoreType.OPERATIONAL, this.locRibTableIID.child(Attributes.class), newAttValue); } final DataObjectModification routesChangesContainer = table.getModifiedChildContainer(this.ribSupport.routesContainerClass()); if (routesChangesContainer == null) { return; } updateRoutesEntries(routesChangesContainer.getModifiedChildren(), peerUuid, routes); } @SuppressWarnings("unchecked") private void updateRoutesEntries( final Collection> routeChanges, final UnsignedInteger routerId, final Map routes ) { for (final DataObjectModification route : routeChanges) { final Route newRoute = (Route) route.getDataAfter(); final Route oldRoute = (Route) route.getDataBefore(); String routeKey; RouteEntry entry; if (newRoute != null) { routeKey = newRoute.getRouteKey(); entry = this.routeEntries.get(routeKey); if (entry == null) { entry = createEntry(routeKey); } final long pathId = newRoute.getPathId().getValue(); entry.addRoute(routerId, pathId, newRoute); this.totalPathsCounter.increment(); } else { routeKey = oldRoute.getRouteKey(); entry = this.routeEntries.get(routeKey); if(entry != null) { this.totalPathsCounter.decrement(); final long pathId = oldRoute.getPathId().getValue(); if (entry.removeRoute(routerId, pathId)) { this.routeEntries.remove(routeKey); this.totalPrefixesCounter.decrement(); LOG.trace("Removed route from {}", routerId); } } } final RouteUpdateKey routeUpdateKey = new RouteUpdateKey(routerId, routeKey); LOG.debug("Updated route {} entry {}", routeKey, entry); routes.put(routeUpdateKey, entry); } } private void walkThrough(final WriteTransaction tx, final Set> toUpdate) { for (final Map.Entry e : toUpdate) { LOG.trace("Walking through {}", e); final RouteEntry entry = e.getValue(); if (!entry.selectBest(this.ourAs)) { LOG.trace("Best path has not changed, continuing"); continue; } entry.updateBestPaths(entryDep, e.getKey().getRouteId(), tx); } } @Override public long getPrefixesCount() { return this.totalPrefixesCounter.longValue(); } @Override public long getPathsCount() { return this.totalPathsCounter.longValue(); } public TablesKey getTableKey() { return this.tk; } }