Use boxed Path IDs for AddPath
[bgpcep.git] / bgp / path-selection-mode / src / main / java / org / opendaylight / protocol / bgp / mode / impl / add / AddPathAbstractRouteEntry.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.add;
9
10 import static org.opendaylight.protocol.bgp.parser.spi.PathIdUtil.NON_PATH_ID;
11 import static org.opendaylight.protocol.bgp.parser.spi.PathIdUtil.NON_PATH_ID_VALUE;
12
13 import com.google.common.collect.ImmutableList;
14 import com.google.common.collect.Lists;
15 import com.google.common.primitives.UnsignedInteger;
16 import java.util.ArrayList;
17 import java.util.Collections;
18 import java.util.List;
19 import java.util.Optional;
20 import java.util.stream.Collectors;
21 import javax.annotation.concurrent.NotThreadSafe;
22 import org.opendaylight.protocol.bgp.mode.api.RouteEntry;
23 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
24 import org.opendaylight.protocol.bgp.rib.spi.entry.ActualBestPathRoutes;
25 import org.opendaylight.protocol.bgp.rib.spi.entry.AdvertizedRoute;
26 import org.opendaylight.protocol.bgp.rib.spi.entry.RouteEntryInfo;
27 import org.opendaylight.protocol.bgp.rib.spi.entry.StaleBestPathRoute;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.PathId;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.Route;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.tables.Routes;
32 import org.opendaylight.yangtools.yang.binding.ChildOf;
33 import org.opendaylight.yangtools.yang.binding.ChoiceIn;
34 import org.opendaylight.yangtools.yang.binding.DataObject;
35 import org.opendaylight.yangtools.yang.binding.Identifiable;
36 import org.opendaylight.yangtools.yang.binding.Identifier;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 /**
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.
46  */
47 @NotThreadSafe
48 public abstract class AddPathAbstractRouteEntry<C extends Routes & DataObject & ChoiceIn<Tables>,
49         S extends ChildOf<? super C>,
50         R extends Route & ChildOf<? super S> & Identifiable<I>, I extends Identifier<R>>
51         implements RouteEntry<C, S, R, I> {
52
53     private static final Logger LOG = LoggerFactory.getLogger(AddPathAbstractRouteEntry.class);
54     private static final Long[] EMPTY_PATHS_ID = new Long[0];
55     private static final Route[] EMPTY_VALUES = new Route[0];
56
57     protected OffsetMap offsets = OffsetMap.EMPTY;
58     protected R[] values = (R[]) EMPTY_VALUES;
59     protected Long[] pathsId = EMPTY_PATHS_ID;
60     private List<AddPathBestPath> bestPath;
61     private List<AddPathBestPath> bestPathRemoved;
62     private long pathIdCounter = 0L;
63     private boolean isNonAddPathBestPathNew;
64     private List<AddPathBestPath> newBestPathToBeAdvertised;
65     private List<Long> removedPathsId;
66
67     private R createRoute(final RIBSupport<C, S, R, I> ribSup, final String routeKey, final AddPathBestPath path) {
68         final OffsetMap map = getOffsets();
69         final R route = map.getValue(this.values, map.offsetOf(path.getRouteKey()));
70         return ribSup.createRoute(route, ribSup.createRouteListKey(pathIdObj(path.getPathIdLong()), routeKey),
71             path.getAttributes());
72     }
73
74     @Override
75     public final int addRoute(final UnsignedInteger routerId, final Long remotePathId, final R route) {
76         final RouteKey key = new RouteKey(routerId, remotePathId);
77         int offset = this.offsets.offsetOf(key);
78         if (offset < 0) {
79             final OffsetMap newOffsets = this.offsets.with(key);
80             offset = newOffsets.offsetOf(key);
81             final R[] newRoute = newOffsets.expand(this.offsets, this.values, offset);
82             final Long[] newPathsId = newOffsets.expand(this.offsets, this.pathsId, offset);
83             this.values = newRoute;
84             this.offsets = newOffsets;
85             this.pathsId = newPathsId;
86             this.offsets.setValue(this.pathsId, offset, ++this.pathIdCounter);
87         }
88         this.offsets.setValue(this.values, offset, route);
89         LOG.trace("Added route {} from {}", route, routerId);
90         return offset;
91     }
92
93     @Override
94     public final boolean removeRoute(final UnsignedInteger routerId, final Long remotePathId) {
95         final RouteKey key = new RouteKey(routerId, remotePathId);
96         final int offset = getOffsets().offsetOf(key);
97         final Long pathId = this.offsets.getValue(this.pathsId, offset);
98         this.values = this.offsets.removeValue(this.values, offset, (R[]) EMPTY_VALUES);
99         this.pathsId = this.offsets.removeValue(this.pathsId, offset, EMPTY_PATHS_ID);
100         this.offsets = this.offsets.without(key);
101         if (this.removedPathsId == null) {
102             this.removedPathsId = new ArrayList<>();
103         }
104         this.removedPathsId.add(pathId);
105         return isEmpty();
106     }
107
108     @Override
109     public final Optional<StaleBestPathRoute<C, S, R, I>> removeStalePaths(final RIBSupport<C, S, R, I> ribSupport,
110             final String routeKey) {
111         if ((this.bestPathRemoved == null || this.bestPathRemoved.isEmpty()) && this.removedPathsId == null) {
112             return Optional.empty();
113         }
114         List<Long> stalePaths = Collections.emptyList();
115         if (this.bestPathRemoved != null && !this.bestPathRemoved.isEmpty()) {
116             stalePaths = this.bestPathRemoved.stream().map(AddPathBestPath::getPathId).collect(Collectors.toList());
117             this.bestPathRemoved = null;
118         }
119         final StaleBestPathRoute<C, S, R, I> stale = new StaleBestPathRoute<>(ribSupport, routeKey, stalePaths,
120                 this.removedPathsId, this.isNonAddPathBestPathNew);
121         this.removedPathsId = null;
122         return Optional.of(stale);
123     }
124
125     @Override
126     public final List<AdvertizedRoute<C, S, R, I>> newBestPaths(final RIBSupport<C, S, R, I> ribSupport,
127             final String routeKey) {
128         if (this.newBestPathToBeAdvertised == null || this.newBestPathToBeAdvertised.isEmpty()) {
129             return Collections.emptyList();
130         }
131         final List<AdvertizedRoute<C, S, R, I>> advertized = new ArrayList<>(newBestPathToBeAdvertised.size());
132         final AddPathBestPath firstBestPath = this.bestPath.isEmpty() ? null : this.bestPath.get(0);
133         for (final AddPathBestPath path : this.newBestPathToBeAdvertised) {
134             final R routeAddPath = createRoute(ribSupport, routeKey, path);
135             // FIXME: can we use identity check here?
136             final boolean isFirstBestPath = firstBestPath != null && firstBestPath.equals(path);
137             final AdvertizedRoute<C, S, R, I> adv = new AdvertizedRoute<>(ribSupport, isFirstBestPath,
138                     routeAddPath, path.getAttributes(), path.getPeerId(), path.isDepreferenced());
139             advertized.add(adv);
140         }
141         this.newBestPathToBeAdvertised = null;
142         return advertized;
143     }
144
145     @Override
146     public final List<ActualBestPathRoutes<C, S, R, I>> actualBestPaths(final RIBSupport<C, S, R, I> ribSupport,
147             final RouteEntryInfo entryInfo) {
148         if (this.bestPath == null || this.bestPath.isEmpty()) {
149             return Collections.emptyList();
150         }
151         final List<ActualBestPathRoutes<C, S, R, I>> preexistentRoutes = new ArrayList<>();
152         for (final AddPathBestPath path : this.bestPath) {
153             final R route = createRoute(ribSupport, entryInfo.getRouteKey(), path);
154             final ActualBestPathRoutes<C, S, R, I> adv = new ActualBestPathRoutes<>(ribSupport, route, path.getPeerId(),
155                     path.getAttributes(), path.isDepreferenced());
156             preexistentRoutes.add(adv);
157         }
158         return preexistentRoutes;
159     }
160
161     private OffsetMap getOffsets() {
162         return this.offsets;
163     }
164
165     public final boolean isEmpty() {
166         return this.offsets.isEmpty();
167     }
168
169     private void selectBest(final RouteKey key, final AddPathSelector selector) {
170         final int offset = this.offsets.offsetOf(key);
171         final R route = this.offsets.getValue(this.values, offset);
172         final Long pathId = this.offsets.getValue(this.pathsId, offset);
173         LOG.trace("Processing router key {} route {}", key, route);
174         selector.processPath(route.getAttributes(), key, offset, pathId);
175     }
176
177     /**
178      * Process best path selection.
179      *
180      * @param localAs The local autonomous system number
181      * @param keyList List of RouteKey
182      * @return the best path inside offset map passed
183      */
184     protected AddPathBestPath selectBest(final long localAs, final List<RouteKey> keyList) {
185         /*
186          * FIXME: optimize flaps by making sure we consider stability of currently-selected route.
187          */
188         final AddPathSelector selector = new AddPathSelector(localAs);
189         Lists.reverse(keyList).forEach(key -> selectBest(key, selector));
190         LOG.trace("Best path selected {}", this.bestPath);
191         return selector.result();
192     }
193
194     protected boolean isBestPathNew(final ImmutableList<AddPathBestPath> newBestPathList) {
195         this.isNonAddPathBestPathNew = !isNonAddPathBestPathTheSame(newBestPathList);
196         filterRemovedPaths(newBestPathList);
197         if (this.bestPathRemoved != null && !this.bestPathRemoved.isEmpty()
198                 || newBestPathList != null
199                 && !newBestPathList.equals(this.bestPath)) {
200             if (this.bestPath != null) {
201                 this.newBestPathToBeAdvertised = new ArrayList<>(newBestPathList);
202                 this.newBestPathToBeAdvertised.removeAll(this.bestPath);
203             } else {
204                 this.newBestPathToBeAdvertised = newBestPathList;
205             }
206             this.bestPath = newBestPathList;
207             LOG.trace("Actual Best {}, removed best {}", this.bestPath, this.bestPathRemoved);
208             return true;
209         }
210         return false;
211     }
212
213     private boolean isNonAddPathBestPathTheSame(final List<AddPathBestPath> newBestPathList) {
214         return !(isEmptyOrNull(this.bestPath) || isEmptyOrNull(newBestPathList))
215                 && this.bestPath.get(0).equals(newBestPathList.get(0));
216     }
217
218     private static boolean isEmptyOrNull(final List<AddPathBestPath> pathList) {
219         return pathList == null || pathList.isEmpty();
220     }
221
222     private void filterRemovedPaths(final List<AddPathBestPath> newBestPathList) {
223         if (this.bestPath == null) {
224             return;
225         }
226         this.bestPathRemoved = new ArrayList<>(this.bestPath);
227         this.bestPath.forEach(oldBest -> {
228             final Optional<AddPathBestPath> present = newBestPathList.stream()
229                     .filter(newBest -> newBest.getPathId() == oldBest.getPathId()
230                             && newBest.getRouteKey() == oldBest.getRouteKey()).findAny();
231             present.ifPresent(addPathBestPath -> this.bestPathRemoved.remove(oldBest));
232         });
233     }
234
235     private static PathId pathIdObj(final Long pathId) {
236         return pathId == NON_PATH_ID_VALUE ? NON_PATH_ID : new PathId(pathId);
237     }
238 }