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 java.util.List;
18 import java.util.Optional;
20 import java.util.concurrent.ExecutionException;
21 import org.checkerframework.checker.lock.qual.GuardedBy;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.opendaylight.mdsal.common.api.CommitInfo;
24 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
25 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteOperations;
26 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
27 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
28 import org.opendaylight.protocol.bgp.mode.impl.BGPRouteEntryExportParametersImpl;
29 import org.opendaylight.protocol.bgp.rib.impl.spi.PeerTransactionChain;
30 import org.opendaylight.protocol.bgp.rib.impl.spi.RIB;
31 import org.opendaylight.protocol.bgp.rib.impl.state.BGPPeerStateImpl;
32 import org.opendaylight.protocol.bgp.rib.spi.BGPPeerTracker;
33 import org.opendaylight.protocol.bgp.rib.spi.IdentifierUtils;
34 import org.opendaylight.protocol.bgp.rib.spi.Peer;
35 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
36 import org.opendaylight.protocol.bgp.rib.spi.entry.AbstractAdvertizedRoute;
37 import org.opendaylight.protocol.bgp.rib.spi.entry.ActualBestPathRoutes;
38 import org.opendaylight.protocol.bgp.rib.spi.entry.AdvertizedRoute;
39 import org.opendaylight.protocol.bgp.rib.spi.entry.RouteEntryDependenciesContainer;
40 import org.opendaylight.protocol.bgp.rib.spi.entry.RouteKeyIdentifier;
41 import org.opendaylight.protocol.bgp.rib.spi.entry.StaleBestPathRoute;
42 import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRouteEntryExportParameters;
43 import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRouteEntryImportParameters;
44 import org.opendaylight.protocol.bgp.rib.spi.state.BGPAfiSafiState;
45 import org.opendaylight.protocol.bgp.rib.spi.state.BGPErrorHandlingState;
46 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.AsNumber;
47 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddressNoZone;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.path.attributes.Attributes;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.PeerId;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.PeerRole;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesKey;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.tables.Routes;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.ClusterIdentifier;
55 import org.opendaylight.yangtools.yang.binding.ChildOf;
56 import org.opendaylight.yangtools.yang.binding.ChoiceIn;
57 import org.opendaylight.yangtools.yang.binding.DataObject;
58 import org.opendaylight.yangtools.yang.common.Empty;
59 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
60 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
61 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
62 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
63 import org.slf4j.Logger;
64 import org.slf4j.LoggerFactory;
66 abstract class AbstractPeer extends BGPPeerStateImpl implements BGPRouteEntryImportParameters, Peer,
67 PeerTransactionChain, FutureCallback<Empty> {
68 private static final Logger LOG = LoggerFactory.getLogger(AbstractPeer.class);
70 final RTCClientRouteCache rtCache = new RTCClientRouteCache();
73 private final ClusterIdentifier clusterId;
74 private final PeerRole peerRole;
75 private final AsNumber localAs;
76 private final String name;
78 // FIXME: Revisit locking here to improve concurrency:
79 // -- identifiers, peerId are a shared resource
80 // -- domChain seems to really be 'ribInChain', accessed from netty thread
81 // -- ribOutChain is accessed from LocRibWriter
82 // hence we want to use the two chains concurrently. The problem is their lifecycle in response to errors,
83 // which needs figuring out.
85 private DOMTransactionChain domChain = null;
86 // FIXME: This is an invariant once the peer is 'resolved' -- which happens instantaneously for ApplicationPeer.
87 // There are also a number YangInstanceIdentifiers which are tied to it. We want to keep all of them in one
88 // structure for isolation. This could be a separate DTO (JDK16 record) or isolated into an abstract behavior
93 // These seem to be separate
96 DOMTransactionChain ribOutChain;
98 private FluentFuture<? extends CommitInfo> submitted;
102 final String peerName,
103 final String groupId,
105 final @Nullable ClusterIdentifier clusterId,
106 final @Nullable AsNumber localAs,
107 final IpAddressNoZone neighborAddress,
108 final Set<TablesKey> afiSafisAdvertized,
109 final Set<TablesKey> afiSafisGracefulAdvertized,
110 final Map<TablesKey, Integer> afiSafisLlGracefulAdvertized) {
111 super(rib.getInstanceIdentifier(), groupId, neighborAddress, afiSafisAdvertized, afiSafisGracefulAdvertized,
112 afiSafisLlGracefulAdvertized);
115 this.clusterId = clusterId;
116 this.localAs = localAs;
120 final synchronized FluentFuture<? extends CommitInfo> removePeer(final @Nullable YangInstanceIdentifier peerPath) {
121 if (peerPath == null) {
122 return CommitInfo.emptyFluentFuture();
124 LOG.info("Closed per Peer {} removed", peerPath);
125 final DOMDataTreeWriteTransaction tx = domChain.newWriteOnlyTransaction();
126 tx.delete(LogicalDatastoreType.OPERATIONAL, peerPath);
127 final FluentFuture<? extends CommitInfo> future = tx.commit();
128 future.addCallback(new FutureCallback<CommitInfo>() {
130 public void onSuccess(final CommitInfo result) {
131 LOG.debug("Peer {} removed", peerPath);
135 public void onFailure(final Throwable throwable) {
136 LOG.error("Failed to remove Peer {}", peerPath, throwable);
138 }, MoreExecutors.directExecutor());
142 final YangInstanceIdentifier createPeerPath(final PeerId newPeerId) {
143 return rib.getYangRibId().node(PEER_NID).node(IdentifierUtils.domPeerId(newPeerId));
147 public final synchronized PeerId getPeerId() {
152 public final PeerRole getRole() {
157 public final PeerRole getFromPeerRole() {
162 public final PeerId getFromPeerId() {
167 public final ClusterIdentifier getFromClusterId() {
168 return getClusterId();
172 public final void onSuccess(final Empty value) {
173 LOG.debug("Transaction chain successful");
177 public final BGPErrorHandlingState getBGPErrorHandlingState() {
182 public final BGPAfiSafiState getBGPAfiSafiState() {
187 public final AsNumber getFromPeerLocalAs() {
192 public final String getName() {
197 public final ClusterIdentifier getClusterId() {
202 public final AsNumber getLocalAs() {
207 public synchronized DOMTransactionChain getDomChain() {
212 * Returns true if route can be send.
214 private boolean filterRoutes(final PeerId fromPeer, final TablesKey localTK) {
215 return supportsTable(localTK) && !fromPeer.equals(getPeerId());
219 public final synchronized <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>>
220 void initializeRibOut(final RouteEntryDependenciesContainer entryDep,
221 final List<ActualBestPathRoutes<C, S>> routesToStore) {
222 if (ribOutChain == null) {
223 LOG.debug("Session closed, skip changes to peer AdjRibsOut {}", getPeerId());
227 final RIBSupport<C, S> ribSupport = entryDep.getRIBSupport();
228 final YangInstanceIdentifier tableRibout = getRibOutIId(ribSupport.tablesKey());
229 final boolean addPathSupported = supportsAddPathSupported(ribSupport.getTablesKey());
231 final DOMDataTreeWriteTransaction tx = ribOutChain.newWriteOnlyTransaction();
232 for (final ActualBestPathRoutes<C, S> initRoute : routesToStore) {
233 if (!supportsLLGR() && initRoute.isDepreferenced()) {
234 // Stale Long-lived Graceful Restart routes should not be propagated
238 final PeerId fromPeerId = initRoute.getFromPeerId();
239 if (!filterRoutes(fromPeerId, ribSupport.getTablesKey())) {
243 final MapEntryNode route = initRoute.getRoute();
244 final Peer fromPeer = entryDep.getPeerTracker().getPeer(fromPeerId);
245 if (fromPeer == null) {
246 LOG.debug("Failed to acquire peer structure for {}, ignoring route {}", fromPeerId, initRoute);
250 final YangInstanceIdentifier routePath = createRoutePath(ribSupport, tableRibout, initRoute,
252 applyExportPolicy(entryDep, fromPeerId, route, routePath, initRoute.getAttributes()).ifPresent(
253 attributes -> storeRoute(ribSupport, initRoute, route, routePath, attributes, tx));
256 final FluentFuture<? extends CommitInfo> future = tx.commit();
258 future.addCallback(new FutureCallback<CommitInfo>() {
260 public void onSuccess(final CommitInfo result) {
261 LOG.trace("Successful update commit");
265 public void onFailure(final Throwable trw) {
266 LOG.error("Failed update commit", trw);
268 }, MoreExecutors.directExecutor());
272 public final synchronized <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>>
273 void refreshRibOut(final RouteEntryDependenciesContainer entryDep,
274 final List<StaleBestPathRoute> staleRoutes, final List<AdvertizedRoute<C, S>> newRoutes) {
275 if (ribOutChain == null) {
276 LOG.debug("Session closed, skip changes to peer AdjRibsOut {}", getPeerId());
279 final DOMDataTreeWriteTransaction tx = ribOutChain.newWriteOnlyTransaction();
280 final RIBSupport<C, S> ribSupport = entryDep.getRIBSupport();
281 deleteRouteRibOut(ribSupport, staleRoutes, tx);
282 installRouteRibOut(entryDep, newRoutes, tx);
284 final FluentFuture<? extends CommitInfo> future = tx.commit();
286 future.addCallback(new FutureCallback<CommitInfo>() {
288 public void onSuccess(final CommitInfo result) {
289 LOG.trace("Successful update commit");
293 public void onFailure(final Throwable trw) {
294 LOG.error("Failed update commit", trw);
296 }, MoreExecutors.directExecutor());
300 public final synchronized <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>>
301 void reEvaluateAdvertizement(final RouteEntryDependenciesContainer entryDep,
302 final List<ActualBestPathRoutes<C, S>> routesToStore) {
303 if (ribOutChain == null) {
304 LOG.debug("Session closed, skip changes to peer AdjRibsOut {}", getPeerId());
308 final RIBSupport<C, S> ribSupport = entryDep.getRIBSupport();
309 final NodeIdentifierWithPredicates tk = ribSupport.tablesKey();
310 final boolean addPathSupported = supportsAddPathSupported(ribSupport.getTablesKey());
312 final DOMDataTreeWriteTransaction tx = ribOutChain.newWriteOnlyTransaction();
313 for (final ActualBestPathRoutes<C, S> actualBestRoute : routesToStore) {
314 final PeerId fromPeerId = actualBestRoute.getFromPeerId();
315 if (!filterRoutes(fromPeerId, ribSupport.getTablesKey())) {
319 final YangInstanceIdentifier tableRibout = getRibOutIId(tk);
320 // Stale Long-lived Graceful Restart routes should not be propagated
321 if (supportsLLGR() || !actualBestRoute.isDepreferenced()) {
322 final YangInstanceIdentifier routePath = createRoutePath(ribSupport, tableRibout, actualBestRoute,
324 final MapEntryNode route = actualBestRoute.getRoute();
325 final Optional<ContainerNode> effAttr = applyExportPolicy(entryDep, fromPeerId, route, routePath,
326 actualBestRoute.getAttributes());
327 if (effAttr.isPresent()) {
328 storeRoute(ribSupport, actualBestRoute, route, routePath, effAttr.orElseThrow(), tx);
333 deleteRoute(ribSupport, addPathSupported, tableRibout, actualBestRoute, tx);
336 final FluentFuture<? extends CommitInfo> future = tx.commit();
338 future.addCallback(new FutureCallback<CommitInfo>() {
340 public void onSuccess(final CommitInfo result) {
341 LOG.trace("Successful update commit");
345 public void onFailure(final Throwable trw) {
346 LOG.error("Failed update commit", trw);
348 }, MoreExecutors.directExecutor());
351 private Optional<ContainerNode> applyExportPolicy(final RouteEntryDependenciesContainer entryDep,
352 final PeerId fromPeerId, final MapEntryNode route, final YangInstanceIdentifier routePath,
353 final ContainerNode attrs) {
354 final Peer fromPeer = entryDep.getPeerTracker().getPeer(fromPeerId);
355 final RIBSupport<?, ?> ribSupport = entryDep.getRIBSupport();
356 final BGPRouteEntryExportParameters routeEntry = new BGPRouteEntryExportParametersImpl(fromPeer, this,
357 ribSupport.extractRouteKey(route.name()), rtCache);
359 final Attributes bindingAttrs = ribSupport.attributeFromContainerNode(attrs);
360 final Optional<Attributes> optExportAttrs = entryDep.getRoutingPolicies().applyExportPolicies(routeEntry,
361 bindingAttrs, entryDep.getAfiSafType());
362 if (optExportAttrs.isEmpty()) {
364 return Optional.empty();
366 final Attributes exportAttrs = optExportAttrs.orElseThrow();
368 // If the same object is returned we can just reuse 'attrs' instead. Since we are in control of lifecycle here,
369 // we use identity comparison, as equality is too costly for the common case -- assuming export policy will not
370 // churn objects when it does not have to
371 return Optional.of(exportAttrs == bindingAttrs ? attrs
372 : ribSupport.attributeToContainerNode(routePath.node(ribSupport.routeAttributesIdentifier()), exportAttrs));
375 private <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>> void installRouteRibOut(
376 final RouteEntryDependenciesContainer entryDep, final List<AdvertizedRoute<C, S>> routes,
377 final DOMDataTreeWriteOperations tx) {
378 final RIBSupport<C, S> ribSupport = entryDep.getRIBSupport();
379 final TablesKey tk = ribSupport.getTablesKey();
380 final BGPPeerTracker peerTracker = entryDep.getPeerTracker();
381 final boolean addPathSupported = supportsAddPathSupported(tk);
382 final YangInstanceIdentifier tableRibout = getRibOutIId(ribSupport.tablesKey());
384 for (final AdvertizedRoute<C, S> advRoute : routes) {
385 final PeerId fromPeerId = advRoute.getFromPeerId();
386 if (!filterRoutes(fromPeerId, tk) || !advRoute.isFirstBestPath() && !addPathSupported) {
389 if (!supportsLLGR() && advRoute.isDepreferenced()) {
390 // https://tools.ietf.org/html/draft-uttaro-idr-bgp-persistence-04#section-4.3
391 // o The route SHOULD NOT be advertised to any neighbor from which the
392 // Long-lived Graceful Restart Capability has not been received. The
393 // exception is described in the Optional Partial Deployment
394 // Procedure section (Section 4.7). Note that this requirement
395 // implies that such routes should be withdrawn from any such
397 deleteRoute(ribSupport, addPathSupported, tableRibout, advRoute, tx);
401 final Peer fromPeer = peerTracker.getPeer(fromPeerId);
402 final ContainerNode attributes = advRoute.getAttributes();
403 if (fromPeer != null && attributes != null) {
404 final YangInstanceIdentifier routePath = createRoutePath(ribSupport, tableRibout, advRoute,
406 final MapEntryNode route = advRoute.getRoute();
407 applyExportPolicy(entryDep, fromPeerId, route, routePath, attributes).ifPresent(
408 attrs -> storeRoute(ribSupport, advRoute, route, routePath, attrs, tx));
413 private static YangInstanceIdentifier createRoutePath(final RIBSupport<?, ?> ribSupport,
414 final YangInstanceIdentifier tableRibout, final RouteKeyIdentifier advRoute, final boolean withAddPath) {
415 return ribSupport.createRouteIdentifier(tableRibout,
416 withAddPath ? advRoute.getAddPathRouteKeyIdentifier() : advRoute.getNonAddPathRouteKeyIdentifier());
419 private synchronized <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>>
420 void deleteRouteRibOut(final RIBSupport<C, S> ribSupport, final List<StaleBestPathRoute> staleRoutesIid,
421 final DOMDataTreeWriteOperations tx) {
422 final YangInstanceIdentifier tableRibout = getRibOutIId(ribSupport.tablesKey());
423 final boolean addPathSupported = supportsAddPathSupported(ribSupport.getTablesKey());
424 staleRoutesIid.forEach(staleRouteIid
425 -> removeRoute(ribSupport, addPathSupported, tableRibout, staleRouteIid, tx));
428 private <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>> void storeRoute(
429 final RIBSupport<C, S> ribSupport, final RouteKeyIdentifier advRoute, final MapEntryNode route,
430 final YangInstanceIdentifier routePath, final ContainerNode effAttr, final DOMDataTreeWriteOperations tx) {
431 LOG.debug("Write advRoute {} to peer AdjRibsOut {}", advRoute, getPeerId());
432 tx.put(LogicalDatastoreType.OPERATIONAL, routePath, ribSupport.createRoute(route,
433 (NodeIdentifierWithPredicates) routePath.getLastPathArgument(), effAttr));
436 private synchronized <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>>
437 void removeRoute(final RIBSupport<C, S> ribSupport, final boolean addPathSupported,
438 final YangInstanceIdentifier tableRibout, final StaleBestPathRoute staleRouteIid,
439 final DOMDataTreeWriteOperations tx) {
440 if (addPathSupported) {
441 List<NodeIdentifierWithPredicates> staleRoutesIId = staleRouteIid.getAddPathRouteKeyIdentifiers();
442 for (final NodeIdentifierWithPredicates id : staleRoutesIId) {
443 final YangInstanceIdentifier ribOutTarget = ribSupport.createRouteIdentifier(tableRibout, id);
444 LOG.trace("Removing {} from transaction for peer {}", ribOutTarget, getPeerId());
445 tx.delete(LogicalDatastoreType.OPERATIONAL, ribOutTarget);
448 if (!staleRouteIid.isNonAddPathBestPathNew()) {
451 final YangInstanceIdentifier ribOutTarget = ribSupport.createRouteIdentifier(tableRibout,
452 staleRouteIid.getNonAddPathRouteKeyIdentifier());
453 LOG.trace("Removing {} from transaction for peer {}", ribOutTarget, getPeerId());
454 tx.delete(LogicalDatastoreType.OPERATIONAL, ribOutTarget);
458 // FIXME: why is this different from removeRoute()?
459 private <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>> void deleteRoute(
460 final RIBSupport<C, S> ribSupport, final boolean addPathSupported,
461 final YangInstanceIdentifier tableRibout, final AbstractAdvertizedRoute<C, S> advRoute,
462 final DOMDataTreeWriteOperations tx) {
463 final YangInstanceIdentifier ribOutTarget = ribSupport.createRouteIdentifier(tableRibout,
464 addPathSupported ? advRoute.getAddPathRouteKeyIdentifier() : advRoute.getNonAddPathRouteKeyIdentifier());
465 LOG.trace("Removing {} from transaction for peer {}", ribOutTarget, getPeerId());
466 tx.delete(LogicalDatastoreType.OPERATIONAL, ribOutTarget);
469 // FIXME: make this asynchronous?
470 final synchronized void releaseRibOutChain(final boolean isWaitForSubmitted) {
471 if (isWaitForSubmitted) {
472 if (submitted != null) {
475 } catch (final InterruptedException | ExecutionException throwable) {
476 LOG.error("Write routes failed", throwable);
481 if (ribOutChain != null) {
482 LOG.info("Closing peer chain {}", getPeerId());
488 final synchronized void createDomChain() {
489 if (domChain == null) {
490 LOG.info("Creating DOM peer chain {}", getPeerId());
491 domChain = rib.createPeerDOMChain();
492 domChain.addCallback(this);
496 final synchronized void closeDomChain() {
497 if (domChain != null) {
498 LOG.info("Closing DOM peer chain {}", getPeerId());
504 boolean supportsLLGR() {