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.api.BestPath;
20 import org.opendaylight.protocol.bgp.mode.spi.AbstractRouteEntry;
21 import org.opendaylight.protocol.bgp.rib.spi.ExportPolicyPeerTracker;
22 import org.opendaylight.protocol.bgp.rib.spi.PeerExportGroup;
23 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
24 import org.opendaylight.protocol.bgp.rib.spi.RouterIds;
25 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.PeerId;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.PeerRole;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.rib.TablesKey;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
31 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
39 * A single route entry inside a route table. Maintains the attributes of
40 * from all contributing peers. The information is stored in arrays with a
41 * shared map of offsets for peers to allow lookups. This is needed to
42 * maintain low memory overhead in face of large number of routes and peers,
43 * where individual object overhead becomes the dominating factor.
46 public abstract class AddPathAbstractRouteEntry extends AbstractRouteEntry {
48 private static final Logger LOG = LoggerFactory.getLogger(AddPathAbstractRouteEntry.class);
49 private List<AddPathBestPath> bestPath;
50 private List<AddPathBestPath> bestPathRemoved;
51 protected OffsetMap offsets = OffsetMap.EMPTY;
52 protected ContainerNode[] values = new ContainerNode[0];
53 protected Long[] pathsId = new Long[0];
54 private long pathIdCounter = 0L;
55 private boolean oldNonAddPathBestPathTheSame;
56 private List<AddPathBestPath> newBestPathToBeAdvertised;
57 private List<RemovedPath> removedPaths;
59 private static final class RemovedPath {
60 private final RouteKey key;
61 private final Long pathId;
63 RemovedPath(final RouteKey key, final Long pathId) {
72 UnsignedInteger getRouteId() {
73 return this.key.getRouteId();
77 private int addRoute(final RouteKey key, final ContainerNode attributes) {
78 int offset = this.offsets.offsetOf(key);
80 final OffsetMap newOffsets = this.offsets.with(key);
81 offset = newOffsets.offsetOf(key);
82 final ContainerNode[] newAttributes = newOffsets.expand(this.offsets, this.values, offset);
83 final Long[] newPathsId = newOffsets.expand(this.offsets, this.pathsId, offset);
84 this.values = newAttributes;
85 this.offsets = newOffsets;
86 this.pathsId = newPathsId;
87 this.offsets.setValue(this.pathsId, offset, ++this.pathIdCounter);
89 this.offsets.setValue(this.values, offset, attributes);
90 LOG.trace("Added route from {} attributes {}", key.getRouteId(), attributes);
94 protected int addRoute(final RouteKey key, final NodeIdentifier attributesIdentifier,
95 final NormalizedNode<?, ?> data) {
96 LOG.trace("Find {} in {}", attributesIdentifier, data);
97 final ContainerNode advertisedAttrs
98 = (ContainerNode) NormalizedNodes.findNode(data, attributesIdentifier).orElse(null);
99 return addRoute(key, advertisedAttrs);
105 * @param key RouteKey of removed route
106 * @param offset Offset of removed route
107 * @return true if it was the last route
109 protected final boolean removeRoute(final RouteKey key, final int offset) {
110 final Long pathId = this.offsets.getValue(this.pathsId, offset);
111 this.values = this.offsets.removeValue(this.values, offset);
112 this.pathsId = this.offsets.removeValue(this.pathsId, offset);
113 this.offsets = this.offsets.without(key);
114 if (this.removedPaths == null) {
115 this.removedPaths = new ArrayList<>();
117 this.removedPaths.add(new RemovedPath(key, pathId));
122 public void updateRoute(final TablesKey localTK, final ExportPolicyPeerTracker peerPT,
123 final YangInstanceIdentifier locRibTarget, final RIBSupport ribSupport, final DOMDataWriteTransaction tx,
124 final PathArgument routeIdPA) {
125 if (this.bestPathRemoved != null) {
126 this.bestPathRemoved.forEach(path -> {
127 final PathArgument routeIdAddPath = ribSupport.getRouteIdAddPath(path.getPathId(), routeIdPA);
128 final YangInstanceIdentifier pathAddPathTarget = ribSupport
129 .routePath(locRibTarget.node(ROUTES_IDENTIFIER), routeIdAddPath);
130 fillLocRib(pathAddPathTarget, null, tx);
132 this.bestPathRemoved = null;
134 if (this.removedPaths != null) {
135 this.removedPaths.forEach(removedPath -> {
136 final PathArgument routeIdAddPath = ribSupport.getRouteIdAddPath(removedPath.getPathId(), routeIdPA);
137 final PathArgument routeIdAddPathDefault = ribSupport.getRouteIdAddPath(NON_PATH_ID, routeIdPA);
138 fillAdjRibsOut(true, null, null, null,
139 routeIdAddPathDefault, routeIdAddPath,
140 RouterIds.createPeerId(removedPath.getRouteId()), peerPT, localTK, ribSupport, tx);
142 this.removedPaths = null;
145 if (this.newBestPathToBeAdvertised != null) {
146 this.newBestPathToBeAdvertised.forEach(path -> addPathToDataStore(path,
147 isFirstBestPath(this.bestPath.indexOf(path)), routeIdPA, locRibTarget, ribSupport, peerPT,
149 this.newBestPathToBeAdvertised = null;
154 public void writeRoute(final PeerId destPeer, final PathArgument routeId, final YangInstanceIdentifier rootPath,
155 final PeerExportGroup peerGroup, final TablesKey localTK, final ExportPolicyPeerTracker peerPT,
156 final RIBSupport ribSup, final DOMDataWriteTransaction tx) {
157 final boolean destPeerSupAddPath = peerPT.isAddPathSupportedByPeer(destPeer);
158 if (this.bestPath != null) {
159 final PeerRole destPeerRole = getRoutePeerIdRole(peerPT, destPeer);
160 this.bestPath.stream().filter(path -> filterRoutes(path.getPeerId(), destPeer, peerPT, localTK,
161 destPeerRole) && peersSupportsAddPathOrIsFirstBestPath(destPeerSupAddPath,
162 isFirstBestPath(this.bestPath.indexOf(path)))).forEach(path -> writeRoutePath(destPeer, routeId,
163 peerPT, peerGroup, destPeerSupAddPath, path, rootPath, localTK, ribSup, tx));
167 private void writeRoutePath(final PeerId destPeer, final PathArgument routeId, final ExportPolicyPeerTracker peerPT,
168 final PeerExportGroup peerGroup, final boolean destPeerSupAddPath, final BestPath path,
169 final YangInstanceIdentifier rootPath, final TablesKey localTK, final RIBSupport ribSup,
170 final DOMDataWriteTransaction tx) {
171 final ContainerNode effectiveAttributes = peerGroup
172 .effectiveAttributes(getRoutePeerIdRole(peerPT,path.getPeerId()), path.getAttributes());
173 PathArgument routeIdAddPath;
174 if (destPeerSupAddPath) {
175 routeIdAddPath = ribSup.getRouteIdAddPath(path.getPathId(), routeId);
177 routeIdAddPath = ribSup.getRouteIdAddPath(NON_PATH_ID, routeId);
179 writeRoute(destPeer, getAdjRibOutYII(ribSup, rootPath, routeIdAddPath, localTK), effectiveAttributes,
180 createValue(routeIdAddPath, path), ribSup, tx);
183 private void addPathToDataStore(final BestPath path, final boolean isFirstBestPath, final PathArgument routeIdPA,
184 final YangInstanceIdentifier locRibTarget, final RIBSupport ribSup, final ExportPolicyPeerTracker peerPT,
185 final TablesKey localTK, final DOMDataWriteTransaction tx) {
186 final PathArgument routeIdAddPath = ribSup.getRouteIdAddPath(path.getPathId(), routeIdPA);
187 final PathArgument routeIdAddPathDefault = ribSup.getRouteIdAddPath(NON_PATH_ID, routeIdPA);
188 final YangInstanceIdentifier pathAddPathTarget = ribSup.routePath(locRibTarget.node(ROUTES_IDENTIFIER),
190 final MapEntryNode addPathValue = createValue(routeIdAddPath, path);
191 final MapEntryNode defaultValue = createValue(routeIdAddPathDefault, path);
192 LOG.trace("Selected best value {}", addPathValue);
193 fillLocRib(pathAddPathTarget, addPathValue, tx);
194 fillAdjRibsOut(isFirstBestPath, path.getAttributes(), defaultValue, addPathValue, routeIdAddPathDefault,
195 routeIdAddPath, path.getPeerId(), peerPT, localTK, ribSup, tx);
198 private void fillAdjRibsOut(final boolean isFirstBestPath, final ContainerNode attributes,
199 final MapEntryNode defaultValue, final MapEntryNode addPathValue,
200 final PathArgument routeIdAddPathDefault,
201 final PathArgument routeIdAddPath, final PeerId routePeerId, final ExportPolicyPeerTracker peerPT,
202 final TablesKey localTK, final RIBSupport ribSup, 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,
216 routePeerId), attributes);
217 peerGroup.forEach((destPeer, rootPath) -> {
218 final boolean destPeerSupAddPath = peerPT.isAddPathSupportedByPeer(destPeer);
219 if (filterRoutes(routePeerId, destPeer, peerPT, localTK, role)
220 && peersSupportsAddPathOrIsFirstBestPath(destPeerSupAddPath, isFirstBestPath)) {
221 if (destPeerSupAddPath) {
222 update(destPeer, getAdjRibOutYII(ribSup, rootPath, routeIdAddPath, localTK),
223 effectiveAttributes, addPathValue, ribSup, tx);
224 } else if (!this.oldNonAddPathBestPathTheSame) {
225 update(destPeer, getAdjRibOutYII(ribSup, rootPath, routeIdAddPathDefault, localTK),
226 effectiveAttributes, defaultValue, ribSup, tx);
235 protected final OffsetMap getOffsets() {
239 public final boolean isEmpty() {
240 return this.offsets.isEmpty();
243 private void selectBest(final RouteKey key, final AddPathSelector selector) {
244 final int offset = this.offsets.offsetOf(key);
245 final ContainerNode attributes = this.offsets.getValue(this.values, offset);
246 final Long pathId = this.offsets.getValue(this.pathsId, offset);
247 LOG.trace("Processing router key {} attributes {}", key, attributes);
248 selector.processPath(attributes, key, offset, pathId);
252 * Process best path selection.
254 * @param localAs The local autonomous system number
255 * @param keyList List of RouteKey
256 * @return the best path inside offset map passed
258 protected AddPathBestPath selectBest(final long localAs, final List<RouteKey> keyList) {
260 * FIXME: optimize flaps by making sure we consider stability of currently-selected route.
262 final AddPathSelector selector = new AddPathSelector(localAs);
263 Lists.reverse(keyList).forEach(key -> selectBest(key, selector));
264 LOG.trace("Best path selected {}", this.bestPath);
265 return selector.result();
268 private static boolean isFirstBestPath(final int bestPathPosition) {
269 return bestPathPosition == 0;
272 private static boolean peersSupportsAddPathOrIsFirstBestPath(final boolean peerSupportsAddPath,
273 final boolean isFirstBestPath) {
274 return !(!peerSupportsAddPath && !isFirstBestPath);
277 protected boolean isBestPathNew(final List<AddPathBestPath> newBestPathList) {
278 this.oldNonAddPathBestPathTheSame = isNonAddPathBestPathTheSame(newBestPathList);
279 filterRemovedPaths(newBestPathList);
280 if (this.bestPathRemoved != null && !this.bestPathRemoved.isEmpty() || newBestPathList != null
281 && !newBestPathList.equals(this.bestPath)) {
282 this.newBestPathToBeAdvertised = new ArrayList<>(newBestPathList);
283 if (this.bestPath != null) {
284 this.newBestPathToBeAdvertised.removeAll(this.bestPath);
286 this.bestPath = newBestPathList;
287 LOG.trace("Actual Best {}, removed best {}", this.bestPath, this.bestPathRemoved);
293 private boolean isNonAddPathBestPathTheSame(final List<AddPathBestPath> newBestPathList) {
294 return !(isEmptyOrNull(this.bestPath) || isEmptyOrNull(newBestPathList))
295 && this.bestPath.get(0).equals(newBestPathList.get(0));
298 private static boolean isEmptyOrNull(final List<AddPathBestPath> pathList) {
299 return pathList == null || pathList.isEmpty();
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()
310 && newBest.getRouteKey() == oldBest.getRouteKey()).findAny();
311 present.ifPresent(addPathBestPath -> this.bestPathRemoved.remove(oldBest));