2 * Copyright (c) 2015 Cisco Systems, Inc. and others. 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.mode.impl.add;
10 import com.google.common.collect.Lists;
11 import com.google.common.primitives.UnsignedInteger;
12 import java.util.ArrayList;
13 import java.util.List;
15 import java.util.Optional;
16 import javax.annotation.concurrent.NotThreadSafe;
17 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
18 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
19 import org.opendaylight.protocol.bgp.mode.api.BestPath;
20 import org.opendaylight.protocol.bgp.mode.spi.AbstractRouteEntry;
21 import org.opendaylight.protocol.bgp.rib.spi.CacheDisconnectedPeers;
22 import org.opendaylight.protocol.bgp.rib.spi.ExportPolicyPeerTracker;
23 import org.opendaylight.protocol.bgp.rib.spi.PeerExportGroup;
24 import org.opendaylight.protocol.bgp.rib.spi.PeerExportGroup.PeerExporTuple;
25 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
26 import org.opendaylight.protocol.bgp.rib.spi.RouterIds;
27 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Address;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.PeerId;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.PeerRole;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.TablesKey;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
33 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
34 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
42 * A single route entry inside a route table. Maintains the attributes of
43 * from all contributing peers. The information is stored in arrays with a
44 * shared map of offsets for peers to allow lookups. This is needed to
45 * maintain low memory overhead in face of large number of routes and peers,
46 * where individual object overhead becomes the dominating factor.
49 public abstract class AddPathAbstractRouteEntry extends AbstractRouteEntry {
51 private static final Logger LOG = LoggerFactory.getLogger(AddPathAbstractRouteEntry.class);
52 private List<AddPathBestPath> bestPath;
53 private List<AddPathBestPath> bestPathRemoved;
54 protected OffsetMap offsets = OffsetMap.EMPTY;
55 protected ContainerNode[] values = new ContainerNode[0];
56 protected Long[] pathsId = new Long[0];
57 private long pathIdCounter = 0L;
58 private boolean oldNonAddPathBestPathTheSame;
59 private List<AddPathBestPath> newBestPathToBeAdvertised;
60 private List<RemovedPath> removedPaths;
62 private static final class RemovedPath {
63 private final RouteKey key;
64 private final Long pathId;
66 RemovedPath(final RouteKey key, final Long pathId) {
75 UnsignedInteger getRouteId() {
76 return this.key.getRouteId();
80 private int addRoute(final RouteKey key, final ContainerNode attributes) {
81 int offset = this.offsets.offsetOf(key);
83 final OffsetMap newOffsets = this.offsets.with(key);
84 offset = newOffsets.offsetOf(key);
85 final ContainerNode[] newAttributes = newOffsets.expand(this.offsets, this.values, offset);
86 final Long[] newPathsId = newOffsets.expand(this.offsets, this.pathsId, offset);
87 this.values = newAttributes;
88 this.offsets = newOffsets;
89 this.pathsId = newPathsId;
90 this.offsets.setValue(this.pathsId, offset, ++this.pathIdCounter);
92 this.offsets.setValue(this.values, offset, attributes);
93 LOG.trace("Added route from {} attributes {}", key.getRouteId(), attributes);
97 protected int addRoute(final RouteKey key, final NodeIdentifier attributesIdentifier, final NormalizedNode<?, ?> data) {
98 LOG.trace("Find {} in {}", attributesIdentifier, data);
99 final ContainerNode advertisedAttrs = (ContainerNode) NormalizedNodes.findNode(data, attributesIdentifier).orNull();
100 return addRoute(key, advertisedAttrs);
106 * @param key RouteKey of removed route
107 * @param offset Offset of removed route
108 * @return true if it was the last route
110 protected final boolean removeRoute(final RouteKey key, final int offset) {
111 final Long pathId = this.offsets.getValue(this.pathsId, offset);
112 this.values = this.offsets.removeValue(this.values, offset);
113 this.pathsId = this.offsets.removeValue(this.pathsId, offset);
114 this.offsets = this.offsets.without(key);
115 if(this.removedPaths == null) {
116 this.removedPaths = new ArrayList<>();
118 this.removedPaths.add(new RemovedPath(key, pathId));
123 public void updateRoute(final TablesKey localTK, final ExportPolicyPeerTracker peerPT, final YangInstanceIdentifier locRibTarget, final RIBSupport ribSup,
124 final CacheDisconnectedPeers discPeers, final DOMDataWriteTransaction tx, final PathArgument routeIdPA) {
125 //FIXME: Here we should have 2 independent removal for LocRib and RibOut, first will be only executed for best path changes, the second
126 // when the owner of the route removes it.
127 if(this.bestPathRemoved != null) {
128 this.bestPathRemoved.forEach(path -> {
129 final PathArgument routeIdAddPath = ribSup.getRouteIdAddPath(path.getPathId(), routeIdPA);
130 final YangInstanceIdentifier pathAddPathTarget = ribSup.routePath(locRibTarget.node(ROUTES_IDENTIFIER), routeIdAddPath);
131 fillLocRib(pathAddPathTarget, null, tx);
133 this.bestPathRemoved = null;
135 if(this.removedPaths != null) {
136 this.removedPaths.forEach(removedPath -> {
137 final PathArgument routeIdAddPath = ribSup.getRouteIdAddPath(removedPath.getPathId(), routeIdPA);
138 fillAdjRibsOut(true, null, null, null, routeIdPA, routeIdAddPath, RouterIds.createPeerId(removedPath.getRouteId()),
139 peerPT, localTK, ribSup, discPeers, tx);
141 this.removedPaths = null;
144 if(this.newBestPathToBeAdvertised != null) {
145 this.newBestPathToBeAdvertised.forEach(path -> addPathToDataStore(path, isFirstBestPath(this.bestPath.indexOf(path)), routeIdPA,
146 locRibTarget, ribSup, peerPT, localTK, discPeers, tx));
147 this.newBestPathToBeAdvertised = null;
152 public void writeRoute(final PeerId destPeer, final PathArgument routeId, final YangInstanceIdentifier rootPath, final PeerExportGroup peerGroup,
153 final TablesKey localTK, final ExportPolicyPeerTracker peerPT, final RIBSupport ribSup, final CacheDisconnectedPeers discPeers,
154 final DOMDataWriteTransaction tx) {
155 final boolean destPeerSupAddPath = peerPT.isAddPathSupportedByPeer(destPeer);
156 if(this.bestPath != null) {
157 final PeerRole destPeerRole = getRoutePeerIdRole(peerPT, destPeer);
158 this.bestPath.stream().filter(path -> filterRoutes(path.getPeerId(), destPeer, peerPT, localTK, discPeers, destPeerRole) &&
159 peersSupportsAddPathOrIsFirstBestPath(destPeerSupAddPath, isFirstBestPath(this.bestPath.indexOf(path))))
160 .forEach(path -> writeRoutePath(destPeer, routeId, peerPT, peerGroup, destPeerSupAddPath, path, rootPath, localTK, ribSup, tx));
164 private void writeRoutePath(final PeerId destPeer, final PathArgument routeId, final ExportPolicyPeerTracker peerPT,
165 final PeerExportGroup peerGroup, final boolean destPeerSupAddPath,
166 final BestPath path, final YangInstanceIdentifier rootPath, final TablesKey localTK, final RIBSupport ribSup, final DOMDataWriteTransaction tx) {
167 final PathArgument routeIdAddPath = ribSup.getRouteIdAddPath(path.getPathId(), routeId);
168 final ContainerNode effectiveAttributes = peerGroup.effectiveAttributes(getRoutePeerIdRole(peerPT,path.getPeerId()), path.getAttributes());
169 if (destPeerSupAddPath) {
170 writeRoute(destPeer, getAdjRibOutYII(ribSup, rootPath, routeIdAddPath, localTK), effectiveAttributes, createValue(routeIdAddPath, path), ribSup, tx);
172 writeRoute(destPeer, getAdjRibOutYII(ribSup, rootPath, routeId, localTK), effectiveAttributes, createValue(routeId, path), ribSup, tx);
176 private void addPathToDataStore(final BestPath path, final boolean isFirstBestPath, final PathArgument routeIdPA, final YangInstanceIdentifier locRibTarget,
177 final RIBSupport ribSup, final ExportPolicyPeerTracker peerPT, final TablesKey localTK, final CacheDisconnectedPeers discPeers,
178 final DOMDataWriteTransaction tx) {
179 final PathArgument routeIdAddPath = ribSup.getRouteIdAddPath(path.getPathId(), routeIdPA);
180 final YangInstanceIdentifier pathAddPathTarget = ribSup.routePath(locRibTarget.node(ROUTES_IDENTIFIER), routeIdAddPath);
181 final MapEntryNode addPathValue = createValue(routeIdAddPath, path);
182 final MapEntryNode value = createValue(routeIdPA, path);
183 LOG.trace("Selected best value {}", addPathValue);
184 fillLocRib(pathAddPathTarget, addPathValue, tx);
185 fillAdjRibsOut(isFirstBestPath, path.getAttributes(), value, addPathValue, routeIdPA, routeIdAddPath, path.getPeerId(), peerPT, localTK,
186 ribSup, discPeers, tx);
189 private void removePathFromDataStore(final BestPath path, final boolean isFirstBestPath, final PathArgument routeIdPA,
190 final YangInstanceIdentifier locRibTarget, final RIBSupport ribSup, final ExportPolicyPeerTracker peerPT, final TablesKey localTK,
191 final CacheDisconnectedPeers discPeers, final DOMDataWriteTransaction tx) {
192 LOG.trace("Best Path removed {}", path);
193 final PathArgument routeIdAddPath = ribSup.getRouteIdAddPath(path.getPathId(), routeIdPA);
194 final YangInstanceIdentifier pathAddPathTarget = ribSup.routePath(locRibTarget.node(ROUTES_IDENTIFIER), routeIdAddPath);
196 fillLocRib(pathAddPathTarget, null, tx);
197 fillAdjRibsOut(isFirstBestPath, null, null, null, routeIdPA, routeIdAddPath, path.getPeerId(), peerPT, localTK, ribSup, discPeers, tx);
200 private void fillAdjRibsOut(final boolean isFirstBestPath, final ContainerNode attributes, final NormalizedNode<?, ?> value, final MapEntryNode addPathValue,
201 final PathArgument routeId, final PathArgument routeIdAddPath, final PeerId routePeerId, final ExportPolicyPeerTracker peerPT, final TablesKey
202 localTK, final RIBSupport ribSup, final CacheDisconnectedPeers discPeers, final DOMDataWriteTransaction tx) {
204 * We need to keep track of routers and populate adj-ribs-out, too. If we do not, we need to
205 * expose from which client a particular route was learned from in the local RIB, and have
206 * the listener perform filtering.
208 * We walk the policy set in order to minimize the amount of work we do for multiple peers:
209 * if we have two eBGP peers, for example, there is no reason why we should perform the translation
212 for (final PeerRole role : PeerRole.values()) {
213 final PeerExportGroup peerGroup = peerPT.getPeerGroup(role);
214 if (peerGroup != null) {
215 final ContainerNode effectiveAttributes = peerGroup.effectiveAttributes(getRoutePeerIdRole(peerPT, routePeerId), attributes);
216 for (final Map.Entry<PeerId, PeerExporTuple> pid : peerGroup.getPeers()) {
217 final PeerId destPeer = pid.getKey();
218 final boolean destPeerSupAddPath = peerPT.isAddPathSupportedByPeer(destPeer);
219 if (filterRoutes(routePeerId, destPeer, peerPT, localTK, discPeers, getRoutePeerIdRole(peerPT, destPeer))
220 && peersSupportsAddPathOrIsFirstBestPath(destPeerSupAddPath, isFirstBestPath)) {
221 if (destPeerSupAddPath) {
222 update(destPeer, getAdjRibOutYII(ribSup, pid.getValue().getYii(), routeIdAddPath, localTK), effectiveAttributes,
223 addPathValue, ribSup, tx);
224 } else if(!this.oldNonAddPathBestPathTheSame){
225 update(destPeer, getAdjRibOutYII(ribSup, pid.getValue().getYii(), routeId, localTK), effectiveAttributes, value, ribSup, tx);
233 private void update(final PeerId destPeer, final YangInstanceIdentifier routeTarget, final ContainerNode effAttr, final NormalizedNode<?, ?> value,
234 final RIBSupport ribSup, final DOMDataWriteTransaction tx) {
235 if (!writeRoute(destPeer, routeTarget, effAttr, value, ribSup, tx)) {
236 LOG.trace("Removing {} from transaction for peer {}", routeTarget, destPeer);
237 tx.delete(LogicalDatastoreType.OPERATIONAL, routeTarget);
241 protected final OffsetMap getOffsets() {
245 public final boolean isEmpty() {
246 return this.offsets.isEmpty();
249 private void selectBest(final RouteKey key, final AddPathSelector selector) {
250 final int offset = this.offsets.offsetOf(key);
251 final ContainerNode attributes = this.offsets.getValue(this.values, offset);
252 final Long pathId = this.offsets.getValue(this.pathsId, offset);
253 LOG.trace("Processing router key {} attributes {}", key, attributes);
254 selector.processPath(attributes, key, offset, pathId);
258 * Process best path selection
260 * @param localAs The local autonomous system number
261 * @param keyList List of RouteKey
262 * @return the best path inside offset map passed
264 protected AddPathBestPath selectBest(final long localAs, final List<RouteKey> keyList) {
266 * FIXME: optimize flaps by making sure we consider stability of currently-selected route.
268 final AddPathSelector selector = new AddPathSelector(localAs);
269 Lists.reverse(keyList).forEach(key -> selectBest(key, selector));
270 LOG.trace("Best path selected {}", this.bestPath);
271 return selector.result();
274 private boolean isFirstBestPath(final int bestPathPosition) {
275 return bestPathPosition == 0;
278 private boolean peersSupportsAddPathOrIsFirstBestPath(final boolean peerSupportsAddPath, final boolean isFirstBestPath) {
279 return !(!peerSupportsAddPath && !isFirstBestPath);
282 protected boolean isBestPathNew(final List<AddPathBestPath> newBestPathList) {
283 this.oldNonAddPathBestPathTheSame = isNonAddPathBestPathTheSame(newBestPathList);
284 filterRemovedPaths(newBestPathList);
285 if (this.bestPathRemoved != null && !this.bestPathRemoved.isEmpty() || newBestPathList != null && !newBestPathList.equals(this.bestPath)) {
286 this.newBestPathToBeAdvertised = new ArrayList<>(newBestPathList);
287 if(this.bestPath != null) {
288 this.newBestPathToBeAdvertised.removeAll(this.bestPath);
290 this.bestPath = newBestPathList;
291 LOG.trace("Actual Best {}, removed best {}", this.bestPath, this.bestPathRemoved);
297 private boolean isNonAddPathBestPathTheSame(final List<AddPathBestPath> newBestPathList) {
298 return !(this.bestPath == null || newBestPathList == null || this.bestPath.isEmpty() || newBestPathList.isEmpty()) &&
299 this.bestPath.get(0).equals(newBestPathList.get(0));
302 private void filterRemovedPaths(final List<AddPathBestPath> newBestPathList) {
303 if(this.bestPath == null) {
306 this.bestPathRemoved = new ArrayList<>(this.bestPath);
307 this.bestPath.forEach(oldBest -> {
308 final Optional<AddPathBestPath> present = newBestPathList.stream()
309 .filter(newBest -> newBest.getPathId() == oldBest.getPathId() && newBest.getRouteKey() == oldBest.getRouteKey()).findAny();
310 if(present.isPresent()) {
311 this.bestPathRemoved.remove(oldBest);