All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opendaylight.protocol.bgp.rib.impl.AbstractPeer Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2018 AT&T Intellectual Property. 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 org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.PEER_NID;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.FluentFuture;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import org.checkerframework.checker.lock.qual.GuardedBy;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.mdsal.common.api.CommitInfo;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteOperations;
import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
import org.opendaylight.protocol.bgp.mode.impl.BGPRouteEntryExportParametersImpl;
import org.opendaylight.protocol.bgp.rib.impl.spi.PeerTransactionChain;
import org.opendaylight.protocol.bgp.rib.impl.spi.RIB;
import org.opendaylight.protocol.bgp.rib.impl.state.BGPPeerStateImpl;
import org.opendaylight.protocol.bgp.rib.spi.BGPPeerTracker;
import org.opendaylight.protocol.bgp.rib.spi.IdentifierUtils;
import org.opendaylight.protocol.bgp.rib.spi.Peer;
import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
import org.opendaylight.protocol.bgp.rib.spi.entry.AbstractAdvertizedRoute;
import org.opendaylight.protocol.bgp.rib.spi.entry.ActualBestPathRoutes;
import org.opendaylight.protocol.bgp.rib.spi.entry.AdvertizedRoute;
import org.opendaylight.protocol.bgp.rib.spi.entry.RouteEntryDependenciesContainer;
import org.opendaylight.protocol.bgp.rib.spi.entry.RouteKeyIdentifier;
import org.opendaylight.protocol.bgp.rib.spi.entry.StaleBestPathRoute;
import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRouteEntryExportParameters;
import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRouteEntryImportParameters;
import org.opendaylight.protocol.bgp.rib.spi.state.BGPAfiSafiState;
import org.opendaylight.protocol.bgp.rib.spi.state.BGPErrorHandlingState;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.AsNumber;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddressNoZone;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.path.attributes.Attributes;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.PeerId;
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.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.Routes;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.ClusterIdentifier;
import org.opendaylight.yangtools.binding.ChildOf;
import org.opendaylight.yangtools.binding.ChoiceIn;
import org.opendaylight.yangtools.binding.DataObject;
import org.opendaylight.yangtools.yang.common.Empty;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

