Added IPv6 routing support to SDN-IP, and integrated it with BGP:
 * The routing entries as received from BGP can be either IPv4 or IPv6
 * The CLI prints the IPv4 and IPv6 routes
 * The BGP peering is still over IPv4, so configuration-wise the IPv6
   routes from the eBGP peers have to be configured to use the IPv4 peering.
 * The integration/testing with the IPv6 Network Discovery Protocol is not
   done yet.
 * The integration/testing with IPv6 intents is not done yet.

Also:
 * Moved nested class BgpSessionManager.BgpRouteSelector out of the
   BgpSessionManager class.
 * Code cleanup.

Change-Id: I9f2dbe4395a72d353bbf215a8a14b01b53c3423f
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/bgp/BgpRouteSelector.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/bgp/BgpRouteSelector.java
new file mode 100644
index 0000000..1569c07
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/bgp/BgpRouteSelector.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.sdnip.bgp;
+
+import java.util.Collection;
+import java.util.LinkedList;
+
+import org.onlab.packet.IpPrefix;
+import org.onosproject.sdnip.RouteUpdate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class to receive and process the BGP routes from each BGP Session/Peer.
+ */
+class BgpRouteSelector {
+    private static final Logger log =
+        LoggerFactory.getLogger(BgpRouteSelector.class);
+
+    private BgpSessionManager bgpSessionManager;
+
+    /**
+     * Constructor.
+     *
+     * @param bgpSessionManager the BGP Session Manager to use
+     */
+    BgpRouteSelector(BgpSessionManager bgpSessionManager) {
+        this.bgpSessionManager = bgpSessionManager;
+    }
+
+    /**
+     * Processes route entry updates: added/updated and deleted route
+     * entries.
+     *
+     * @param bgpSession the BGP session the route entry updates were
+     * received on
+     * @param addedBgpRouteEntries the added/updated route entries to process
+     * @param deletedBgpRouteEntries the deleted route entries to process
+     */
+    synchronized void routeUpdates(BgpSession bgpSession,
+                        Collection<BgpRouteEntry> addedBgpRouteEntries,
+                        Collection<BgpRouteEntry> deletedBgpRouteEntries) {
+        Collection<RouteUpdate> routeUpdates = new LinkedList<>();
+        RouteUpdate routeUpdate;
+
+        if (bgpSessionManager.isShutdown()) {
+            return;         // Ignore any leftover updates if shutdown
+        }
+        // Process the deleted route entries
+        for (BgpRouteEntry bgpRouteEntry : deletedBgpRouteEntries) {
+            routeUpdate = processDeletedRoute(bgpSession, bgpRouteEntry);
+            if (routeUpdate != null) {
+                routeUpdates.add(routeUpdate);
+            }
+        }
+
+        // Process the added/updated route entries
+        for (BgpRouteEntry bgpRouteEntry : addedBgpRouteEntries) {
+            routeUpdate = processAddedRoute(bgpSession, bgpRouteEntry);
+            if (routeUpdate != null) {
+                routeUpdates.add(routeUpdate);
+            }
+        }
+        bgpSessionManager.getRouteListener().update(routeUpdates);
+    }
+
+    /**
+     * Processes an added/updated route entry.
+     *
+     * @param bgpSession the BGP session the route entry update was received on
+     * @param bgpRouteEntry the added/updated route entry
+     * @return the result route update that should be forwarded to the
+     * Route Listener, or null if no route update should be forwarded
+     */
+    private RouteUpdate processAddedRoute(BgpSession bgpSession,
+                                          BgpRouteEntry bgpRouteEntry) {
+        RouteUpdate routeUpdate;
+        BgpRouteEntry bestBgpRouteEntry =
+            bgpSessionManager.findBgpRoute(bgpRouteEntry.prefix());
+
+        //
+        // Install the new route entry if it is better than the
+        // current best route.
+        //
+        if ((bestBgpRouteEntry == null) ||
+            bgpRouteEntry.isBetterThan(bestBgpRouteEntry)) {
+            bgpSessionManager.addBgpRoute(bgpRouteEntry);
+            routeUpdate =
+                new RouteUpdate(RouteUpdate.Type.UPDATE, bgpRouteEntry);
+            return routeUpdate;
+        }
+
+        //
+        // If the route entry arrived on the same BGP Session as
+        // the current best route, then elect the next best route
+        // and install it.
+        //
+        if (bestBgpRouteEntry.getBgpSession() !=
+            bgpRouteEntry.getBgpSession()) {
+            return null;            // Nothing to do
+        }
+
+        // Find the next best route
+        bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
+        if (bestBgpRouteEntry == null) {
+            //
+            // TODO: Shouldn't happen. Install the new route as a
+            // pre-caution.
+            //
+            log.debug("BGP next best route for prefix {} is missing. " +
+                      "Adding the route that is currently processed.",
+                      bgpRouteEntry.prefix());
+            bestBgpRouteEntry = bgpRouteEntry;
+        }
+
+        // Install the next best route
+        bgpSessionManager.addBgpRoute(bestBgpRouteEntry);
+        routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
+                                      bestBgpRouteEntry);
+        return routeUpdate;
+    }
+
+    /**
+     * Processes a deleted route entry.
+     *
+     * @param bgpSession the BGP session the route entry update was received on
+     * @param bgpRouteEntry the deleted route entry
+     * @return the result route update that should be forwarded to the
+     * Route Listener, or null if no route update should be forwarded
+     */
+    private RouteUpdate processDeletedRoute(BgpSession bgpSession,
+                                            BgpRouteEntry bgpRouteEntry) {
+        RouteUpdate routeUpdate;
+        BgpRouteEntry bestBgpRouteEntry =
+            bgpSessionManager.findBgpRoute(bgpRouteEntry.prefix());
+
+        //
+        // Remove the route entry only if it was the best one.
+        // Install the the next best route if it exists.
+        //
+        // NOTE: We intentionally use "==" instead of method equals(),
+        // because we need to check whether this is same object.
+        //
+        if (bgpRouteEntry != bestBgpRouteEntry) {
+            return null;            // Nothing to do
+        }
+
+        //
+        // Find the next best route
+        //
+        bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
+        if (bestBgpRouteEntry != null) {
+            // Install the next best route
+            bgpSessionManager.addBgpRoute(bestBgpRouteEntry);
+            routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
+                                          bestBgpRouteEntry);
+            return routeUpdate;
+        }
+
+        //
+        // No route found. Remove the route entry
+        //
+        bgpSessionManager.removeBgpRoute(bgpRouteEntry.prefix());
+        routeUpdate = new RouteUpdate(RouteUpdate.Type.DELETE, bgpRouteEntry);
+        return routeUpdate;
+    }
+
+    /**
+     * Finds the best route entry among all BGP Sessions.
+     *
+     * @param prefix the prefix of the route
+     * @return the best route if found, otherwise null
+     */
+    private BgpRouteEntry findBestBgpRoute(IpPrefix prefix) {
+        BgpRouteEntry bestRoute = null;
+
+        // Iterate across all BGP Sessions and select the best route
+        for (BgpSession bgpSession : bgpSessionManager.getBgpSessions()) {
+            BgpRouteEntry route = bgpSession.findBgpRoute(prefix);
+            if (route == null) {
+                continue;
+            }
+            if ((bestRoute == null) || route.isBetterThan(bestRoute)) {
+                bestRoute = route;
+            }
+        }
+        return bestRoute;
+    }
+}