*/
package org.opendaylight.protocol.bgp.mode.impl.add;
+import static com.google.common.base.Verify.verifyNotNull;
import static org.opendaylight.protocol.bgp.parser.spi.PathIdUtil.NON_PATH_ID;
import static org.opendaylight.protocol.bgp.parser.spi.PathIdUtil.NON_PATH_ID_VALUE;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
import com.google.common.primitives.UnsignedInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.stream.Collectors;
import javax.annotation.concurrent.NotThreadSafe;
import org.opendaylight.protocol.bgp.mode.api.RouteEntry;
+import org.opendaylight.protocol.bgp.mode.impl.BestPathStateImpl;
import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
import org.opendaylight.protocol.bgp.rib.spi.entry.ActualBestPathRoutes;
import org.opendaylight.protocol.bgp.rib.spi.entry.AdvertizedRoute;
private static final Long[] EMPTY_PATHS_ID = new Long[0];
private static final Route[] EMPTY_VALUES = new Route[0];
- protected OffsetMap offsets = OffsetMap.EMPTY;
- protected R[] values = (R[]) EMPTY_VALUES;
- protected Long[] pathsId = EMPTY_PATHS_ID;
+ private OffsetMap offsets = OffsetMap.EMPTY;
+ private R[] values = (R[]) EMPTY_VALUES;
+ private Long[] pathsId = EMPTY_PATHS_ID;
private List<AddPathBestPath> bestPath;
private List<AddPathBestPath> bestPathRemoved;
- private long pathIdCounter = 0L;
- private boolean isNonAddPathBestPathNew;
private List<AddPathBestPath> newBestPathToBeAdvertised;
private List<Long> removedPathsId;
+ private long pathIdCounter = 0L;
+ private boolean isNonAddPathBestPathNew;
+
private R createRoute(final RIBSupport<C, S, R, I> ribSup, final String routeKey, final AddPathBestPath path) {
- final OffsetMap map = getOffsets();
+ final OffsetMap map = this.offsets;
final R route = map.getValue(this.values, map.offsetOf(path.getRouteKey()));
return ribSup.createRoute(route, ribSup.createRouteListKey(pathIdObj(path.getPathIdLong()), routeKey),
path.getAttributes());
@Override
public final boolean removeRoute(final UnsignedInteger routerId, final Long remotePathId) {
final RouteKey key = new RouteKey(routerId, remotePathId);
- final int offset = getOffsets().offsetOf(key);
+ final int offset = this.offsets.offsetOf(key);
final Long pathId = this.offsets.getValue(this.pathsId, offset);
this.values = this.offsets.removeValue(this.values, offset, (R[]) EMPTY_VALUES);
this.pathsId = this.offsets.removeValue(this.pathsId, offset, EMPTY_PATHS_ID);
this.removedPathsId = new ArrayList<>();
}
this.removedPathsId.add(pathId);
- return isEmpty();
+ return this.offsets.isEmpty();
}
@Override
return preexistentRoutes;
}
- private OffsetMap getOffsets() {
- return this.offsets;
+ @Override
+ public final boolean selectBest(final long localAs) {
+ final int size;
+ return isBestPathNew((size = offsets.size()) == 0 ? ImmutableList.of() : selectBest(localAs, size));
}
- public final boolean isEmpty() {
- return this.offsets.isEmpty();
- }
+ protected abstract ImmutableList<AddPathBestPath> selectBest(long localAs, int size);
- private void selectBest(final RouteKey key, final AddPathSelector selector) {
- final int offset = this.offsets.offsetOf(key);
- final R route = this.offsets.getValue(this.values, offset);
- final Long pathId = this.offsets.getValue(this.pathsId, offset);
+ /**
+ * Process a specific route offset into specified selector.
+ *
+ * @param selector selector to update
+ * @param offset offset to process
+ */
+ protected final void processOffset(final AddPathSelector selector, final int offset) {
+ final RouteKey key = offsets.getKey(offset);
+ final R route = offsets.getValue(values, offset);
+ final Long pathId = offsets.getValue(pathsId, offset);
LOG.trace("Processing router key {} route {}", key, route);
selector.processPath(route.getAttributes(), key, offset, pathId);
}
- /**
- * Process best path selection.
- *
- * @param localAs The local autonomous system number
- * @param keyList List of RouteKey
- * @return the best path inside offset map passed
- */
- protected AddPathBestPath selectBest(final long localAs, final List<RouteKey> keyList) {
- /*
- * FIXME: optimize flaps by making sure we consider stability of currently-selected route.
- */
- final AddPathSelector selector = new AddPathSelector(localAs);
- Lists.reverse(keyList).forEach(key -> selectBest(key, selector));
- LOG.trace("Best path selected {}", this.bestPath);
- return selector.result();
+ protected final AddPathBestPath bestPathAt(final int offset) {
+ final Route route = verifyNotNull(offsets.getValue(values, offset));
+ return new AddPathBestPath(new BestPathStateImpl(route.getAttributes()), offsets.getKey(offset),
+ offsets.getValue(pathsId, offset), offset);
}
- protected boolean isBestPathNew(final ImmutableList<AddPathBestPath> newBestPathList) {
+ private boolean isBestPathNew(final ImmutableList<AddPathBestPath> newBestPathList) {
this.isNonAddPathBestPathNew = !isNonAddPathBestPathTheSame(newBestPathList);
filterRemovedPaths(newBestPathList);
if (this.bestPathRemoved != null && !this.bestPathRemoved.isEmpty()
private final Long pathId;
private final int offset;
- public AddPathBestPath(@Nonnull final BestPathState state, @Nonnull final RouteKey key, final @Nonnull Long pathId,
+ AddPathBestPath(@Nonnull final BestPathState state, @Nonnull final RouteKey key, final @Nonnull Long pathId,
final int offset) {
super(state);
this.routeKey = requireNonNull(key);
this.offset = offset;
}
- public RouteKey getRouteKey() {
+ RouteKey getRouteKey() {
return this.routeKey;
}
*
* @return Path Id as a {@link Long}
*/
- public @Nonnull Long getPathIdLong() {
+ @Nonnull Long getPathIdLong() {
return this.pathId;
}
}
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
-import java.util.List;
import java.util.Set;
-import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
* We also provide utility reformat methods, which provide access to
* array members and array management features.
*/
-public final class OffsetMap {
+final class OffsetMap {
private static final Logger LOG = LoggerFactory.getLogger(OffsetMap.class);
private static final String NEGATIVEOFFSET = "Invalid negative offset %s";
private static final String INVALIDOFFSET = "Invalid offset %s for %s router IDs";
this.routeKeys = array;
}
- public int offsetOf(final RouteKey key) {
+ int offsetOf(final RouteKey key) {
return Arrays.binarySearch(this.routeKeys, key, COMPARATOR);
}
- public int size() {
+ int size() {
return this.routeKeys.length;
}
- public OffsetMap with(final RouteKey key) {
+ OffsetMap with(final RouteKey key) {
// TODO: we could make this faster if we had an array-backed Set and requiring
// the caller to give us the result of offsetOf() -- as that indicates
// where to insert the new routerId while maintaining the sorted nature
return OFFSETMAPS.getUnchecked(builder.build());
}
- public OffsetMap without(final RouteKey key) {
+ OffsetMap without(final RouteKey key) {
final Builder<RouteKey> builder = ImmutableSet.builder();
final int index = indexOfRouterId(key);
if (index < 0) {
return -1;
}
- public <T> T getValue(final T[] array, final int offset) {
+ RouteKey getKey(final int offset) {
+ return routeKeys[offset];
+ }
+
+ <T> T getValue(final T[] array, final int offset) {
Preconditions.checkArgument(offset >= 0, NEGATIVEOFFSET, offset);
Preconditions.checkArgument(offset < this.routeKeys.length, INVALIDOFFSET, offset, this.routeKeys.length);
return array[offset];
}
- public <T> void setValue(final T[] array, final int offset, final T value) {
+ <T> void setValue(final T[] array, final int offset, final T value) {
Preconditions.checkArgument(offset >= 0, NEGATIVEOFFSET, offset);
Preconditions.checkArgument(offset < this.routeKeys.length, INVALIDOFFSET, offset, this.routeKeys.length);
array[offset] = value;
}
- public <T> T[] expand(final OffsetMap oldOffsets, final T[] oldArray, final int offset) {
+ <T> T[] expand(final OffsetMap oldOffsets, final T[] oldArray, final int offset) {
@SuppressWarnings("unchecked")
final T[] ret = (T[]) Array.newInstance(oldArray.getClass().getComponentType(), this.routeKeys.length);
final int oldSize = oldOffsets.routeKeys.length;
return ret;
}
- public <T> T[] removeValue(final T[] oldArray, final int offset, final T[] emptyArray) {
+ <T> T[] removeValue(final T[] oldArray, final int offset, final T[] emptyArray) {
final int length = oldArray.length;
Preconditions.checkArgument(offset >= 0, NEGATIVEOFFSET, offset);
Preconditions.checkArgument(offset < this.routeKeys.length, INVALIDOFFSET, offset, length);
boolean isEmpty() {
return this.size() == 0;
}
-
- public List<RouteKey> getRouteKeysList() {
- return Arrays.stream(this.routeKeys).collect(Collectors.toList());
- }
}
\ No newline at end of file
import org.opendaylight.yangtools.concepts.Immutable;
@NonNullByDefault
-public final class RouteKey implements Comparable<RouteKey>, Immutable {
+final class RouteKey implements Comparable<RouteKey>, Immutable {
private final UnsignedInteger routerId;
private final long remotePathId;
package org.opendaylight.protocol.bgp.mode.impl.add.all.paths;
import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.List;
-import org.opendaylight.protocol.bgp.mode.api.BestPathState;
-import org.opendaylight.protocol.bgp.mode.impl.BestPathStateImpl;
+import com.google.common.collect.ImmutableList.Builder;
import org.opendaylight.protocol.bgp.mode.impl.add.AddPathAbstractRouteEntry;
import org.opendaylight.protocol.bgp.mode.impl.add.AddPathBestPath;
-import org.opendaylight.protocol.bgp.mode.impl.add.RouteKey;
+import org.opendaylight.protocol.bgp.mode.impl.add.AddPathSelector;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.Route;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.tables.Routes;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.Identifiable;
import org.opendaylight.yangtools.yang.binding.Identifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
final class AllPathsRouteEntry<C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>,
R extends Route & ChildOf<? super S> & Identifiable<I>, I extends Identifier<R>>
extends AddPathAbstractRouteEntry<C, S, R, I> {
- AllPathsRouteEntry() {
- }
+ private static final Logger LOG = LoggerFactory.getLogger(AllPathsRouteEntry.class);
@Override
- public boolean selectBest(final long localAs) {
- final List<AddPathBestPath> newBestPathList = new ArrayList<>();
- final List<RouteKey> keyList = this.offsets.getRouteKeysList();
-
- if (!keyList.isEmpty()) {
- /* we set the best path first on List for not supported Add path cases*/
- final AddPathBestPath newBest = selectBest(localAs, keyList);
- newBestPathList.add(newBest);
- keyList.remove(newBest.getRouteKey());
- /*we add the rest of path, regardless in what order they are, since this is all path case */
- for (final RouteKey key : keyList) {
- final int offset = this.offsets.offsetOf(key);
- final Route route = this.offsets.getValue(this.values, offset);
- if (route != null) {
- final BestPathState state = new BestPathStateImpl(route.getAttributes());
- final AddPathBestPath bestPath = new AddPathBestPath(state, key,
- this.offsets.getValue(this.pathsId, offset), offset);
- newBestPathList.add(bestPath);
- }
- }
+ protected ImmutableList<AddPathBestPath> selectBest(final long localAs, final int size) {
+ // Select the best path for the case when AddPath is not supported
+ final AddPathSelector selector = new AddPathSelector(localAs);
+ for (int offset = 0; offset < size; ++offset) {
+ processOffset(selector, offset);
}
- return isBestPathNew(ImmutableList.copyOf(newBestPathList));
+
+ final AddPathBestPath newBest = selector.result();
+ LOG.trace("Best path selected {}", newBest);
+
+ final Builder<AddPathBestPath> builder = ImmutableList.builderWithExpectedSize(size);
+ builder.add(newBest);
+
+ // Since we are selecting all paths, add all the other paths, do that in two steps, skipping the selected
+ // route.
+ for (int offset = 0; offset < newBest.getOffset(); ++offset) {
+ builder.add(bestPathAt(offset));
+ }
+ for (int offset = newBest.getOffset() + 1; offset < size; ++offset) {
+ builder.add(bestPathAt(offset));
+ }
+
+ return builder.build();
}
}
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
-
package org.opendaylight.protocol.bgp.mode.impl.add.n.paths;
+import static com.google.common.base.Preconditions.checkArgument;
+
import org.opendaylight.protocol.bgp.mode.api.PathSelectionMode;
import org.opendaylight.protocol.bgp.mode.api.RouteEntry;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.Route;
import org.opendaylight.yangtools.yang.binding.Identifier;
public final class AddPathBestNPathSelection implements PathSelectionMode {
- private final long npaths;
+ private final int npaths;
- public AddPathBestNPathSelection(final long npaths) {
+ public AddPathBestNPathSelection(final int npaths) {
+ checkArgument(npaths > 1);
this.npaths = npaths;
}
package org.opendaylight.protocol.bgp.mode.impl.add.n.paths;
import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.List;
+import com.google.common.collect.ImmutableList.Builder;
import org.opendaylight.protocol.bgp.mode.impl.add.AddPathAbstractRouteEntry;
import org.opendaylight.protocol.bgp.mode.impl.add.AddPathBestPath;
-import org.opendaylight.protocol.bgp.mode.impl.add.RouteKey;
+import org.opendaylight.protocol.bgp.mode.impl.add.AddPathSelector;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.Route;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.tables.Routes;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.Identifiable;
import org.opendaylight.yangtools.yang.binding.Identifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
final class NPathsRouteEntry<C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>,
R extends Route & ChildOf<? super S> & Identifiable<I>, I extends Identifier<R>>
extends AddPathAbstractRouteEntry<C, S, R, I> {
- private final long npaths;
+ private static final Logger LOG = LoggerFactory.getLogger(NPathsRouteEntry.class);
- NPathsRouteEntry(final long npaths) {
+ private final int npaths;
+
+ NPathsRouteEntry(final int npaths) {
this.npaths = npaths;
}
@Override
- public boolean selectBest(final long localAs) {
- final List<AddPathBestPath> newBestPathList = new ArrayList<>();
- final List<RouteKey> keyList = this.offsets.getRouteKeysList();
- final long maxSearch = this.npaths < this.offsets.size()
- && this.npaths != 0 ? this.npaths : this.offsets.size();
- for (long i = 0; i < maxSearch; ++i) {
- final AddPathBestPath newBest = selectBest(localAs, keyList);
- newBestPathList.add(newBest);
- keyList.remove(newBest.getRouteKey());
+ protected ImmutableList<AddPathBestPath> selectBest(final long localAs, final int size) {
+ final int limit = Math.min(npaths, size);
+ switch (limit) {
+ case 0:
+ return ImmutableList.of();
+ case 1:
+ return ImmutableList.of(bestPathAt(0));
+ default:
+ return selectBest(localAs, size, limit);
}
- return isBestPathNew(ImmutableList.copyOf(newBestPathList));
+ }
+
+ private ImmutableList<AddPathBestPath> selectBest(final long localAs, final int size, final int limit) {
+ // Scratch pool of offsets, we set them to true as we use them up.
+ final boolean[] offsets = new boolean[size];
+ final Builder<AddPathBestPath> builder = ImmutableList.builderWithExpectedSize(limit);
+
+ // This ends up being (limit * size) traversals of offsets[], but that should be fine, as it is a dense array.
+ // If this ever becomes a problem, we can optimize by having a AddPathSelector which remembers previous state,
+ // so that we can rewind it. That will allow us to not only skip offset searches, but also recomputing the
+ // (relatively) costly per-path state from scratch.
+ for (int search = 0; search < limit; ++search) {
+ final AddPathSelector selector = new AddPathSelector(localAs);
+ for (int offset = 0; offset < size; ++offset) {
+ if (!offsets[offset]) {
+ processOffset(selector, offset);
+ }
+ }
+ final AddPathBestPath result = selector.result();
+ LOG.trace("Path {} selected {}", search, result);
+ builder.add(result);
+
+ offsets[result.getOffset()] = true;
+ }
+
+ return builder.build();
}
}
if (afiSafi2 != null) {
final Optional<BgpTableType> bgpTableType = tableTypeRegistry.getTableType(afiSafi.getAfiSafiName());
if (bgpTableType.isPresent()) {
- final Short sendMax = afiSafi2.getSendMax();
+ final int sendMax = afiSafi2.getSendMax();
final PathSelectionMode selectionMode;
if (sendMax > 1) {
- selectionMode = new AddPathBestNPathSelection(sendMax.longValue());
+ selectionMode = new AddPathBestNPathSelection(sendMax);
} else {
selectionMode = new AllPathSelection();
}
private RIBImpl ribImpl;
private Channel serverChannel;
+ @Override
@Before
public void setUp() throws Exception {
super.setUp();
final TablesKey tk = new TablesKey(Ipv4AddressFamily.class, UnicastSubsequentAddressFamily.class);
- final Map<TablesKey, PathSelectionMode> pathTables = ImmutableMap.of(tk, new AddPathBestNPathSelection(2L));
+ final Map<TablesKey, PathSelectionMode> pathTables = ImmutableMap.of(tk, new AddPathBestNPathSelection(2));
this.ribImpl = new RIBImpl(this.tableRegistry, new RibId("test-rib"), AS_NUMBER, new BgpId(RIB_ID),
this.ribExtension,
this.serverChannel = channelFuture.channel();
}
+ @Override
@After
public void tearDown() throws ExecutionException, InterruptedException {
waitFutureSuccess(this.serverChannel.close());
import static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IetfInetUtil.INSTANCE;
import com.google.common.collect.ImmutableList;
-import com.google.common.primitives.Shorts;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
private static final Ipv4Address CLUSTER_ID = new Ipv4Address("4.3.2.1");
private static final Ipv4Address LOCAL_HOST = new Ipv4Address("127.0.0.1");
- private static final Long ALL_PATHS = 0L;
- private static final Long N_PATHS = 2L;
+ private static final Short ALL_PATHS = 0;
+ private static final Short N_PATHS = 2;
private static final PathSelectionMode ADD_PATH_BEST_N_PATH_SELECTION = new AddPathBestNPathSelection(N_PATHS);
private static final PathSelectionMode ADD_PATH_BEST_ALL_PATH_SELECTION = new AllPathSelection();
private static final BgpTableType BGP_TABLE_TYPE_IPV4 = new BgpTableTypeImpl(Ipv4AddressFamily.class,
TABLE_TYPES.add(new BgpTableTypeImpl(Ipv4AddressFamily.class, UnicastSubsequentAddressFamily.class));
TABLE_TYPES.add(new BgpTableTypeImpl(Ipv6AddressFamily.class, UnicastSubsequentAddressFamily.class));
AFISAFIS.add(new AfiSafiBuilder().setAfiSafiName(IPV4UNICAST.class)
- .addAugmentation(GlobalAddPathsConfig.class, new GlobalAddPathsConfigBuilder().setReceive(true)
- .setSendMax(Shorts.checkedCast(N_PATHS)).build()).build());
+ .addAugmentation(GlobalAddPathsConfig.class, new GlobalAddPathsConfigBuilder().setReceive(Boolean.TRUE)
+ .setSendMax(N_PATHS).build()).build());
AFISAFIS.add(new AfiSafiBuilder().setAfiSafiName(IPV6UNICAST.class)
- .addAugmentation(GlobalAddPathsConfig.class, new GlobalAddPathsConfigBuilder().setReceive(true)
- .setSendMax(Shorts.checkedCast(ALL_PATHS)).build()).build());
+ .addAugmentation(GlobalAddPathsConfig.class, new GlobalAddPathsConfigBuilder().setReceive(Boolean.TRUE)
+ .setSendMax(ALL_PATHS).build()).build());
}
@Mock
final List<AfiSafi> families = new ArrayList<>();
families.add(new AfiSafiBuilder().setAfiSafiName(IPV4UNICAST.class)
.addAugmentation(GlobalAddPathsConfig.class, new GlobalAddPathsConfigBuilder()
- .setSendMax(Shorts.checkedCast(N_PATHS)).build()).build());
+ .setSendMax(N_PATHS).build()).build());
families.add(new AfiSafiBuilder().setAfiSafiName(IPV6UNICAST.class)
.addAugmentation(GlobalAddPathsConfig.class, new GlobalAddPathsConfigBuilder()
- .setSendMax(Shorts.checkedCast(ALL_PATHS)).build()).build());
+ .setSendMax(ALL_PATHS).build()).build());
final Map<BgpTableType, PathSelectionMode> result = OpenConfigMappingUtil
.toPathSelectionMode(families, this.tableTypeRegistry);
final Map<BgpTableType, PathSelectionMode> expected = new HashMap<>();
families.add(new AfiSafiBuilder().setAfiSafiName(IPV4UNICAST.class)
.addAugmentation(NeighborAddPathsConfig.class,
new NeighborAddPathsConfigBuilder()
- .setReceive(true).setSendMax(Shorts.checkedCast(ALL_PATHS)).build()).build());
+ .setReceive(Boolean.TRUE).setSendMax(ALL_PATHS).build()).build());
families.add(new AfiSafiBuilder().setAfiSafiName(IPV6UNICAST.class)
.addAugmentation(NeighborAddPathsConfig.class,
new NeighborAddPathsConfigBuilder()
- .setReceive(false).setSendMax(Shorts.checkedCast(N_PATHS)).build()).build());
+ .setReceive(Boolean.FALSE).setSendMax(N_PATHS).build()).build());
families.add(new AfiSafiBuilder().setAfiSafiName(IPV6LABELLEDUNICAST.class)
.addAugmentation(NeighborAddPathsConfig.class,
- new NeighborAddPathsConfigBuilder()
- .setReceive(false).build()).build());
+ new NeighborAddPathsConfigBuilder().setReceive(Boolean.FALSE).build()).build());
final List<AddressFamilies> result = OpenConfigMappingUtil
.toAddPathCapability(families, this.tableTypeRegistry);
assertEquals(FAMILIES, result);