2 * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.protocol.bgp.rib.impl;
10 import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.PEER_NID;
12 import com.google.common.annotations.VisibleForTesting;
13 import com.google.common.util.concurrent.FluentFuture;
14 import com.google.common.util.concurrent.FutureCallback;
15 import com.google.common.util.concurrent.MoreExecutors;
16 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
17 import java.util.Collections;
18 import java.util.List;
20 import java.util.Optional;
22 import java.util.concurrent.ExecutionException;
23 import org.checkerframework.checker.lock.qual.GuardedBy;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.opendaylight.mdsal.common.api.CommitInfo;
26 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
27 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteOperations;
28 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
29 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
30 import org.opendaylight.protocol.bgp.mode.impl.BGPRouteEntryExportParametersImpl;
31 import org.opendaylight.protocol.bgp.rib.impl.spi.PeerTransactionChain;
32 import org.opendaylight.protocol.bgp.rib.impl.spi.RIB;
33 import org.opendaylight.protocol.bgp.rib.impl.state.BGPPeerStateImpl;
34 import org.opendaylight.protocol.bgp.rib.spi.BGPPeerTracker;
35 import org.opendaylight.protocol.bgp.rib.spi.IdentifierUtils;
36 import org.opendaylight.protocol.bgp.rib.spi.Peer;
37 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
38 import org.opendaylight.protocol.bgp.rib.spi.entry.AbstractAdvertizedRoute;
39 import org.opendaylight.protocol.bgp.rib.spi.entry.ActualBestPathRoutes;
40 import org.opendaylight.protocol.bgp.rib.spi.entry.AdvertizedRoute;
41 import org.opendaylight.protocol.bgp.rib.spi.entry.RouteEntryDependenciesContainer;
42 import org.opendaylight.protocol.bgp.rib.spi.entry.RouteKeyIdentifier;
43 import org.opendaylight.protocol.bgp.rib.spi.entry.StaleBestPathRoute;
44 import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRouteEntryExportParameters;
45 import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRouteEntryImportParameters;
46 import org.opendaylight.protocol.bgp.rib.spi.state.BGPAfiSafiState;
47 import org.opendaylight.protocol.bgp.rib.spi.state.BGPErrorHandlingState;
48 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.AsNumber;
49 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddressNoZone;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.path.attributes.Attributes;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.PeerId;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.PeerRole;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesKey;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.tables.Routes;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.ClusterIdentifier;
57 import org.opendaylight.yangtools.yang.binding.ChildOf;
58 import org.opendaylight.yangtools.yang.binding.ChoiceIn;
59 import org.opendaylight.yangtools.yang.binding.DataObject;
60 import org.opendaylight.yangtools.yang.common.Empty;
61 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
62 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
63 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
64 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
65 import org.slf4j.Logger;
66 import org.slf4j.LoggerFactory;
68 abstract class AbstractPeer extends BGPPeerStateImpl implements BGPRouteEntryImportParameters, Peer,
69 PeerTransactionChain, FutureCallback<Empty> {
70 private static final Logger LOG = LoggerFactory.getLogger(AbstractPeer.class);
72 final RTCClientRouteCache rtCache = new RTCClientRouteCache();
75 private final ClusterIdentifier clusterId;
76 private final PeerRole peerRole;
77 private final AsNumber localAs;
78 private final String name;
80 // FIXME: Revisit locking here to improve concurrency:
81 // -- identifiers, peerId are a shared resource
82 // -- domChain seems to really be 'ribInChain', accessed from netty thread
83 // -- ribOutChain is accessed from LocRibWriter
84 // hence we want to use the two chains concurrently. The problem is their lifecycle in response to errors,
85 // which needs figuring out.
87 private DOMTransactionChain domChain;
88 // FIXME: This is an invariant once the peer is 'resolved' -- which happens instantaneously for ApplicationPeer.
89 // There are also a number YangInstanceIdentifiers which are tied to it. We want to keep all of them in one
90 // structure for isolation. This could be a separate DTO (JDK16 record) or isolated into an abstract behavior
95 // These seem to be separate
98 DOMTransactionChain ribOutChain;
100 private FluentFuture<? extends CommitInfo> submitted;
102 @SuppressFBWarnings(value = "MC_OVERRIDABLE_METHOD_CALL_IN_CONSTRUCTOR",
103 justification = "False positive on synchronized createDomChain()")
106 final String peerName,
107 final String groupId,
109 final @Nullable ClusterIdentifier clusterId,
110 final @Nullable AsNumber localAs,
111 final IpAddressNoZone neighborAddress,
112 final Set<TablesKey> afiSafisAdvertized,
113 final Set<TablesKey> afiSafisGracefulAdvertized,
114 final Map<TablesKey, Integer> afiSafisLlGracefulAdvertized) {
115 super(rib.getInstanceIdentifier(), groupId, neighborAddress, afiSafisAdvertized, afiSafisGracefulAdvertized,
116 afiSafisLlGracefulAdvertized);
119 this.clusterId = clusterId;
120 this.localAs = localAs;
127 final String peerName,
128 final String groupId,
130 final IpAddressNoZone neighborAddress,
131 final Set<TablesKey> afiSafisGracefulAdvertized) {
132 this(rib, peerName, groupId, role, null, null, neighborAddress,
133 rib.getLocalTablesKeys(), afiSafisGracefulAdvertized, Collections.emptyMap());
136 final synchronized FluentFuture<? extends CommitInfo> removePeer(final @Nullable YangInstanceIdentifier peerPath) {
137 if (peerPath == null) {
138 return CommitInfo.emptyFluentFuture();
140 LOG.info("Closed per Peer {} removed", peerPath);
141 final DOMDataTreeWriteTransaction tx = domChain.newWriteOnlyTransaction();
142 tx.delete(LogicalDatastoreType.OPERATIONAL, peerPath);
143 final FluentFuture<? extends CommitInfo> future = tx.commit();
144 future.addCallback(new FutureCallback<CommitInfo>() {
146 public void onSuccess(final CommitInfo result) {
147 LOG.debug("Peer {} removed", peerPath);
151 public void onFailure(final Throwable throwable) {
152 LOG.error("Failed to remove Peer {}", peerPath, throwable);
154 }, MoreExecutors.directExecutor());
158 final YangInstanceIdentifier createPeerPath(final PeerId newPeerId) {
159 return rib.getYangRibId().node(PEER_NID).node(IdentifierUtils.domPeerId(newPeerId));
163 public final synchronized PeerId getPeerId() {
168 public final PeerRole getRole() {
173 public final PeerRole getFromPeerRole() {
178 public final PeerId getFromPeerId() {
183 public final ClusterIdentifier getFromClusterId() {
184 return getClusterId();
188 public final void onSuccess(final Empty value) {
189 LOG.debug("Transaction chain successful");
193 public final BGPErrorHandlingState getBGPErrorHandlingState() {
198 public final BGPAfiSafiState getBGPAfiSafiState() {
203 public final AsNumber getFromPeerLocalAs() {
208 public final String getName() {
213 public final ClusterIdentifier getClusterId() {
218 public final AsNumber getLocalAs() {
223 public synchronized DOMTransactionChain getDomChain() {
228 * Returns true if route can be send.
230 private boolean filterRoutes(final PeerId fromPeer, final TablesKey localTK) {
231 return supportsTable(localTK) && !fromPeer.equals(getPeerId());
235 public final synchronized <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>>
236 void initializeRibOut(final RouteEntryDependenciesContainer entryDep,
237 final List<ActualBestPathRoutes<C, S>> routesToStore) {
238 if (ribOutChain == null) {
239 LOG.debug("Session closed, skip changes to peer AdjRibsOut {}", getPeerId());
243 final RIBSupport<C, S> ribSupport = entryDep.getRIBSupport();
244 final YangInstanceIdentifier tableRibout = getRibOutIId(ribSupport.tablesKey());
245 final boolean addPathSupported = supportsAddPathSupported(ribSupport.getTablesKey());
247 final DOMDataTreeWriteTransaction tx = ribOutChain.newWriteOnlyTransaction();
248 for (final ActualBestPathRoutes<C, S> initRoute : routesToStore) {
249 if (!supportsLLGR() && initRoute.isDepreferenced()) {
250 // Stale Long-lived Graceful Restart routes should not be propagated
254 final PeerId fromPeerId = initRoute.getFromPeerId();
255 if (!filterRoutes(fromPeerId, ribSupport.getTablesKey())) {
259 final MapEntryNode route = initRoute.getRoute();
260 final Peer fromPeer = entryDep.getPeerTracker().getPeer(fromPeerId);
261 if (fromPeer == null) {
262 LOG.debug("Failed to acquire peer structure for {}, ignoring route {}", fromPeerId, initRoute);
266 final YangInstanceIdentifier routePath = createRoutePath(ribSupport, tableRibout, initRoute,
268 applyExportPolicy(entryDep, fromPeerId, route, routePath, initRoute.getAttributes()).ifPresent(
269 attributes -> storeRoute(ribSupport, initRoute, route, routePath, attributes, tx));
272 final FluentFuture<? extends CommitInfo> future = tx.commit();
274 future.addCallback(new FutureCallback<CommitInfo>() {
276 public void onSuccess(final CommitInfo result) {
277 LOG.trace("Successful update commit");
281 public void onFailure(final Throwable trw) {
282 LOG.error("Failed update commit", trw);
284 }, MoreExecutors.directExecutor());
288 public final synchronized <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>>
289 void refreshRibOut(final RouteEntryDependenciesContainer entryDep,
290 final List<StaleBestPathRoute> staleRoutes, final List<AdvertizedRoute<C, S>> newRoutes) {
291 if (ribOutChain == null) {
292 LOG.debug("Session closed, skip changes to peer AdjRibsOut {}", getPeerId());
295 final DOMDataTreeWriteTransaction tx = ribOutChain.newWriteOnlyTransaction();
296 final RIBSupport<C, S> ribSupport = entryDep.getRIBSupport();
297 deleteRouteRibOut(ribSupport, staleRoutes, tx);
298 installRouteRibOut(entryDep, newRoutes, tx);
300 final FluentFuture<? extends CommitInfo> future = tx.commit();
302 future.addCallback(new FutureCallback<CommitInfo>() {
304 public void onSuccess(final CommitInfo result) {
305 LOG.trace("Successful update commit");
309 public void onFailure(final Throwable trw) {
310 LOG.error("Failed update commit", trw);
312 }, MoreExecutors.directExecutor());
316 public final synchronized <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>>
317 void reEvaluateAdvertizement(final RouteEntryDependenciesContainer entryDep,
318 final List<ActualBestPathRoutes<C, S>> routesToStore) {
319 if (ribOutChain == null) {
320 LOG.debug("Session closed, skip changes to peer AdjRibsOut {}", getPeerId());
324 final RIBSupport<C, S> ribSupport = entryDep.getRIBSupport();
325 final NodeIdentifierWithPredicates tk = ribSupport.tablesKey();
326 final boolean addPathSupported = supportsAddPathSupported(ribSupport.getTablesKey());
328 final DOMDataTreeWriteTransaction tx = ribOutChain.newWriteOnlyTransaction();
329 for (final ActualBestPathRoutes<C, S> actualBestRoute : routesToStore) {
330 final PeerId fromPeerId = actualBestRoute.getFromPeerId();
331 if (!filterRoutes(fromPeerId, ribSupport.getTablesKey())) {
335 final YangInstanceIdentifier tableRibout = getRibOutIId(tk);
336 // Stale Long-lived Graceful Restart routes should not be propagated
337 if (supportsLLGR() || !actualBestRoute.isDepreferenced()) {
338 final YangInstanceIdentifier routePath = createRoutePath(ribSupport, tableRibout, actualBestRoute,
340 final MapEntryNode route = actualBestRoute.getRoute();
341 final Optional<ContainerNode> effAttr = applyExportPolicy(entryDep, fromPeerId, route, routePath,
342 actualBestRoute.getAttributes());
343 if (effAttr.isPresent()) {
344 storeRoute(ribSupport, actualBestRoute, route, routePath, effAttr.orElseThrow(), tx);
349 deleteRoute(ribSupport, addPathSupported, tableRibout, actualBestRoute, tx);
352 final FluentFuture<? extends CommitInfo> future = tx.commit();
354 future.addCallback(new FutureCallback<CommitInfo>() {
356 public void onSuccess(final CommitInfo result) {
357 LOG.trace("Successful update commit");
361 public void onFailure(final Throwable trw) {
362 LOG.error("Failed update commit", trw);
364 }, MoreExecutors.directExecutor());
367 private Optional<ContainerNode> applyExportPolicy(final RouteEntryDependenciesContainer entryDep,
368 final PeerId fromPeerId, final MapEntryNode route, final YangInstanceIdentifier routePath,
369 final ContainerNode attrs) {
370 final Peer fromPeer = entryDep.getPeerTracker().getPeer(fromPeerId);
371 final RIBSupport<?, ?> ribSupport = entryDep.getRIBSupport();
372 final BGPRouteEntryExportParameters routeEntry = new BGPRouteEntryExportParametersImpl(fromPeer, this,
373 ribSupport.extractRouteKey(route.name()), rtCache);
375 final Attributes bindingAttrs = ribSupport.attributeFromContainerNode(attrs);
376 final Optional<Attributes> optExportAttrs = entryDep.getRoutingPolicies().applyExportPolicies(routeEntry,
377 bindingAttrs, entryDep.getAfiSafType());
378 if (optExportAttrs.isEmpty()) {
380 return Optional.empty();
382 final Attributes exportAttrs = optExportAttrs.orElseThrow();
384 // If the same object is returned we can just reuse 'attrs' instead. Since we are in control of lifecycle here,
385 // we use identity comparison, as equality is too costly for the common case -- assuming export policy will not
386 // churn objects when it does not have to
387 return Optional.of(exportAttrs == bindingAttrs ? attrs
388 : ribSupport.attributeToContainerNode(routePath.node(ribSupport.routeAttributesIdentifier()), exportAttrs));
391 private <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>> void installRouteRibOut(
392 final RouteEntryDependenciesContainer entryDep, final List<AdvertizedRoute<C, S>> routes,
393 final DOMDataTreeWriteOperations tx) {
394 final RIBSupport<C, S> ribSupport = entryDep.getRIBSupport();
395 final TablesKey tk = ribSupport.getTablesKey();
396 final BGPPeerTracker peerTracker = entryDep.getPeerTracker();
397 final boolean addPathSupported = supportsAddPathSupported(tk);
398 final YangInstanceIdentifier tableRibout = getRibOutIId(ribSupport.tablesKey());
400 for (final AdvertizedRoute<C, S> advRoute : routes) {
401 final PeerId fromPeerId = advRoute.getFromPeerId();
402 if (!filterRoutes(fromPeerId, tk) || !advRoute.isFirstBestPath() && !addPathSupported) {
405 if (!supportsLLGR() && advRoute.isDepreferenced()) {
406 // https://tools.ietf.org/html/draft-uttaro-idr-bgp-persistence-04#section-4.3
407 // o The route SHOULD NOT be advertised to any neighbor from which the
408 // Long-lived Graceful Restart Capability has not been received. The
409 // exception is described in the Optional Partial Deployment
410 // Procedure section (Section 4.7). Note that this requirement
411 // implies that such routes should be withdrawn from any such
413 deleteRoute(ribSupport, addPathSupported, tableRibout, advRoute, tx);
417 final Peer fromPeer = peerTracker.getPeer(fromPeerId);
418 final ContainerNode attributes = advRoute.getAttributes();
419 if (fromPeer != null && attributes != null) {
420 final YangInstanceIdentifier routePath = createRoutePath(ribSupport, tableRibout, advRoute,
422 final MapEntryNode route = advRoute.getRoute();
423 applyExportPolicy(entryDep, fromPeerId, route, routePath, attributes).ifPresent(
424 attrs -> storeRoute(ribSupport, advRoute, route, routePath, attrs, tx));
429 private static YangInstanceIdentifier createRoutePath(final RIBSupport<?, ?> ribSupport,
430 final YangInstanceIdentifier tableRibout, final RouteKeyIdentifier advRoute, final boolean withAddPath) {
431 return ribSupport.createRouteIdentifier(tableRibout,
432 withAddPath ? advRoute.getAddPathRouteKeyIdentifier() : advRoute.getNonAddPathRouteKeyIdentifier());
435 private synchronized <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>>
436 void deleteRouteRibOut(final RIBSupport<C, S> ribSupport, final List<StaleBestPathRoute> staleRoutesIid,
437 final DOMDataTreeWriteOperations tx) {
438 final YangInstanceIdentifier tableRibout = getRibOutIId(ribSupport.tablesKey());
439 final boolean addPathSupported = supportsAddPathSupported(ribSupport.getTablesKey());
440 staleRoutesIid.forEach(staleRouteIid
441 -> removeRoute(ribSupport, addPathSupported, tableRibout, staleRouteIid, tx));
444 private <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>> void storeRoute(
445 final RIBSupport<C, S> ribSupport, final RouteKeyIdentifier advRoute, final MapEntryNode route,
446 final YangInstanceIdentifier routePath, final ContainerNode effAttr, final DOMDataTreeWriteOperations tx) {
447 LOG.debug("Write advRoute {} to peer AdjRibsOut {}", advRoute, getPeerId());
448 tx.put(LogicalDatastoreType.OPERATIONAL, routePath, ribSupport.createRoute(route,
449 (NodeIdentifierWithPredicates) routePath.getLastPathArgument(), effAttr));
452 private synchronized <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>>
453 void removeRoute(final RIBSupport<C, S> ribSupport, final boolean addPathSupported,
454 final YangInstanceIdentifier tableRibout, final StaleBestPathRoute staleRouteIid,
455 final DOMDataTreeWriteOperations tx) {
456 if (addPathSupported) {
457 List<NodeIdentifierWithPredicates> staleRoutesIId = staleRouteIid.getAddPathRouteKeyIdentifiers();
458 for (final NodeIdentifierWithPredicates id : staleRoutesIId) {
459 final YangInstanceIdentifier ribOutTarget = ribSupport.createRouteIdentifier(tableRibout, id);
460 LOG.trace("Removing {} from transaction for peer {}", ribOutTarget, getPeerId());
461 tx.delete(LogicalDatastoreType.OPERATIONAL, ribOutTarget);
464 if (!staleRouteIid.isNonAddPathBestPathNew()) {
467 final YangInstanceIdentifier ribOutTarget = ribSupport.createRouteIdentifier(tableRibout,
468 staleRouteIid.getNonAddPathRouteKeyIdentifier());
469 LOG.trace("Removing {} from transaction for peer {}", ribOutTarget, getPeerId());
470 tx.delete(LogicalDatastoreType.OPERATIONAL, ribOutTarget);
474 // FIXME: why is this different from removeRoute()?
475 private <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>> void deleteRoute(
476 final RIBSupport<C, S> ribSupport, final boolean addPathSupported,
477 final YangInstanceIdentifier tableRibout, final AbstractAdvertizedRoute<C, S> advRoute,
478 final DOMDataTreeWriteOperations tx) {
479 final YangInstanceIdentifier ribOutTarget = ribSupport.createRouteIdentifier(tableRibout,
480 addPathSupported ? advRoute.getAddPathRouteKeyIdentifier() : advRoute.getNonAddPathRouteKeyIdentifier());
481 LOG.trace("Removing {} from transaction for peer {}", ribOutTarget, getPeerId());
482 tx.delete(LogicalDatastoreType.OPERATIONAL, ribOutTarget);
485 // FIXME: make this asynchronous?
486 final synchronized void releaseRibOutChain(final boolean isWaitForSubmitted) {
487 if (isWaitForSubmitted) {
488 if (submitted != null) {
491 } catch (final InterruptedException | ExecutionException throwable) {
492 LOG.error("Write routes failed", throwable);
497 if (ribOutChain != null) {
498 LOG.info("Closing peer chain {}", getPeerId());
504 final synchronized void createDomChain() {
505 if (domChain == null) {
506 LOG.info("Creating DOM peer chain {}", getPeerId());
507 domChain = rib.createPeerDOMChain();
508 domChain.addCallback(this);
512 final synchronized void closeDomChain() {
513 if (domChain != null) {
514 LOG.info("Closing DOM peer chain {}", getPeerId());
520 boolean supportsLLGR() {