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 static org.opendaylight.protocol.bgp.parser.spi.PathIdUtil.NON_PATH_ID;
12 import com.google.common.collect.Lists;
13 import com.google.common.primitives.UnsignedInteger;
14 import java.util.ArrayList;
15 import java.util.List;
16 import java.util.Optional;
17 import javax.annotation.concurrent.NotThreadSafe;
18 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
19 import org.opendaylight.protocol.bgp.mode.spi.AbstractRouteEntry;
20 import org.opendaylight.protocol.bgp.rib.spi.ExportPolicyPeerTracker;
21 import org.opendaylight.protocol.bgp.rib.spi.PeerExportGroup;
22 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
23 import org.opendaylight.protocol.bgp.rib.spi.RouterIds;
24 import org.opendaylight.protocol.bgp.rib.spi.entry.RouteEntryDependenciesContainer;
25 import org.opendaylight.protocol.bgp.rib.spi.entry.RouteEntryInfo;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.PeerId;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.PeerRole;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.rib.TablesKey;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
33 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
41 * A single route entry inside a route table. Maintains the attributes of
42 * from all contributing peers. The information is stored in arrays with a
43 * shared map of offsets for peers to allow lookups. This is needed to
44 * maintain low memory overhead in face of large number of routes and peers,
45 * where individual object overhead becomes the dominating factor.
48 public abstract class AddPathAbstractRouteEntry extends AbstractRouteEntry<AddPathBestPath> {
50 private static final Logger LOG = LoggerFactory.getLogger(AddPathAbstractRouteEntry.class);
51 private List<AddPathBestPath> bestPath;
52 private List<AddPathBestPath> bestPathRemoved;
53 protected OffsetMap offsets = OffsetMap.EMPTY;
54 protected ContainerNode[] values = new ContainerNode[0];
55 protected Long[] pathsId = new Long[0];
56 private long pathIdCounter = 0L;
57 private boolean oldNonAddPathBestPathTheSame;
58 private List<AddPathBestPath> newBestPathToBeAdvertised;
59 private List<RemovedPath> removedPaths;
61 private static final class RemovedPath {
62 private final RouteKey key;
63 private final Long pathId;
65 RemovedPath(final RouteKey key, final Long pathId) {
74 UnsignedInteger getRouteId() {
75 return this.key.getRouteId();
79 private int addRoute(final RouteKey key, final ContainerNode attributes) {
80 int offset = this.offsets.offsetOf(key);
82 final OffsetMap newOffsets = this.offsets.with(key);
83 offset = newOffsets.offsetOf(key);
84 final ContainerNode[] newAttributes = newOffsets.expand(this.offsets, this.values, offset);
85 final Long[] newPathsId = newOffsets.expand(this.offsets, this.pathsId, offset);
86 this.values = newAttributes;
87 this.offsets = newOffsets;
88 this.pathsId = newPathsId;
89 this.offsets.setValue(this.pathsId, offset, ++this.pathIdCounter);
91 this.offsets.setValue(this.values, offset, attributes);
92 LOG.trace("Added route from {} attributes {}", key.getRouteId(), attributes);
96 protected int addRoute(final RouteKey key, final NodeIdentifier attributesIdentifier,
97 final NormalizedNode<?, ?> data) {
98 LOG.trace("Find {} in {}", attributesIdentifier, data);
99 final ContainerNode advertisedAttrs
100 = (ContainerNode) NormalizedNodes.findNode(data, attributesIdentifier).orElse(null);
101 return addRoute(key, advertisedAttrs);
107 * @param key RouteKey of removed route
108 * @param offset Offset of removed route
109 * @return true if it was the last route
111 protected final boolean removeRoute(final RouteKey key, final int offset) {
112 final Long pathId = this.offsets.getValue(this.pathsId, offset);
113 this.values = this.offsets.removeValue(this.values, offset);
114 this.pathsId = this.offsets.removeValue(this.pathsId, offset);
115 this.offsets = this.offsets.without(key);
116 if (this.removedPaths == null) {
117 this.removedPaths = new ArrayList<>();
119 this.removedPaths.add(new RemovedPath(key, pathId));
124 public void updateBestPaths(final RouteEntryDependenciesContainer entryDependencies,
125 final NodeIdentifierWithPredicates routeIdPA, final DOMDataWriteTransaction tx) {
126 final RIBSupport ribSupport = entryDependencies.getRibSupport();
127 final ExportPolicyPeerTracker peerPT = entryDependencies.getExportPolicyPeerTracker();
128 if (this.bestPathRemoved != null) {
129 this.bestPathRemoved.forEach(path -> {
130 final PathArgument routeIdAddPath = ribSupport.getRouteIdAddPath(path.getPathId(), routeIdPA);
131 final YangInstanceIdentifier pathAddPathTarget = ribSupport
132 .routePath(entryDependencies.getLocRibTableTarget().node(ROUTES_IDENTIFIER), routeIdAddPath);
133 fillLocRib(pathAddPathTarget, null, tx);
135 this.bestPathRemoved = null;
137 if (this.removedPaths != null) {
138 this.removedPaths.forEach(removedPath -> {
139 final PathArgument routeIdAddPath = ribSupport.getRouteIdAddPath(removedPath.getPathId(), routeIdPA);
140 final PathArgument routeIdAddPathDefault = ribSupport.getRouteIdAddPath(NON_PATH_ID, routeIdPA);
141 fillAdjRibsOut(true, null, null, null,
142 routeIdAddPathDefault, routeIdAddPath,
143 RouterIds.createPeerId(removedPath.getRouteId()),
144 peerPT, entryDependencies.getLocalTablesKey(), ribSupport, tx);
146 this.removedPaths = null;
149 if (this.newBestPathToBeAdvertised != null) {
150 this.newBestPathToBeAdvertised.forEach(path -> addPathToDataStore(entryDependencies, path,
151 isFirstBestPath(this.bestPath.indexOf(path)), routeIdPA,
153 this.newBestPathToBeAdvertised = null;
158 public void initializeBestPaths(final RouteEntryDependenciesContainer entryDependencies,
159 final RouteEntryInfo entryInfo, final PeerExportGroup peerGroup, final DOMDataWriteTransaction tx) {
160 final PeerId toPeer = entryInfo.getToPeerId();
161 final ExportPolicyPeerTracker peerPT = entryDependencies.getExportPolicyPeerTracker();
162 final boolean destPeerSupAddPath = peerPT.isAddPathSupportedByPeer(toPeer);
163 if (this.bestPath != null) {
164 final PeerRole destPeerRole = getRoutePeerIdRole(peerPT, toPeer);
165 final TablesKey localTk = entryDependencies.getLocalTablesKey();
166 final RIBSupport ribSup = entryDependencies.getRibSupport();
167 this.bestPath.stream().filter(path -> filterRoutes(path.getPeerId(), toPeer, peerPT, localTk,
168 destPeerRole) && peersSupportsAddPathOrIsFirstBestPath(destPeerSupAddPath,
169 isFirstBestPath(this.bestPath.indexOf(path))))
170 .forEach(path -> writeRoutePath(entryInfo, peerPT, peerGroup, destPeerSupAddPath,
171 path, localTk, ribSup, tx));
175 private void writeRoutePath(final RouteEntryInfo entryInfo, final ExportPolicyPeerTracker peerPT,
176 final PeerExportGroup peerGroup, final boolean destPeerSupAddPath, final AddPathBestPath path,
177 final TablesKey localTK, final RIBSupport ribSup, final DOMDataWriteTransaction tx) {
178 final NodeIdentifierWithPredicates routeId = entryInfo.getRouteId();
179 final ContainerNode effectiveAttributes = peerGroup
180 .effectiveAttributes(getRoutePeerIdRole(peerPT, path.getPeerId()), path.getAttributes());
181 final NodeIdentifierWithPredicates routeIdAddPath = ribSup
182 .getRouteIdAddPath(destPeerSupAddPath ? path.getPathId() : NON_PATH_ID, routeId);
184 writeRoute(entryInfo.getToPeerId(), getAdjRibOutYII(ribSup, entryInfo.getRootPath(), routeIdAddPath, localTK),
185 effectiveAttributes, createValue(routeIdAddPath, path), ribSup, tx);
188 private void addPathToDataStore(final RouteEntryDependenciesContainer entryDependencies, final AddPathBestPath path,
189 final boolean isFirstBestPath, final NodeIdentifierWithPredicates routeIdPA,
190 final ExportPolicyPeerTracker peerPT, final DOMDataWriteTransaction tx) {
191 final RIBSupport ribSup = entryDependencies.getRibSupport();
192 final NodeIdentifierWithPredicates routeIdAddPath = ribSup.getRouteIdAddPath(path.getPathId(), routeIdPA);
193 final NodeIdentifierWithPredicates routeIdAddPathDefault = ribSup.getRouteIdAddPath(NON_PATH_ID, routeIdPA);
194 final YangInstanceIdentifier pathAddPathTarget = ribSup.routePath(entryDependencies.getLocRibTableTarget()
195 .node(ROUTES_IDENTIFIER), routeIdAddPath);
196 final MapEntryNode addPathValue = createValue(routeIdAddPath, path);
197 final MapEntryNode defaultValue = createValue(routeIdAddPathDefault, path);
198 LOG.trace("Selected best value {}", addPathValue);
199 fillLocRib(pathAddPathTarget, addPathValue, tx);
200 fillAdjRibsOut(isFirstBestPath, path.getAttributes(), defaultValue, addPathValue, routeIdAddPathDefault,
201 routeIdAddPath, path.getPeerId(), peerPT, entryDependencies.getLocalTablesKey(), ribSup, tx);
204 private void fillAdjRibsOut(final boolean isFirstBestPath, final ContainerNode attributes,
205 final MapEntryNode defaultValue, final MapEntryNode addPathValue,
206 final PathArgument routeIdAddPathDefault,
207 final PathArgument routeIdAddPath, final PeerId routePeerId, final ExportPolicyPeerTracker peerPT,
208 final TablesKey localTK, final RIBSupport ribSup, final DOMDataWriteTransaction tx) {
210 * We need to keep track of routers and populate adj-ribs-out, too. If we do not, we need to
211 * expose from which client a particular route was learned from in the local RIB, and have
212 * the listener perform filtering.
214 * We walk the policy set in order to minimize the amount of work we do for multiple peers:
215 * if we have two eBGP peers, for example, there is no reason why we should perform the translation
218 for (final PeerRole role : PeerRole.values()) {
219 final PeerExportGroup peerGroup = peerPT.getPeerGroup(role);
220 if (peerGroup != null) {
221 final ContainerNode effectiveAttributes = peerGroup.effectiveAttributes(getRoutePeerIdRole(peerPT,
222 routePeerId), attributes);
223 peerGroup.forEach((destPeer, rootPath) -> {
224 final boolean destPeerSupAddPath = peerPT.isAddPathSupportedByPeer(destPeer);
225 if (filterRoutes(routePeerId, destPeer, peerPT, localTK, role)
226 && peersSupportsAddPathOrIsFirstBestPath(destPeerSupAddPath, isFirstBestPath)) {
227 if (destPeerSupAddPath) {
228 update(destPeer, getAdjRibOutYII(ribSup, rootPath, routeIdAddPath, localTK),
229 effectiveAttributes, addPathValue, ribSup, tx);
230 } else if (!this.oldNonAddPathBestPathTheSame) {
231 update(destPeer, getAdjRibOutYII(ribSup, rootPath, routeIdAddPathDefault, localTK),
232 effectiveAttributes, defaultValue, ribSup, tx);
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 static boolean isFirstBestPath(final int bestPathPosition) {
275 return bestPathPosition == 0;
278 private static boolean peersSupportsAddPathOrIsFirstBestPath(final boolean peerSupportsAddPath,
279 final boolean isFirstBestPath) {
280 return !(!peerSupportsAddPath && !isFirstBestPath);
283 protected boolean isBestPathNew(final List<AddPathBestPath> newBestPathList) {
284 this.oldNonAddPathBestPathTheSame = isNonAddPathBestPathTheSame(newBestPathList);
285 filterRemovedPaths(newBestPathList);
286 if (this.bestPathRemoved != null && !this.bestPathRemoved.isEmpty()
287 || newBestPathList != null
288 && !newBestPathList.equals(this.bestPath)) {
289 this.newBestPathToBeAdvertised = new ArrayList<>(newBestPathList);
290 if (this.bestPath != null) {
291 this.newBestPathToBeAdvertised.removeAll(this.bestPath);
293 this.bestPath = newBestPathList;
294 LOG.trace("Actual Best {}, removed best {}", this.bestPath, this.bestPathRemoved);
300 private boolean isNonAddPathBestPathTheSame(final List<AddPathBestPath> newBestPathList) {
301 return !(isEmptyOrNull(this.bestPath) || isEmptyOrNull(newBestPathList))
302 && this.bestPath.get(0).equals(newBestPathList.get(0));
305 private static boolean isEmptyOrNull(final List<AddPathBestPath> pathList) {
306 return pathList == null || pathList.isEmpty();
309 private void filterRemovedPaths(final List<AddPathBestPath> newBestPathList) {
310 if (this.bestPath == null) {
313 this.bestPathRemoved = new ArrayList<>(this.bestPath);
314 this.bestPath.forEach(oldBest -> {
315 final Optional<AddPathBestPath> present = newBestPathList.stream()
316 .filter(newBest -> newBest.getPathId() == oldBest.getPathId()
317 && newBest.getRouteKey() == oldBest.getRouteKey()).findAny();
318 present.ifPresent(addPathBestPath -> this.bestPathRemoved.remove(oldBest));