X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=bgp%2Frib-impl%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fprotocol%2Fbgp%2Frib%2Fimpl%2FAbstractPeer.java;h=7893470d4a1c327cfbbfae668ac4ada3b39a5f0a;hb=191f08bf8a0d111d53ce2c20bbaab9ffdfd29205;hp=ba2f53af7173df4b8fc47fb5562c7da08202bdae;hpb=e33bcbbb4e78dd57bc9b6ff3c56f9cae3d59ea34;p=bgpcep.git diff --git a/bgp/rib-impl/src/main/java/org/opendaylight/protocol/bgp/rib/impl/AbstractPeer.java b/bgp/rib-impl/src/main/java/org/opendaylight/protocol/bgp/rib/impl/AbstractPeer.java index ba2f53af71..7893470d4a 100644 --- a/bgp/rib-impl/src/main/java/org/opendaylight/protocol/bgp/rib/impl/AbstractPeer.java +++ b/bgp/rib-impl/src/main/java/org/opendaylight/protocol/bgp/rib/impl/AbstractPeer.java @@ -7,65 +7,112 @@ */ 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.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.Set; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.annotation.concurrent.GuardedBy; -import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; -import org.opendaylight.controller.md.sal.common.api.data.TransactionChain; -import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener; -import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction; -import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain; +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.mdsal.dom.api.DOMTransactionChainListener; +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.IpAddress; +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.types.rev180329.ClusterIdentifier; +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.yang.binding.ChildOf; +import org.opendaylight.yangtools.yang.binding.ChoiceIn; +import org.opendaylight.yangtools.yang.binding.DataObject; 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, TransactionChainListener, - Peer { +abstract class AbstractPeer extends BGPPeerStateImpl implements BGPRouteEntryImportParameters, Peer, + PeerTransactionChain, DOMTransactionChainListener { private static final Logger LOG = LoggerFactory.getLogger(AbstractPeer.class); - protected final RIB rib; - final String name; - final PeerRole peerRole; + + final RTCClientRouteCache rtCache = new RTCClientRouteCache(); + final RIB rib; + private final ClusterIdentifier clusterId; + private final PeerRole peerRole; private final AsNumber localAs; - byte[] rawIdentifier; + 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; @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, - @Nullable final ClusterIdentifier clusterId, - @Nullable final AsNumber localAs, - final IpAddress neighborAddress, + final @Nullable ClusterIdentifier clusterId, + final @Nullable AsNumber localAs, + final IpAddressNoZone neighborAddress, final Set afiSafisAdvertized, - final Set afiSafisGracefulAdvertized) { - super(rib.getInstanceIdentifier(), groupId, neighborAddress, afiSafisAdvertized, afiSafisGracefulAdvertized); + final Set afiSafisGracefulAdvertized, + final Map afiSafisLlGracefulAdvertized) { + super(rib.getInstanceIdentifier(), groupId, neighborAddress, afiSafisAdvertized, afiSafisGracefulAdvertized, + afiSafisLlGracefulAdvertized); this.name = peerName; this.peerRole = role; this.clusterId = clusterId; this.localAs = localAs; this.rib = rib; + createDomChain(); } AbstractPeer( @@ -73,43 +120,40 @@ abstract class AbstractPeer extends BGPPeerStateImpl implements BGPRouteEntryImp final String peerName, final String groupId, final PeerRole role, - final IpAddress neighborAddress, + final IpAddressNoZone neighborAddress, final Set afiSafisGracefulAdvertized) { this(rib, peerName, groupId, role, null, null, neighborAddress, - rib.getLocalTablesKeys(), afiSafisGracefulAdvertized); - } - - final synchronized FluentFuture removePeer( - @Nonnull final DOMTransactionChain chain, - @Nullable final YangInstanceIdentifier peerPath) { - if (peerPath != null) { - LOG.info("AdjRibInWriter closed per Peer {} removed", peerPath); - final DOMDataWriteTransaction tx = chain.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); - } + rib.getLocalTablesKeys(), afiSafisGracefulAdvertized, Collections.emptyMap()); + } - @Override - public void onFailure(final Throwable t) { - LOG.error("Failed to remove Peer {}", peerPath, t); - } - }, MoreExecutors.directExecutor()); - return future; + final synchronized FluentFuture removePeer(final @Nullable YangInstanceIdentifier peerPath) { + if (peerPath == null) { + return CommitInfo.emptyFluentFuture(); } - return CommitInfo.emptyFluentFuture(); + LOG.info("Closed per Peer {} removed", peerPath); + final DOMDataTreeWriteTransaction tx = this.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; } synchronized YangInstanceIdentifier createPeerPath() { - return this.rib.getYangRibId().node(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib - .rev180329.bgp.rib.rib.Peer.QNAME).node(IdentifierUtils.domPeerId(this.peerId)); + return this.rib.getYangRibId().node(PEER_NID).node(IdentifierUtils.domPeerId(this.peerId)); } @Override - public synchronized final PeerId getPeerId() { + public final synchronized PeerId getPeerId() { return this.peerId; } @@ -118,11 +162,6 @@ abstract class AbstractPeer extends BGPPeerStateImpl implements BGPRouteEntryImp return this.peerRole; } - @Override - public final synchronized byte[] getRawIdentifier() { - return Arrays.copyOf(this.rawIdentifier, this.rawIdentifier.length); - } - @Override public final PeerRole getFromPeerRole() { return getRole(); @@ -139,7 +178,7 @@ abstract class AbstractPeer extends BGPPeerStateImpl implements BGPRouteEntryImp } @Override - public final void onTransactionChainSuccessful(final TransactionChain chain) { + public final void onTransactionChainSuccessful(final DOMTransactionChain chain) { LOG.debug("Transaction chain {} successful.", chain); } @@ -172,4 +211,304 @@ abstract class AbstractPeer extends BGPPeerStateImpl implements BGPRouteEntryImp public final AsNumber getLocalAs() { return this.localAs; } + + @Override + public synchronized DOMTransactionChain getDomChain() { + return this.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 (this.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 = this.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(); + this.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 (this.ribOutChain == null) { + LOG.debug("Session closed, skip changes to peer AdjRibsOut {}", getPeerId()); + return; + } + final DOMDataTreeWriteTransaction tx = this.ribOutChain.newWriteOnlyTransaction(); + final RIBSupport ribSupport = entryDep.getRIBSupport(); + deleteRouteRibOut(ribSupport, staleRoutes, tx); + installRouteRibOut(entryDep, newRoutes, tx); + + final FluentFuture future = tx.commit(); + this.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 (this.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 = this.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.get(), tx); + continue; + } + } + + deleteRoute(ribSupport, addPathSupported, tableRibout, actualBestRoute, tx); + } + + final FluentFuture future = tx.commit(); + this.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.getIdentifier()), this.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); + } + + final synchronized void releaseRibOutChain(final boolean isWaitForSubmitted) { + if (isWaitForSubmitted) { + if (this.submitted != null) { + try { + this.submitted.get(); + } catch (final InterruptedException | ExecutionException throwable) { + LOG.error("Write routes failed", throwable); + } + } + } + + if (this.ribOutChain != null) { + LOG.info("Closing peer chain {}", getPeerId()); + this.ribOutChain.close(); + this.ribOutChain = null; + } + } + + final synchronized void createDomChain() { + if (this.domChain == null) { + LOG.info("Creating DOM peer chain {}", getPeerId()); + this.domChain = this.rib.createPeerDOMChain(this); + } + } + + final synchronized void closeDomChain() { + if (this.domChain != null) { + LOG.info("Closing DOM peer chain {}", getPeerId()); + this.domChain.close(); + this.domChain = null; + } + } + + boolean supportsLLGR() { + return false; + } }