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_VALUE;
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.binding.api.WriteTransaction;
19 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
20 import org.opendaylight.protocol.bgp.mode.impl.BGPRouteEntryExportParametersImpl;
21 import org.opendaylight.protocol.bgp.mode.spi.AbstractRouteEntry;
22 import org.opendaylight.protocol.bgp.rib.spi.BGPPeerTracker;
23 import org.opendaylight.protocol.bgp.rib.spi.Peer;
24 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
25 import org.opendaylight.protocol.bgp.rib.spi.RouterIds;
26 import org.opendaylight.protocol.bgp.rib.spi.entry.RouteEntryDependenciesContainer;
27 import org.opendaylight.protocol.bgp.rib.spi.entry.RouteEntryInfo;
28 import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRouteEntryExportParameters;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev171207.path.attributes.Attributes;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.PeerId;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.Route;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.rib.Tables;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.rib.TablesKey;
34 import org.opendaylight.yangtools.yang.binding.Identifier;
35 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
36 import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
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 Attributes[] values = new Attributes[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 public AddPathAbstractRouteEntry(final BGPPeerTracker peerTracker) {
65 private static final class RemovedPath {
66 private final RouteKey key;
67 private final long pathId;
69 RemovedPath(final RouteKey key, final long pathId) {
78 UnsignedInteger getRouteId() {
79 return this.key.getRouteId();
83 protected int addRoute(final RouteKey key, final Attributes attributes) {
84 int offset = this.offsets.offsetOf(key);
86 final OffsetMap newOffsets = this.offsets.with(key);
87 offset = newOffsets.offsetOf(key);
88 final Attributes[] newAttributes = newOffsets.expand(this.offsets, this.values, offset);
89 final Long[] newPathsId = newOffsets.expand(this.offsets, this.pathsId, offset);
90 this.values = newAttributes;
91 this.offsets = newOffsets;
92 this.pathsId = newPathsId;
93 this.offsets.setValue(this.pathsId, offset, ++this.pathIdCounter);
95 this.offsets.setValue(this.values, offset, attributes);
96 LOG.trace("Added route from {} attributes {}", key.getRouteId(), attributes);
103 * @param key RouteKey of removed route
104 * @param offset Offset of removed route
105 * @return true if it was the last route
107 protected final boolean removeRoute(final RouteKey key, final int offset) {
108 final long pathId = this.offsets.getValue(this.pathsId, offset);
109 this.values = this.offsets.removeValue(this.values, offset);
110 this.pathsId = this.offsets.removeValue(this.pathsId, offset);
111 this.offsets = this.offsets.without(key);
112 if (this.removedPaths == null) {
113 this.removedPaths = new ArrayList<>();
115 this.removedPaths.add(new RemovedPath(key, pathId));
120 public void updateBestPaths(
121 final RouteEntryDependenciesContainer entryDependencies,
122 final Identifier routeKey,
123 final WriteTransaction tx) {
125 final RIBSupport ribSupport = entryDependencies.getRibSupport();
126 final KeyedInstanceIdentifier<Tables, TablesKey> locRibTarget = entryDependencies.getLocRibTableTarget();
127 if (this.bestPathRemoved != null) {
128 this.bestPathRemoved.forEach(path -> {
129 final Identifier newRouteKey = ribSupport.createNewRouteKey(path.getPathId(), routeKey);
130 final InstanceIdentifier routeTarget = ribSupport.createRouteIdentifier(locRibTarget, newRouteKey);
131 LOG.debug("Delete route from LocRib {}", routeTarget);
132 tx.delete(LogicalDatastoreType.OPERATIONAL, routeTarget);
134 this.bestPathRemoved = null;
136 if (this.removedPaths != null) {
137 this.removedPaths.forEach(removedPath -> {
138 final Identifier routeKeyAddPath
139 = ribSupport.createNewRouteKey(removedPath.getPathId(), routeKey);
140 final Identifier routeKeyNonAddPath = ribSupport.createNewRouteKey(NON_PATH_ID_VALUE, routeKey);
141 fillAdjRibsOut(true, null, null, null,
142 routeKeyNonAddPath, routeKeyAddPath,
143 RouterIds.createPeerId(removedPath.getRouteId()),
144 entryDependencies.getLocalTablesKey(), entryDependencies, tx);
146 this.removedPaths = null;
149 if (this.newBestPathToBeAdvertised != null) {
150 this.newBestPathToBeAdvertised.forEach(path -> addPathToDataStore(entryDependencies, path,
151 isFirstBestPath(this.bestPath.indexOf(path)), routeKey, tx));
152 this.newBestPathToBeAdvertised = null;
157 public void initializeBestPaths(final RouteEntryDependenciesContainer entryDependencies,
158 final RouteEntryInfo entryInfo, final WriteTransaction tx) {
159 if (this.bestPath != null) {
160 final Peer toPeer = entryInfo.getToPeer();
161 final TablesKey localTk = entryDependencies.getLocalTablesKey();
162 final boolean destPeerSupAddPath = toPeer.supportsAddPathSupported(localTk);
163 for (final AddPathBestPath path : this.bestPath) {
164 if (!filterRoutes(path.getPeerId(), toPeer, localTk)) {
167 writeRoutePath(entryInfo, destPeerSupAddPath, path, localTk, entryDependencies, tx);
172 @SuppressWarnings("unchecked")
173 private void writeRoutePath(final RouteEntryInfo entryInfo,
174 final boolean destPeerSupAddPath, final AddPathBestPath path,
175 final TablesKey localTK, final RouteEntryDependenciesContainer routeEntryDep, final WriteTransaction tx) {
176 final Identifier routeKey = entryInfo.getRouteKey();
177 final RIBSupport ribSupport = routeEntryDep.getRibSupport();
178 final BGPRouteEntryExportParameters baseExp = new BGPRouteEntryExportParametersImpl(
179 this.peerTracker.getPeer(path.getPeerId()), entryInfo.getToPeer());
180 final Optional<Attributes> effAttrib = routeEntryDep.getRoutingPolicies()
181 .applyExportPolicies(baseExp, path.getAttributes());
183 Identifier newRouteKey = ribSupport.createNewRouteKey(destPeerSupAddPath
184 ? path.getPathId() : NON_PATH_ID_VALUE, routeKey);
185 final Peer toPeer = entryInfo.getToPeer();
186 final Route route = createRoute(ribSupport, newRouteKey, destPeerSupAddPath
187 ? path.getPathId() : NON_PATH_ID_VALUE, path);
188 InstanceIdentifier ribOutIId = ribSupport.createRouteIdentifier(toPeer.getRibOutIId(localTK), newRouteKey);
189 if (effAttrib.isPresent() && route != null) {
190 LOG.debug("Write route {} to peer AdjRibsOut {}", route, toPeer.getPeerId());
191 tx.put(LogicalDatastoreType.OPERATIONAL, ribOutIId, route);
192 tx.put(LogicalDatastoreType.OPERATIONAL, ribOutIId.child(Attributes.class), effAttrib.get());
196 private void addPathToDataStore(
197 final RouteEntryDependenciesContainer entryDep,
198 final AddPathBestPath path,
199 final boolean isFirstBestPath,
200 final Identifier routeKey,
201 final WriteTransaction tx) {
202 final RIBSupport ribSup = entryDep.getRibSupport();
203 final Identifier routeKeyAddPath = ribSup.createNewRouteKey(path.getPathId(), routeKey);
204 final Identifier routeKeyAddNonPath = ribSup.createNewRouteKey(NON_PATH_ID_VALUE, routeKey);
205 final Route routeAddPath = createRoute(ribSup, routeKeyAddPath, path.getPathId(), path);
206 final Route routeNonAddPath = createRoute(ribSup, routeKeyAddNonPath, NON_PATH_ID_VALUE, path);
208 final KeyedInstanceIdentifier<Tables, TablesKey> locRibTarget = entryDep.getLocRibTableTarget();
209 final InstanceIdentifier routeTarget = ribSup.createRouteIdentifier(locRibTarget, routeKeyAddPath);
210 LOG.debug("Write route to LocRib {}", routeAddPath);
211 tx.put(LogicalDatastoreType.OPERATIONAL, routeTarget, routeAddPath);
213 fillAdjRibsOut(isFirstBestPath, path.getAttributes(), routeNonAddPath, routeAddPath, routeKeyAddNonPath,
214 routeKeyAddPath, path.getPeerId(), entryDep.getLocalTablesKey(), entryDep, tx);
217 @SuppressWarnings("unchecked")
218 private void fillAdjRibsOut(
219 final boolean isFirstBestPath,
220 final Attributes attributes,
221 final Route routeNonAddPath,
222 final Route routeAddPath,
223 final Identifier routeKeyAddNonPath,
224 final Identifier routeKeyAddPath,
225 final PeerId fromPeerId,
226 final TablesKey localTK,
227 final RouteEntryDependenciesContainer routeEntryDep,
228 final WriteTransaction tx) {
230 * We need to keep track of routers and populate adj-ribs-out, too. If we do not, we need to
231 * expose from which client a particular route was learned from in the local RIB, and have
232 * the listener perform filtering.
234 * We walk the policy set in order to minimize the amount of work we do for multiple peers:
235 * if we have two eBGP peers, for example, there is no reason why we should perform the translation
238 final RIBSupport ribSupport = routeEntryDep.getRibSupport();
239 for (final Peer toPeer : this.peerTracker.getPeers()) {
240 if (!filterRoutes(fromPeerId, toPeer, localTK)) {
243 final boolean destPeerSupAddPath = toPeer.supportsAddPathSupported(localTK);
245 if (toPeer.getPeerId().getValue().equals("bgp://127.0.0.5")) {
246 LOG.debug("Write route {} to peer AdjRibsOut {}", toPeer.getPeerId());
248 if (peersSupportsAddPathOrIsFirstBestPath(destPeerSupAddPath, isFirstBestPath)) {
250 Optional<Attributes> effAttrib = Optional.empty();
251 final Peer fromPeer = this.peerTracker.getPeer(fromPeerId);
253 if (fromPeer != null && attributes != null) {
254 final BGPRouteEntryExportParameters baseExp
255 = new BGPRouteEntryExportParametersImpl(fromPeer, toPeer);
256 effAttrib = routeEntryDep.getRoutingPolicies()
257 .applyExportPolicies(baseExp, attributes);
259 Route newRoute = null;
260 InstanceIdentifier ribOutRoute = null;
261 if (destPeerSupAddPath) {
262 newRoute = routeAddPath;
264 = ribSupport.createRouteIdentifier(toPeer.getRibOutIId(localTK), routeKeyAddPath);
265 } else if (!this.oldNonAddPathBestPathTheSame) {
267 = ribSupport.createRouteIdentifier(toPeer.getRibOutIId(localTK), routeKeyAddNonPath);
268 newRoute = routeNonAddPath;
271 if (effAttrib.isPresent() && newRoute != null) {
272 LOG.debug("Write route {} to peer AdjRibsOut {}", newRoute, toPeer.getPeerId());
273 tx.put(LogicalDatastoreType.OPERATIONAL, ribOutRoute, newRoute);
274 tx.put(LogicalDatastoreType.OPERATIONAL, ribOutRoute.child(Attributes.class), effAttrib.get());
275 } else if (ribOutRoute != null) {
276 LOG.trace("Removing {} from transaction for peer {}", ribOutRoute, toPeer.getPeerId());
277 tx.delete(LogicalDatastoreType.OPERATIONAL, ribOutRoute);
284 protected final OffsetMap getOffsets() {
288 public final boolean isEmpty() {
289 return this.offsets.isEmpty();
292 private void selectBest(final RouteKey key, final AddPathSelector selector) {
293 final int offset = this.offsets.offsetOf(key);
294 final Attributes attributes = this.offsets.getValue(this.values, offset);
295 final long pathId = this.offsets.getValue(this.pathsId, offset);
296 LOG.trace("Processing router key {} attributes {}", key, attributes);
297 selector.processPath(attributes, key, offset, pathId);
301 * Process best path selection.
303 * @param localAs The local autonomous system number
304 * @param keyList List of RouteKey
305 * @return the best path inside offset map passed
307 protected AddPathBestPath selectBest(final long localAs, final List<RouteKey> keyList) {
309 * FIXME: optimize flaps by making sure we consider stability of currently-selected route.
311 final AddPathSelector selector = new AddPathSelector(localAs);
312 Lists.reverse(keyList).forEach(key -> selectBest(key, selector));
313 LOG.trace("Best path selected {}", this.bestPath);
314 return selector.result();
317 private static boolean isFirstBestPath(final int bestPathPosition) {
318 return bestPathPosition == 0;
321 private static boolean peersSupportsAddPathOrIsFirstBestPath(final boolean peerSupportsAddPath,
322 final boolean isFirstBestPath) {
323 return !(!peerSupportsAddPath && !isFirstBestPath);
326 protected boolean isBestPathNew(final List<AddPathBestPath> newBestPathList) {
327 this.oldNonAddPathBestPathTheSame = isNonAddPathBestPathTheSame(newBestPathList);
328 filterRemovedPaths(newBestPathList);
329 if (this.bestPathRemoved != null && !this.bestPathRemoved.isEmpty()
330 || newBestPathList != null
331 && !newBestPathList.equals(this.bestPath)) {
332 this.newBestPathToBeAdvertised = new ArrayList<>(newBestPathList);
333 if (this.bestPath != null) {
334 this.newBestPathToBeAdvertised.removeAll(this.bestPath);
336 this.bestPath = newBestPathList;
337 LOG.trace("Actual Best {}, removed best {}", this.bestPath, this.bestPathRemoved);
343 private boolean isNonAddPathBestPathTheSame(final List<AddPathBestPath> newBestPathList) {
344 return !(isEmptyOrNull(this.bestPath) || isEmptyOrNull(newBestPathList))
345 && this.bestPath.get(0).equals(newBestPathList.get(0));
348 private static boolean isEmptyOrNull(final List<AddPathBestPath> pathList) {
349 return pathList == null || pathList.isEmpty();
352 private void filterRemovedPaths(final List<AddPathBestPath> newBestPathList) {
353 if (this.bestPath == null) {
356 this.bestPathRemoved = new ArrayList<>(this.bestPath);
357 this.bestPath.forEach(oldBest -> {
358 final Optional<AddPathBestPath> present = newBestPathList.stream()
359 .filter(newBest -> newBest.getPathId() == oldBest.getPathId()
360 && newBest.getRouteKey() == oldBest.getRouteKey()).findAny();
361 present.ifPresent(addPathBestPath -> this.bestPathRemoved.remove(oldBest));