abstract class AbstractPeer extends BGPPeerStateImpl implements BGPRouteEntryImportParameters, Peer,
        PeerTransactionChain, FutureCallback {
    private static final Logger LOG = LoggerFactory.getLogger(AbstractPeer.class);

    final RTCClientRouteCache rtCache = new RTCClientRouteCache();
    final RIB rib;

    private final ClusterIdentifier clusterId;
    private final PeerRole peerRole;
    private final AsNumber localAs;
    private final String name;

    // FIXME: Revisit locking here to improve concurrency:
    //        -- identifiers, peerId are a shared resource
    //        -- domChain seems to really be 'ribInChain', accessed from netty thread
    //        -- ribOutChain is accessed from LocRibWriter
    //        hence we want to use the two chains concurrently. The problem is their lifecycle in response to errors,
    //        which needs figuring out.
    @GuardedBy("this")
    private DOMTransactionChain domChain = null;
    // FIXME: This is an invariant once the peer is 'resolved' -- which happens instantaneously for ApplicationPeer.
    //        There are also a number YangInstanceIdentifiers which are tied to it. We want to keep all of them in one
    //        structure for isolation. This could be a separate DTO (JDK16 record) or isolated into an abstract behavior
    //        class.
    @GuardedBy("this")
    PeerId peerId;

    // These seem to be separate
    @GuardedBy("this")
    @VisibleForTesting
    DOMTransactionChain ribOutChain;
    @GuardedBy("this")
    private FluentFuture submitted;

    AbstractPeer(
            final RIB rib,
            final String peerName,
            final String groupId,
            final PeerRole role,
            final @Nullable ClusterIdentifier clusterId,
            final @Nullable AsNumber localAs,
            final IpAddressNoZone neighborAddress,
            final Set afiSafisAdvertized,
            final Set afiSafisGracefulAdvertized,
            final Map afiSafisLlGracefulAdvertized) {
        super(rib.getInstanceIdentifier(), groupId, neighborAddress, afiSafisAdvertized, afiSafisGracefulAdvertized,
                afiSafisLlGracefulAdvertized);
        name = peerName;
        peerRole = role;
        this.clusterId = clusterId;
        this.localAs = localAs;
        this.rib = rib;
    }

    final synchronized FluentFuture removePeer(final @Nullable YangInstanceIdentifier peerPath) {
        if (peerPath == null) {
            return CommitInfo.emptyFluentFuture();
        }
        LOG.info("Closed per Peer {} removed", peerPath);
        final DOMDataTreeWriteTransaction tx = domChain.newWriteOnlyTransaction();
        tx.delete(LogicalDatastoreType.OPERATIONAL, peerPath);
        final FluentFuture future = tx.commit();
        future.addCallback(new FutureCallback() {
            @Override
            public void onSuccess(final CommitInfo result) {
                LOG.debug("Peer {} removed", peerPath);
            }

            @Override
            public void onFailure(final Throwable throwable) {
                LOG.error("Failed to remove Peer {}", peerPath, throwable);
            }
        }, MoreExecutors.directExecutor());
        return future;
    }

    final YangInstanceIdentifier createPeerPath(final PeerId newPeerId) {
        return rib.getYangRibId().node(PEER_NID).node(IdentifierUtils.domPeerId(newPeerId));
    }

    @Override
    public final synchronized PeerId getPeerId() {
        return peerId;
    }

    @Override
    public final PeerRole getRole() {
        return peerRole;
    }

    @Override
    public final PeerRole getFromPeerRole() {
        return getRole();
    }

    @Override
    public final PeerId getFromPeerId() {
        return getPeerId();
    }

    @Override
    public final ClusterIdentifier getFromClusterId() {
        return getClusterId();
    }

    @Override
    public final void onSuccess(final Empty value) {
        LOG.debug("Transaction chain successful");
    }

    @Override
    public final BGPErrorHandlingState getBGPErrorHandlingState() {
        return this;
    }

    @Override
    public final BGPAfiSafiState getBGPAfiSafiState() {
        return this;
    }

    @Override
    public final AsNumber getFromPeerLocalAs() {
        return getLocalAs();
    }

    @Override
    public final String getName() {
        return name;
    }

    @Override
    public final ClusterIdentifier getClusterId() {
        return clusterId;
    }

    @Override
    public final AsNumber getLocalAs() {
        return localAs;
    }

    @Override
    public synchronized DOMTransactionChain getDomChain() {
        return domChain;
    }

    /**
     * Returns true if route can be send.
     */
    private boolean filterRoutes(final PeerId fromPeer, final TablesKey localTK) {
        return supportsTable(localTK) && !fromPeer.equals(getPeerId());
    }

    @Override
    public final synchronized , S extends ChildOf>
            void initializeRibOut(final RouteEntryDependenciesContainer entryDep,
                    final List> routesToStore) {
        if (ribOutChain == null) {
            LOG.debug("Session closed, skip changes to peer AdjRibsOut {}", getPeerId());
            return;
        }

        final RIBSupport ribSupport = entryDep.getRIBSupport();
        final YangInstanceIdentifier tableRibout = getRibOutIId(ribSupport.tablesKey());
        final boolean addPathSupported = supportsAddPathSupported(ribSupport.getTablesKey());

        final DOMDataTreeWriteTransaction tx = ribOutChain.newWriteOnlyTransaction();
        for (final ActualBestPathRoutes initRoute : routesToStore) {
            if (!supportsLLGR() && initRoute.isDepreferenced()) {
                // Stale Long-lived Graceful Restart routes should not be propagated
                continue;
            }

            final PeerId fromPeerId = initRoute.getFromPeerId();
            if (!filterRoutes(fromPeerId, ribSupport.getTablesKey())) {
                continue;
            }

            final MapEntryNode route = initRoute.getRoute();
            final Peer fromPeer = entryDep.getPeerTracker().getPeer(fromPeerId);
            if (fromPeer == null) {
                LOG.debug("Failed to acquire peer structure for {}, ignoring route {}", fromPeerId, initRoute);
                continue;
            }

            final YangInstanceIdentifier routePath = createRoutePath(ribSupport, tableRibout, initRoute,
                addPathSupported);
            applyExportPolicy(entryDep, fromPeerId, route, routePath, initRoute.getAttributes()).ifPresent(
                attributes -> storeRoute(ribSupport, initRoute, route, routePath, attributes, tx));
        }

        final FluentFuture future = tx.commit();
        submitted = future;
        future.addCallback(new FutureCallback() {
            @Override
            public void onSuccess(final CommitInfo result) {
                LOG.trace("Successful update commit");
            }

            @Override
            public void onFailure(final Throwable trw) {
                LOG.error("Failed update commit", trw);
            }
        }, MoreExecutors.directExecutor());
    }

    @Override
    public final synchronized , S extends ChildOf>
            void refreshRibOut(final RouteEntryDependenciesContainer entryDep,
                final List staleRoutes, final List> newRoutes) {
        if (ribOutChain == null) {
            LOG.debug("Session closed, skip changes to peer AdjRibsOut {}", getPeerId());
            return;
        }
        final DOMDataTreeWriteTransaction tx = ribOutChain.newWriteOnlyTransaction();
        final RIBSupport ribSupport = entryDep.getRIBSupport();
        deleteRouteRibOut(ribSupport, staleRoutes, tx);
        installRouteRibOut(entryDep, newRoutes, tx);

        final FluentFuture future = tx.commit();
        submitted = future;
        future.addCallback(new FutureCallback() {
            @Override
            public void onSuccess(final CommitInfo result) {
                LOG.trace("Successful update commit");
            }

            @Override
            public void onFailure(final Throwable trw) {
                LOG.error("Failed update commit", trw);
            }
        }, MoreExecutors.directExecutor());
    }

    @Override
    public final synchronized , S extends ChildOf>
            void reEvaluateAdvertizement(final RouteEntryDependenciesContainer entryDep,
                final List> routesToStore) {
        if (ribOutChain == null) {
            LOG.debug("Session closed, skip changes to peer AdjRibsOut {}", getPeerId());
            return;
        }

        final RIBSupport ribSupport = entryDep.getRIBSupport();
        final NodeIdentifierWithPredicates tk = ribSupport.tablesKey();
        final boolean addPathSupported = supportsAddPathSupported(ribSupport.getTablesKey());

        final DOMDataTreeWriteTransaction tx = ribOutChain.newWriteOnlyTransaction();
        for (final ActualBestPathRoutes actualBestRoute : routesToStore) {
            final PeerId fromPeerId = actualBestRoute.getFromPeerId();
            if (!filterRoutes(fromPeerId, ribSupport.getTablesKey())) {
                continue;
            }

            final YangInstanceIdentifier tableRibout = getRibOutIId(tk);
            // Stale Long-lived Graceful Restart routes should not be propagated
            if (supportsLLGR() || !actualBestRoute.isDepreferenced()) {
                final YangInstanceIdentifier routePath = createRoutePath(ribSupport, tableRibout, actualBestRoute,
                    addPathSupported);
                final MapEntryNode route = actualBestRoute.getRoute();
                final Optional effAttr = applyExportPolicy(entryDep, fromPeerId, route, routePath,
                    actualBestRoute.getAttributes());
                if (effAttr.isPresent()) {
                    storeRoute(ribSupport, actualBestRoute, route, routePath, effAttr.orElseThrow(), tx);
                    continue;
                }
            }

            deleteRoute(ribSupport, addPathSupported, tableRibout, actualBestRoute, tx);
        }

        final FluentFuture future = tx.commit();
        submitted = future;
        future.addCallback(new FutureCallback() {
            @Override
            public void onSuccess(final CommitInfo result) {
                LOG.trace("Successful update commit");
            }

            @Override
            public void onFailure(final Throwable trw) {
                LOG.error("Failed update commit", trw);
            }
        }, MoreExecutors.directExecutor());
    }

    private Optional applyExportPolicy(final RouteEntryDependenciesContainer entryDep,
            final PeerId fromPeerId, final MapEntryNode route, final YangInstanceIdentifier routePath,
            final ContainerNode attrs) {
        final Peer fromPeer = entryDep.getPeerTracker().getPeer(fromPeerId);
        final RIBSupport ribSupport = entryDep.getRIBSupport();
        final BGPRouteEntryExportParameters routeEntry = new BGPRouteEntryExportParametersImpl(fromPeer, this,
            ribSupport.extractRouteKey(route.name()), rtCache);

        final Attributes bindingAttrs = ribSupport.attributeFromContainerNode(attrs);
        final Optional optExportAttrs = entryDep.getRoutingPolicies().applyExportPolicies(routeEntry,
            bindingAttrs, entryDep.getAfiSafType());
        if (optExportAttrs.isEmpty()) {
            // Discards route
            return Optional.empty();
        }
        final Attributes exportAttrs = optExportAttrs.orElseThrow();

        // If the same object is returned we can just reuse 'attrs' instead. Since we are in control of lifecycle here,
        // we use identity comparison, as equality is too costly for the common case -- assuming export policy will not
        // churn objects when it does not have to
        return Optional.of(exportAttrs == bindingAttrs ? attrs
            : ribSupport.attributeToContainerNode(routePath.node(ribSupport.routeAttributesIdentifier()), exportAttrs));
    }

    private , S extends ChildOf> void installRouteRibOut(
            final RouteEntryDependenciesContainer entryDep, final List> routes,
            final DOMDataTreeWriteOperations tx) {
        final RIBSupport ribSupport = entryDep.getRIBSupport();
        final TablesKey tk = ribSupport.getTablesKey();
        final BGPPeerTracker peerTracker = entryDep.getPeerTracker();
        final boolean addPathSupported = supportsAddPathSupported(tk);
        final YangInstanceIdentifier tableRibout = getRibOutIId(ribSupport.tablesKey());

        for (final AdvertizedRoute advRoute : routes) {
            final PeerId fromPeerId = advRoute.getFromPeerId();
            if (!filterRoutes(fromPeerId, tk) || !advRoute.isFirstBestPath() && !addPathSupported) {
                continue;
            }
            if (!supportsLLGR() && advRoute.isDepreferenced()) {
                // https://tools.ietf.org/html/draft-uttaro-idr-bgp-persistence-04#section-4.3
                //     o  The route SHOULD NOT be advertised to any neighbor from which the
                //        Long-lived Graceful Restart Capability has not been received.  The
                //        exception is described in the Optional Partial Deployment
                //        Procedure section (Section 4.7).  Note that this requirement
                //        implies that such routes should be withdrawn from any such
                //        neighbor.
                deleteRoute(ribSupport, addPathSupported, tableRibout, advRoute, tx);
                continue;
            }

            final Peer fromPeer = peerTracker.getPeer(fromPeerId);
            final ContainerNode attributes = advRoute.getAttributes();
            if (fromPeer != null && attributes != null) {
                final YangInstanceIdentifier routePath = createRoutePath(ribSupport, tableRibout, advRoute,
                    addPathSupported);
                final MapEntryNode route = advRoute.getRoute();
                applyExportPolicy(entryDep, fromPeerId, route, routePath, attributes).ifPresent(
                    attrs -> storeRoute(ribSupport, advRoute, route, routePath, attrs, tx));
            }
        }
    }

    private static YangInstanceIdentifier createRoutePath(final RIBSupport ribSupport,
            final YangInstanceIdentifier tableRibout, final RouteKeyIdentifier advRoute, final boolean withAddPath) {
        return ribSupport.createRouteIdentifier(tableRibout,
            withAddPath ? advRoute.getAddPathRouteKeyIdentifier() : advRoute.getNonAddPathRouteKeyIdentifier());
    }

    private synchronized , S extends ChildOf>
            void deleteRouteRibOut(final RIBSupport ribSupport, final List staleRoutesIid,
                final DOMDataTreeWriteOperations tx) {
        final YangInstanceIdentifier tableRibout = getRibOutIId(ribSupport.tablesKey());
        final boolean addPathSupported = supportsAddPathSupported(ribSupport.getTablesKey());
        staleRoutesIid.forEach(staleRouteIid
            -> removeRoute(ribSupport, addPathSupported, tableRibout, staleRouteIid, tx));
    }

    private , S extends ChildOf> void storeRoute(
            final RIBSupport ribSupport, final RouteKeyIdentifier advRoute, final MapEntryNode route,
            final YangInstanceIdentifier routePath, final ContainerNode effAttr, final DOMDataTreeWriteOperations tx) {
        LOG.debug("Write advRoute {} to peer AdjRibsOut {}", advRoute, getPeerId());
        tx.put(LogicalDatastoreType.OPERATIONAL, routePath, ribSupport.createRoute(route,
            (NodeIdentifierWithPredicates) routePath.getLastPathArgument(), effAttr));
    }

    private synchronized , S extends ChildOf>
            void removeRoute(final RIBSupport ribSupport, final boolean addPathSupported,
                final YangInstanceIdentifier tableRibout, final StaleBestPathRoute staleRouteIid,
                final DOMDataTreeWriteOperations tx) {
        if (addPathSupported) {
            List staleRoutesIId = staleRouteIid.getAddPathRouteKeyIdentifiers();
            for (final NodeIdentifierWithPredicates id : staleRoutesIId) {
                final YangInstanceIdentifier ribOutTarget = ribSupport.createRouteIdentifier(tableRibout, id);
                LOG.trace("Removing {} from transaction for peer {}", ribOutTarget, getPeerId());
                tx.delete(LogicalDatastoreType.OPERATIONAL, ribOutTarget);
            }
        } else {
            if (!staleRouteIid.isNonAddPathBestPathNew()) {
                return;
            }
            final YangInstanceIdentifier ribOutTarget = ribSupport.createRouteIdentifier(tableRibout,
                    staleRouteIid.getNonAddPathRouteKeyIdentifier());
            LOG.trace("Removing {} from transaction for peer {}", ribOutTarget, getPeerId());
            tx.delete(LogicalDatastoreType.OPERATIONAL, ribOutTarget);
        }
    }

    // FIXME: why is this different from removeRoute()?
    private , S extends ChildOf> void deleteRoute(
            final RIBSupport ribSupport,  final boolean addPathSupported,
            final YangInstanceIdentifier tableRibout, final AbstractAdvertizedRoute advRoute,
            final DOMDataTreeWriteOperations tx) {
        final YangInstanceIdentifier ribOutTarget = ribSupport.createRouteIdentifier(tableRibout,
            addPathSupported ? advRoute.getAddPathRouteKeyIdentifier() : advRoute.getNonAddPathRouteKeyIdentifier());
        LOG.trace("Removing {} from transaction for peer {}", ribOutTarget, getPeerId());
        tx.delete(LogicalDatastoreType.OPERATIONAL, ribOutTarget);
    }

    // FIXME: make this asynchronous?
    final synchronized void releaseRibOutChain(final boolean isWaitForSubmitted) {
        if (isWaitForSubmitted) {
            if (submitted != null) {
                try {
                    submitted.get();
                } catch (final InterruptedException | ExecutionException throwable) {
                    LOG.error("Write routes failed", throwable);
                }
            }
        }

        if (ribOutChain != null) {
            LOG.info("Closing peer chain {}", getPeerId());
            ribOutChain.close();
            ribOutChain = null;
        }
    }

    final synchronized void createDomChain() {
        if (domChain == null) {
            LOG.info("Creating DOM peer chain {}", getPeerId());
            domChain = rib.createPeerDOMChain();
            domChain.addCallback(this);
        }
    }

    final synchronized void closeDomChain() {
        if (domChain != null) {
            LOG.info("Closing DOM peer chain {}", getPeerId());
            domChain.close();
            domChain = null;
        }
    }

    boolean supportsLLGR() {
        return false;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy