YANG revision dates mass-update
[bgpcep.git] / bgp / path-selection-mode / src / main / java / org / opendaylight / protocol / bgp / mode / impl / add / AddPathAbstractRouteEntry.java
index cc664dc64740c350796ee3d9c0a37679f5fcc55d..56eab86aeff32fc1cba342d1f7821663efb1e930 100644 (file)
  */
 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.List;
-import java.util.Map;
 import java.util.Optional;
-import javax.annotation.concurrent.NotThreadSafe;
-import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
-import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
-import org.opendaylight.protocol.bgp.mode.api.BestPath;
-import org.opendaylight.protocol.bgp.mode.spi.AbstractRouteEntry;
-import org.opendaylight.protocol.bgp.rib.spi.CacheDisconnectedPeers;
-import org.opendaylight.protocol.bgp.rib.spi.ExportPolicyPeerTracker;
-import org.opendaylight.protocol.bgp.rib.spi.PeerExportGroup;
-import org.opendaylight.protocol.bgp.rib.spi.PeerExportGroup.PeerExporTuple;
+import java.util.stream.Collectors;
+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.RouterIds;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.PeerId;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.PeerRole;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.TablesKey;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
+import org.opendaylight.protocol.bgp.rib.spi.RouterId;
+import org.opendaylight.protocol.bgp.rib.spi.entry.ActualBestPathRoutes;
+import org.opendaylight.protocol.bgp.rib.spi.entry.AdvertizedRoute;
+import org.opendaylight.protocol.bgp.rib.spi.entry.RouteEntryInfo;
+import org.opendaylight.protocol.bgp.rib.spi.entry.StaleBestPathRoute;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.PathId;
+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.ChildOf;
+import org.opendaylight.yangtools.yang.binding.ChoiceIn;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.Identifiable;
+import org.opendaylight.yangtools.yang.binding.Identifier;
+import org.opendaylight.yangtools.yang.common.Uint32;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * A single route entry inside a route table. Maintains the attributes of
- * from all contributing peers. The information is stored in arrays with a
- * shared map of offsets for peers to allow lookups. This is needed to
- * maintain low memory overhead in face of large number of routes and peers,
- * where individual object overhead becomes the dominating factor.
+ * A single route entry inside a route table. Maintains the attributes from all contributing peers. The information is
+ * stored in arrays with a shared map of offsets for peers to allow lookups. This is needed to maintain low memory
+ * overhead in face of large number of routes and peers, where individual object overhead becomes the dominating factor.
+ *
+ * <p>
+ * This class is NOT thread-safe.
  */
-@NotThreadSafe
-public abstract class AddPathAbstractRouteEntry extends AbstractRouteEntry {
+public abstract class AddPathAbstractRouteEntry<C extends Routes & DataObject & ChoiceIn<Tables>,
+        S extends ChildOf<? super C>, R extends Route & ChildOf<? super S> & Identifiable<I>, I extends Identifier<R>>
+        implements RouteEntry<C, S, R, I> {
+    private static final class Stale<C extends Routes & DataObject & ChoiceIn<Tables>,
+            S extends ChildOf<? super C>, R extends Route & ChildOf<? super S> & Identifiable<I>,
+            I extends Identifier<R>> extends StaleBestPathRoute<C, S, R, I> {
+        private final List<I> addPathRouteKeyIdentifier;
+        private final List<I> staleRouteKeyIdentifier;
+        private final boolean isNonAddPathBestPathNew;
 
-    private static final Logger LOG = LoggerFactory.getLogger(AddPathAbstractRouteEntry.class);
-    private List<AddPathBestPath> bestPath;
-    private List<AddPathBestPath> bestPathRemoved;
-    protected OffsetMap offsets = OffsetMap.EMPTY;
-    protected ContainerNode[] values = new ContainerNode[0];
-    protected Long[] pathsId = new Long[0];
-    private long pathIdCounter = 0L;
-    private boolean oldNonAddPathBestPathTheSame;
-    private List<AddPathBestPath> newBestPathToBeAdvertised;
-    private List<RemovedPath> removedPaths;
+        Stale(final RIBSupport<C, S, R, I> ribSupport, final String routeKey, final List<PathId> staleRoutesPathIds,
+            final List<PathId> withdrawalRoutePathIds, final boolean isNonAddPathBestPathNew) {
+            super(ribSupport.createRouteListKey(routeKey));
+            this.isNonAddPathBestPathNew = isNonAddPathBestPathNew;
 
-    private static final class RemovedPath {
-        private final RouteKey key;
-        private final Long pathId;
+            this.staleRouteKeyIdentifier = staleRoutesPathIds.stream()
+                    .map(pathId -> ribSupport.createRouteListKey(pathId, routeKey)).collect(Collectors.toList());
+            if (withdrawalRoutePathIds != null) {
+                this.addPathRouteKeyIdentifier = withdrawalRoutePathIds.stream()
+                        .map(pathId -> ribSupport.createRouteListKey(pathId, routeKey)).collect(Collectors.toList());
+            } else {
+                this.addPathRouteKeyIdentifier = Collections.emptyList();
+            }
+        }
 
-        RemovedPath(final RouteKey key, final Long pathId) {
-            this.key = key;
-            this.pathId = pathId;
+        @Override
+        public List<I> getStaleRouteKeyIdentifiers() {
+            return this.staleRouteKeyIdentifier;
         }
 
-        Long getPathId() {
-            return this.pathId;
+        @Override
+        public List<I> getAddPathRouteKeyIdentifiers() {
+            return addPathRouteKeyIdentifier;
         }
 
-        UnsignedInteger getRouteId() {
-            return this.key.getRouteId();
+        @Override
+        public boolean isNonAddPathBestPathNew() {
+            return isNonAddPathBestPathNew;
         }
     }
 
-    private int addRoute(final RouteKey key, final ContainerNode attributes) {
+    private static final Logger LOG = LoggerFactory.getLogger(AddPathAbstractRouteEntry.class);
+    private static final Uint32[] EMPTY_PATHS_ID = new Uint32[0];
+    private static final Route[] EMPTY_VALUES = new Route[0];
+
+    private RouteKeyOffsets offsets = RouteKeyOffsets.EMPTY;
+    private R[] values = (R[]) EMPTY_VALUES;
+    private Uint32[] pathsId = EMPTY_PATHS_ID;
+    private List<AddPathBestPath> bestPath;
+    private List<AddPathBestPath> bestPathRemoved;
+    private List<AddPathBestPath> newBestPathToBeAdvertised;
+    private List<Uint32> 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 RouteKeyOffsets 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 int addRoute(final RouterId routerId, final Uint32 remotePathId, final R route) {
+        final RouteKey key = new RouteKey(routerId, remotePathId);
         int offset = this.offsets.offsetOf(key);
         if (offset < 0) {
-            final OffsetMap newOffsets = this.offsets.with(key);
+            final RouteKeyOffsets newOffsets = this.offsets.with(key);
             offset = newOffsets.offsetOf(key);
-            final ContainerNode[] newAttributes = newOffsets.expand(this.offsets, this.values, offset);
-            final Long[] newPathsId = newOffsets.expand(this.offsets, this.pathsId, offset);
-            this.values = newAttributes;
+            final R[] newRoute = newOffsets.expand(this.offsets, this.values, offset);
+            final Uint32[] newPathsId = newOffsets.expand(this.offsets, this.pathsId, offset);
+            this.values = newRoute;
             this.offsets = newOffsets;
             this.pathsId = newPathsId;
-            this.offsets.setValue(this.pathsId, offset, ++this.pathIdCounter);
+            this.offsets.setValue(this.pathsId, offset, Uint32.valueOf(++this.pathIdCounter));
         }
-        this.offsets.setValue(this.values, offset, attributes);
-        LOG.trace("Added route from {} attributes {}", key.getRouteId(), attributes);
+        this.offsets.setValue(this.values, offset, route);
+        LOG.trace("Added route {} from {}", route, routerId);
         return offset;
     }
 
-    protected int addRoute(final RouteKey key, final NodeIdentifier attributesIdentifier, final NormalizedNode<?, ?> data) {
-        LOG.trace("Find {} in {}", attributesIdentifier, data);
-        final ContainerNode advertisedAttrs = (ContainerNode) NormalizedNodes.findNode(data, attributesIdentifier).orNull();
-        return addRoute(key, advertisedAttrs);
-    }
-
-    /**
-     * Remove route
-     *
-     * @param key RouteKey of removed route
-     * @param offset Offset of removed route
-     * @return true if it was the last route
-     */
-    protected final boolean removeRoute(final RouteKey key, final int offset) {
-        final Long pathId = this.offsets.getValue(this.pathsId, offset);
-        this.values = this.offsets.removeValue(this.values, offset);
-        this.pathsId = this.offsets.removeValue(this.pathsId, offset);
+    @Override
+    public final boolean removeRoute(final RouterId routerId, final Uint32 remotePathId) {
+        final RouteKey key = new RouteKey(routerId, remotePathId);
+        final int offset = this.offsets.offsetOf(key);
+        final Uint32 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.offsets = this.offsets.without(key);
-        if(this.removedPaths == null) {
-            this.removedPaths = new ArrayList<>();
+        if (this.removedPathsId == null) {
+            this.removedPathsId = new ArrayList<>();
         }
-        this.removedPaths.add(new RemovedPath(key, pathId));
-        return isEmpty();
+        this.removedPathsId.add(pathId);
+        return this.offsets.isEmpty();
     }
 
     @Override
-    public void updateRoute(final TablesKey localTK, final ExportPolicyPeerTracker peerPT, final YangInstanceIdentifier locRibTarget, final RIBSupport ribSup,
-        final CacheDisconnectedPeers discPeers, final DOMDataWriteTransaction tx, final PathArgument routeIdPA) {
-        //FIXME: Here we should have 2 independent removal for LocRib and RibOut, first will be only executed for best path changes, the second
-        // when the owner of the route removes it.
-        if(this.bestPathRemoved != null) {
-            this.bestPathRemoved.forEach(path -> {
-                final PathArgument routeIdAddPath = ribSup.getRouteIdAddPath(path.getPathId(), routeIdPA);
-                final YangInstanceIdentifier pathAddPathTarget = ribSup.routePath(locRibTarget.node(ROUTES_IDENTIFIER), routeIdAddPath);
-                fillLocRib(pathAddPathTarget, null, tx);
-            });
-            this.bestPathRemoved = null;
-        }
-        if(this.removedPaths != null) {
-            this.removedPaths.forEach(removedPath -> {
-                final PathArgument routeIdAddPath = ribSup.getRouteIdAddPath(removedPath.getPathId(), routeIdPA);
-                fillAdjRibsOut(true, null, null, null, routeIdPA, routeIdAddPath, RouterIds.createPeerId(removedPath.getRouteId()),
-                    peerPT, localTK, ribSup, discPeers, tx);
-            });
-            this.removedPaths = null;
+    public final Optional<StaleBestPathRoute<C, S, R, I>> removeStalePaths(final RIBSupport<C, S, R, I> ribSupport,
+            final String routeKey) {
+        final List<PathId> stalePaths;
+        if (bestPathRemoved != null && !bestPathRemoved.isEmpty()) {
+            stalePaths = bestPathRemoved.stream().map(AddPathBestPath::getPathIdLong)
+                    .map(AddPathAbstractRouteEntry::pathIdObj).collect(Collectors.toList());
+            bestPathRemoved = null;
+        } else {
+            stalePaths = Collections.emptyList();
         }
 
-        if(this.newBestPathToBeAdvertised != null) {
-            this.newBestPathToBeAdvertised.forEach(path -> addPathToDataStore(path, isFirstBestPath(this.bestPath.indexOf(path)), routeIdPA,
-                locRibTarget, ribSup, peerPT, localTK, discPeers, tx));
-            this.newBestPathToBeAdvertised = null;
+        List<PathId> removedPaths;
+        if (removedPathsId != null) {
+            removedPaths = Lists.transform(removedPathsId, AddPathAbstractRouteEntry::pathIdObj);
+            this.removedPathsId = null;
+        } else {
+            removedPaths = Collections.emptyList();
         }
+
+        return stalePaths.isEmpty() && removedPaths.isEmpty() ? Optional.empty()
+                : Optional.of(new Stale<>(ribSupport, routeKey, stalePaths, removedPaths, isNonAddPathBestPathNew));
     }
 
     @Override
-    public void writeRoute(final PeerId destPeer, final PathArgument routeId, final YangInstanceIdentifier rootPath, final PeerExportGroup peerGroup,
-        final TablesKey localTK, final ExportPolicyPeerTracker peerPT, final RIBSupport ribSup, final CacheDisconnectedPeers discPeers,
-        final DOMDataWriteTransaction tx) {
-        final boolean destPeerSupAddPath = peerPT.isAddPathSupportedByPeer(destPeer);
-        if(this.bestPath != null) {
-            final PeerRole destPeerRole = getRoutePeerIdRole(peerPT, destPeer);
-            this.bestPath.stream().filter(path -> filterRoutes(path.getPeerId(), destPeer, peerPT, localTK, discPeers, destPeerRole) &&
-                peersSupportsAddPathOrIsFirstBestPath(destPeerSupAddPath, isFirstBestPath(this.bestPath.indexOf(path))))
-                .forEach(path -> writeRoutePath(destPeer, routeId, peerPT, peerGroup, destPeerSupAddPath, path, rootPath, localTK, ribSup, tx));
+    public final List<AdvertizedRoute<C, S, R, I>> newBestPaths(final RIBSupport<C, S, R, I> ribSupport,
+            final String routeKey) {
+        if (this.newBestPathToBeAdvertised == null || this.newBestPathToBeAdvertised.isEmpty()) {
+            return Collections.emptyList();
         }
-    }
-
-    private void writeRoutePath(final PeerId destPeer, final PathArgument routeId, final ExportPolicyPeerTracker peerPT,
-        final PeerExportGroup peerGroup, final boolean destPeerSupAddPath,
-        final BestPath path, final YangInstanceIdentifier rootPath, final TablesKey localTK, final RIBSupport ribSup, final DOMDataWriteTransaction tx) {
-        final PathArgument routeIdAddPath = ribSup.getRouteIdAddPath(path.getPathId(), routeId);
-        final ContainerNode effectiveAttributes = peerGroup.effectiveAttributes(getRoutePeerIdRole(peerPT,path.getPeerId()), path.getAttributes());
-        if (destPeerSupAddPath) {
-            writeRoute(destPeer, getAdjRibOutYII(ribSup, rootPath, routeIdAddPath, localTK), effectiveAttributes, createValue(routeIdAddPath, path), ribSup, tx);
-        } else {
-            writeRoute(destPeer, getAdjRibOutYII(ribSup, rootPath, routeId, localTK), effectiveAttributes, createValue(routeId, path), ribSup, tx);
+        final List<AdvertizedRoute<C, S, R, I>> advertized = new ArrayList<>(newBestPathToBeAdvertised.size());
+        final AddPathBestPath firstBestPath = this.bestPath.isEmpty() ? null : this.bestPath.get(0);
+        for (final AddPathBestPath path : this.newBestPathToBeAdvertised) {
+            final R routeAddPath = createRoute(ribSupport, routeKey, path);
+            // FIXME: can we use identity check here?
+            final boolean isFirstBestPath = firstBestPath != null && firstBestPath.equals(path);
+            final AdvertizedRoute<C, S, R, I> adv = new AdvertizedRoute<>(ribSupport, isFirstBestPath,
+                    routeAddPath, path.getAttributes(), path.getPeerId(), path.isDepreferenced());
+            advertized.add(adv);
         }
+        this.newBestPathToBeAdvertised = null;
+        return advertized;
     }
 
-    private void addPathToDataStore(final BestPath path, final boolean isFirstBestPath, final PathArgument routeIdPA, final YangInstanceIdentifier locRibTarget,
-        final RIBSupport ribSup, final ExportPolicyPeerTracker peerPT, final TablesKey localTK, final CacheDisconnectedPeers discPeers,
-        final DOMDataWriteTransaction tx) {
-        final PathArgument routeIdAddPath = ribSup.getRouteIdAddPath(path.getPathId(), routeIdPA);
-        final YangInstanceIdentifier pathAddPathTarget = ribSup.routePath(locRibTarget.node(ROUTES_IDENTIFIER), routeIdAddPath);
-        final MapEntryNode addPathValue = createValue(routeIdAddPath, path);
-        final MapEntryNode value = createValue(routeIdPA, path);
-        LOG.trace("Selected best value {}", addPathValue);
-        fillLocRib(pathAddPathTarget, addPathValue, tx);
-        fillAdjRibsOut(isFirstBestPath, path.getAttributes(), value, addPathValue, routeIdPA, routeIdAddPath, path.getPeerId(), peerPT, localTK,
-            ribSup, discPeers, tx);
-    }
-
-    private void fillAdjRibsOut(final boolean isFirstBestPath, final ContainerNode attributes, final NormalizedNode<?, ?> value, final MapEntryNode addPathValue,
-        final PathArgument routeId, final PathArgument routeIdAddPath, final PeerId routePeerId, final ExportPolicyPeerTracker peerPT, final TablesKey
-        localTK, final RIBSupport ribSup, final CacheDisconnectedPeers discPeers, final DOMDataWriteTransaction tx) {
-        /*
-         * We need to keep track of routers and populate adj-ribs-out, too. If we do not, we need to
-         * expose from which client a particular route was learned from in the local RIB, and have
-         * the listener perform filtering.
-         *
-         * We walk the policy set in order to minimize the amount of work we do for multiple peers:
-         * if we have two eBGP peers, for example, there is no reason why we should perform the translation
-         * multiple times.
-         */
-        for (final PeerRole role : PeerRole.values()) {
-            final PeerExportGroup peerGroup = peerPT.getPeerGroup(role);
-            if (peerGroup != null) {
-                final ContainerNode effectiveAttributes = peerGroup.effectiveAttributes(getRoutePeerIdRole(peerPT, routePeerId), attributes);
-                for (final Map.Entry<PeerId, PeerExporTuple> pid : peerGroup.getPeers()) {
-                    final PeerId destPeer = pid.getKey();
-                    final boolean destPeerSupAddPath = peerPT.isAddPathSupportedByPeer(destPeer);
-                    if (filterRoutes(routePeerId, destPeer, peerPT, localTK, discPeers, getRoutePeerIdRole(peerPT, destPeer))
-                        && peersSupportsAddPathOrIsFirstBestPath(destPeerSupAddPath, isFirstBestPath)) {
-                        if (destPeerSupAddPath) {
-                            update(destPeer, getAdjRibOutYII(ribSup, pid.getValue().getYii(), routeIdAddPath, localTK), effectiveAttributes,
-                                addPathValue, ribSup, tx);
-                        } else if(!this.oldNonAddPathBestPathTheSame){
-                            update(destPeer, getAdjRibOutYII(ribSup, pid.getValue().getYii(), routeId, localTK), effectiveAttributes, value, ribSup, tx);
-                        }
-                    }
-                }
-            }
+    @Override
+    public final List<ActualBestPathRoutes<C, S, R, I>> actualBestPaths(final RIBSupport<C, S, R, I> ribSupport,
+            final RouteEntryInfo entryInfo) {
+        if (this.bestPath == null || this.bestPath.isEmpty()) {
+            return Collections.emptyList();
         }
-    }
-
-    private void update(final PeerId destPeer, final YangInstanceIdentifier routeTarget, final ContainerNode effAttr, final NormalizedNode<?, ?> value,
-        final RIBSupport ribSup, final DOMDataWriteTransaction tx) {
-        if (!writeRoute(destPeer, routeTarget, effAttr, value, ribSup, tx)) {
-            LOG.trace("Removing {} from transaction for peer {}", routeTarget, destPeer);
-            tx.delete(LogicalDatastoreType.OPERATIONAL, routeTarget);
+        final List<ActualBestPathRoutes<C, S, R, I>> preexistentRoutes = new ArrayList<>();
+        for (final AddPathBestPath path : this.bestPath) {
+            final R route = createRoute(ribSupport, entryInfo.getRouteKey(), path);
+            final ActualBestPathRoutes<C, S, R, I> adv = new ActualBestPathRoutes<>(ribSupport, route, path.getPeerId(),
+                    path.getAttributes(), path.isDepreferenced());
+            preexistentRoutes.add(adv);
         }
+        return preexistentRoutes;
     }
 
-    protected final 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();
-    }
-
-    private void selectBest(final RouteKey key, final AddPathSelector selector) {
-        final int offset = this.offsets.offsetOf(key);
-        final ContainerNode attributes = this.offsets.getValue(this.values, offset);
-        final Long pathId = this.offsets.getValue(this.pathsId, offset);
-        LOG.trace("Processing router key {} attributes {}", key, attributes);
-        selector.processPath(attributes, key, offset, pathId);
-    }
+    protected abstract ImmutableList<AddPathBestPath> selectBest(long localAs, int size);
 
     /**
-     * Process best path selection
+     * Process a specific route offset into specified selector.
      *
-     * @param localAs The local autonomous system number
-     * @param keyList List of RouteKey
-     * @return the best path inside offset map passed
+     * @param selector selector to update
+     * @param offset offset to process
      */
-    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 void processOffset(final AddPathSelector selector, final int offset) {
+        final RouteKey key = offsets.getKey(offset);
+        final R route = offsets.getValue(values, offset);
+        final Uint32 pathId = offsets.getValue(pathsId, offset);
+        LOG.trace("Processing router key {} route {}", key, route);
+        selector.processPath(route.getAttributes(), key, offset, pathId);
     }
 
-    private boolean isFirstBestPath(final int bestPathPosition) {
-        return bestPathPosition == 0;
+    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);
     }
 
-    private boolean peersSupportsAddPathOrIsFirstBestPath(final boolean peerSupportsAddPath, final boolean isFirstBestPath) {
-        return !(!peerSupportsAddPath && !isFirstBestPath);
-    }
-
-    protected boolean isBestPathNew(final List<AddPathBestPath> newBestPathList) {
-        this.oldNonAddPathBestPathTheSame = isNonAddPathBestPathTheSame(newBestPathList);
+    private boolean isBestPathNew(final ImmutableList<AddPathBestPath> newBestPathList) {
+        this.isNonAddPathBestPathNew = !isNonAddPathBestPathTheSame(newBestPathList);
         filterRemovedPaths(newBestPathList);
-        if (this.bestPathRemoved != null && !this.bestPathRemoved.isEmpty() || newBestPathList != null && !newBestPathList.equals(this.bestPath)) {
-            this.newBestPathToBeAdvertised = new ArrayList<>(newBestPathList);
-            if(this.bestPath != null) {
+        if (this.bestPathRemoved != null && !this.bestPathRemoved.isEmpty()
+                || newBestPathList != null
+                && !newBestPathList.equals(this.bestPath)) {
+            if (this.bestPath != null) {
+                this.newBestPathToBeAdvertised = new ArrayList<>(newBestPathList);
                 this.newBestPathToBeAdvertised.removeAll(this.bestPath);
+            } else {
+                this.newBestPathToBeAdvertised = newBestPathList;
             }
             this.bestPath = newBestPathList;
             LOG.trace("Actual Best {}, removed best {}", this.bestPath, this.bestPathRemoved);
@@ -283,21 +252,28 @@ public abstract class AddPathAbstractRouteEntry extends AbstractRouteEntry {
     }
 
     private boolean isNonAddPathBestPathTheSame(final List<AddPathBestPath> newBestPathList) {
-        return !(this.bestPath == null || newBestPathList == null || this.bestPath.isEmpty() || newBestPathList.isEmpty()) &&
-            this.bestPath.get(0).equals(newBestPathList.get(0));
+        return !(isEmptyOrNull(this.bestPath) || isEmptyOrNull(newBestPathList))
+                && this.bestPath.get(0).equals(newBestPathList.get(0));
+    }
+
+    private static boolean isEmptyOrNull(final List<AddPathBestPath> pathList) {
+        return pathList == null || pathList.isEmpty();
     }
 
     private void filterRemovedPaths(final List<AddPathBestPath> newBestPathList) {
-        if(this.bestPath == null) {
+        if (this.bestPath == null) {
             return;
         }
         this.bestPathRemoved = new ArrayList<>(this.bestPath);
         this.bestPath.forEach(oldBest -> {
             final Optional<AddPathBestPath> present = newBestPathList.stream()
-                .filter(newBest -> newBest.getPathId() == oldBest.getPathId() && newBest.getRouteKey() == oldBest.getRouteKey()).findAny();
-            if(present.isPresent()) {
-                this.bestPathRemoved.remove(oldBest);
-            }
+                    .filter(newBest -> newBest.getPathId() == oldBest.getPathId()
+                            && newBest.getRouteKey() == oldBest.getRouteKey()).findAny();
+            present.ifPresent(addPathBestPath -> this.bestPathRemoved.remove(oldBest));
         });
     }
+
+    private static PathId pathIdObj(final Uint32 pathId) {
+        return NON_PATH_ID_VALUE.equals(pathId) ? NON_PATH_ID : new PathId(pathId);
+    }
 }