BUG-6747: Race condition on peer connection
[bgpcep.git] / bgp / path-selection-mode / src / main / java / org / opendaylight / protocol / bgp / mode / impl / base / BaseAbstractRouteEntry.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.protocol.bgp.mode.impl.base;
9
10 import com.google.common.annotations.VisibleForTesting;
11 import com.google.common.primitives.UnsignedInteger;
12 import javax.annotation.concurrent.NotThreadSafe;
13 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
14 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
15 import org.opendaylight.protocol.bgp.mode.api.BestPath;
16 import org.opendaylight.protocol.bgp.mode.spi.AbstractRouteEntry;
17 import org.opendaylight.protocol.bgp.rib.spi.ExportPolicyPeerTracker;
18 import org.opendaylight.protocol.bgp.rib.spi.PeerExportGroup;
19 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
20 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.PeerId;
21 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.PeerRole;
22 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.TablesKey;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
26 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
27 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
28 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 @NotThreadSafe
33 abstract class BaseAbstractRouteEntry extends AbstractRouteEntry {
34     private static final Logger LOG = LoggerFactory.getLogger(BaseAbstractRouteEntry.class);
35     private static final ContainerNode[] EMPTY_ATTRIBUTES = new ContainerNode[0];
36     private OffsetMap offsets = OffsetMap.EMPTY;
37     private ContainerNode[] values = EMPTY_ATTRIBUTES;
38     private BaseBestPath bestPath;
39     private BaseBestPath removedBestPath;
40
41     private int addRoute(final UnsignedInteger routerId, final ContainerNode attributes) {
42         int offset = this.offsets.offsetOf(routerId);
43         if (offset < 0) {
44             final OffsetMap newOffsets = this.offsets.with(routerId);
45             offset = newOffsets.offsetOf(routerId);
46
47             this.values = newOffsets.expand(this.offsets, this.values, offset);
48             this.offsets = newOffsets;
49         }
50
51         this.offsets.setValue(this.values, offset, attributes);
52         LOG.trace("Added route from {} attributes {}", routerId, attributes);
53         return offset;
54     }
55
56     /**
57      * Remove route
58      *
59      * @param routerId router ID in unsigned integer
60      * @param offset of removed route
61      * @return true if its the last route
62      */
63     protected final boolean removeRoute(final UnsignedInteger routerId, final int offset) {
64         this.values = this.offsets.removeValue(this.values, offset);
65         this.offsets = this.offsets.without(routerId);
66         return this.offsets.isEmpty();
67     }
68
69     @Override
70     public final boolean selectBest(final long localAs) {
71         /*
72          * FIXME: optimize flaps by making sure we consider stability of currently-selected route.
73          */
74         final BasePathSelector selector = new BasePathSelector(localAs);
75
76         // Select the best route.
77         for (int i = 0; i < this.offsets.size(); ++i) {
78             final UnsignedInteger routerId = this.offsets.getRouterKey(i);
79             final ContainerNode attributes = this.offsets.getValue(this.values, i);
80             LOG.trace("Processing router id {} attributes {}", routerId, attributes);
81             selector.processPath(routerId, attributes);
82         }
83
84         // Get the newly-selected best path.
85         final BaseBestPath newBestPath = selector.result();
86         final boolean modified = newBestPath == null || !newBestPath.equals(this.bestPath);
87         if (modified) {
88             if(this.offsets.isEmpty()) {
89                 this.removedBestPath = this.bestPath;
90             }
91             LOG.trace("Previous best {}, current best {}", this.bestPath, newBestPath);
92             this.bestPath = newBestPath;
93         }
94         return modified;
95     }
96
97     @Override
98     public int addRoute(final UnsignedInteger routerId, final Long remotePathId, final NodeIdentifier attributesIdentifier, final NormalizedNode<?, ?> data) {
99         LOG.trace("Find {} in {}", attributesIdentifier, data);
100         final ContainerNode advertisedAttrs = (ContainerNode) NormalizedNodes.findNode(data, attributesIdentifier).orNull();
101         return addRoute(routerId, advertisedAttrs);
102     }
103
104     @Override
105     public void updateRoute(final TablesKey localTK, final ExportPolicyPeerTracker peerPT, final YangInstanceIdentifier locRibTarget, final RIBSupport ribSup,
106         final DOMDataWriteTransaction tx, final PathArgument routeIdPA) {
107         if (this.removedBestPath != null) {
108             removePathFromDataStore(this.removedBestPath, routeIdPA, locRibTarget, peerPT, localTK, ribSup, tx);
109             this.removedBestPath = null;
110         }
111         if (this.bestPath != null) {
112             addPathToDataStore(this.bestPath, routeIdPA, locRibTarget, ribSup, peerPT, localTK, tx);
113         }
114     }
115
116     @Override
117     public void writeRoute(final PeerId destPeer, final PathArgument routeId, final YangInstanceIdentifier rootPath, final PeerExportGroup peerGroup,
118         final TablesKey localTK, final ExportPolicyPeerTracker peerPT, final RIBSupport ribSupport, final DOMDataWriteTransaction tx) {
119         if (this.bestPath != null) {
120             final BaseBestPath path = this.bestPath;
121             final PeerRole destPeerRole = getRoutePeerIdRole(peerPT, destPeer);
122             if (filterRoutes(path.getPeerId(), destPeer, peerPT, localTK, destPeerRole)) {
123                 final ContainerNode effAttrib = peerGroup.effectiveAttributes(getRoutePeerIdRole(peerPT,path.getPeerId()), path.getAttributes());
124                 writeRoute(destPeer, getAdjRibOutYII(ribSupport, rootPath, routeId, localTK), effAttrib,
125                     createValue(routeId, path), ribSupport, tx);
126             }
127         }
128     }
129
130     private void removePathFromDataStore(final BestPath path, final PathArgument routeIdPA, final YangInstanceIdentifier locRibTarget,
131         final ExportPolicyPeerTracker peerPT, final TablesKey localTK, final RIBSupport ribSup, final DOMDataWriteTransaction tx) {
132         LOG.trace("Best Path removed {}", path);
133         final PathArgument routeIdAddPath = ribSup.getRouteIdAddPath(path.getPathId(), routeIdPA);
134         final YangInstanceIdentifier pathTarget = ribSup.routePath(locRibTarget.node(ROUTES_IDENTIFIER), routeIdPA);
135         YangInstanceIdentifier pathAddPathTarget = null;
136         if (routeIdAddPath != null) {
137             pathAddPathTarget = ribSup.routePath(locRibTarget.node(ROUTES_IDENTIFIER), routeIdAddPath);
138         }
139         fillLocRib(pathAddPathTarget == null ? pathTarget : pathAddPathTarget, null, tx);
140         fillAdjRibsOut(null, null, routeIdPA, path.getPeerId(), peerPT, localTK, ribSup, tx);
141     }
142
143     private void addPathToDataStore(final BestPath path, final PathArgument routeIdPA, final YangInstanceIdentifier locRibTarget,
144         final RIBSupport ribSup, final ExportPolicyPeerTracker peerPT, final TablesKey localTK, final DOMDataWriteTransaction tx) {
145         final PathArgument routeIdAddPath = ribSup.getRouteIdAddPath(path.getPathId(), routeIdPA);
146         final YangInstanceIdentifier pathTarget = ribSup.routePath(locRibTarget.node(ROUTES_IDENTIFIER), routeIdPA);
147         final NormalizedNode<?, ?> value = createValue(routeIdPA, path);
148         NormalizedNode<?, ?> addPathValue = null;
149         YangInstanceIdentifier pathAddPathTarget = null;
150         if (routeIdAddPath == null) {
151             LOG.trace("Selected best value {}", value);
152         } else {
153             pathAddPathTarget = ribSup.routePath(locRibTarget.node(ROUTES_IDENTIFIER), routeIdAddPath);
154             addPathValue = createValue(routeIdAddPath, path);
155             LOG.trace("Selected best value {}", addPathValue);
156         }
157         fillLocRib(pathAddPathTarget == null ? pathTarget : pathAddPathTarget, addPathValue == null ? value : addPathValue, tx);
158         fillAdjRibsOut(path.getAttributes(), value, routeIdPA, path.getPeerId(), peerPT, localTK, ribSup, tx);
159     }
160
161     final OffsetMap getOffsets() {
162         return this.offsets;
163     }
164
165     @VisibleForTesting
166     private void fillAdjRibsOut(final ContainerNode attributes, final NormalizedNode<?, ?> value,
167         final PathArgument routeId, final PeerId routePeerId, final ExportPolicyPeerTracker peerPT, final TablesKey localTK, final RIBSupport ribSup,
168         final DOMDataWriteTransaction tx) {
169         /*
170          * We need to keep track of routers and populate adj-ribs-out, too. If we do not, we need to
171          * expose from which client a particular route was learned from in the local RIB, and have
172          * the listener perform filtering.
173          *
174          * We walk the policy set in order to minimize the amount of work we do for multiple peers:
175          * if we have two eBGP peers, for example, there is no reason why we should perform the translation
176          * multiple times.
177          */
178         for (final PeerRole role : PeerRole.values()) {
179             final PeerExportGroup peerGroup = peerPT.getPeerGroup(role);
180             if (peerGroup != null) {
181                 final ContainerNode effAttrib = peerGroup.effectiveAttributes(getRoutePeerIdRole(peerPT, routePeerId), attributes);
182                 peerGroup.getPeers().stream()
183                     .filter(pid -> filterRoutes(routePeerId, pid.getKey(), peerPT, localTK, getRoutePeerIdRole(peerPT, pid.getKey())))
184                     .forEach(pid -> update(pid.getKey(), getAdjRibOutYII(ribSup, pid.getValue().getYii(), routeId, localTK), effAttrib, value, ribSup, tx));
185             }
186         }
187     }
188
189     private void update(final PeerId destPeer, final YangInstanceIdentifier routeTarget, final ContainerNode effAttrib,
190         final NormalizedNode<?, ?> value, final RIBSupport ribSup, final DOMDataWriteTransaction tx) {
191         if (!writeRoute(destPeer, routeTarget, effAttrib, value, ribSup, tx)) {
192             LOG.trace("Removing {} from transaction for peer {}", routeTarget, destPeer);
193             tx.delete(LogicalDatastoreType.OPERATIONAL, routeTarget);
194         }
195     }
196 }