Correctly initialize routes with multiple next hops

- Solve an issue where next hops are available on both leaf switch but one still pointing to its pair through the spines
- Improve unit tests

Change-Id: I94fe79bd9289efe544d82b858928d65201a0b0b2
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RouteHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RouteHandler.java
index 6d77da4..b278b8c 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RouteHandler.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RouteHandler.java
@@ -83,26 +83,24 @@
     }
 
     protected void init(DeviceId deviceId) {
-        srManager.routeService.getRouteTables().forEach(routeTableId ->
-            srManager.routeService.getRoutes(routeTableId).forEach(routeInfo ->
-                routeInfo.allRoutes().forEach(resolvedRoute ->
-                    srManager.nextHopLocations(resolvedRoute).stream()
-                            .filter(location -> deviceId.equals(location.deviceId()))
-                            .forEach(location -> processRouteAddedInternal(resolvedRoute)
-                    )
-                )
-            )
-        );
+        Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(deviceId);
+
+        srManager.routeService.getRouteTables().stream()
+                .map(srManager.routeService::getRoutes)
+                .flatMap(Collection::stream)
+                .map(RouteInfo::allRoutes)
+                .filter(allRoutes -> allRoutes.stream().allMatch(resolvedRoute ->
+                        srManager.nextHopLocations(resolvedRoute).stream().allMatch(cp ->
+                            deviceId.equals(cp.deviceId()) ||
+                                    (pairDeviceId.isPresent() && pairDeviceId.get().equals(cp.deviceId()))
+                        )))
+                .forEach(this::processRouteAddedInternal);
     }
 
     void processRouteAdded(RouteEvent event) {
         enqueueRouteEvent(event);
     }
 
-    private void processRouteAddedInternal(ResolvedRoute route) {
-        processRouteAddedInternal(Sets.newHashSet(route));
-    }
-
     private void processRouteAddedInternal(Collection<ResolvedRoute> routes) {
         if (!isReady()) {
             log.info("System is not ready. Skip adding route for {}", routes);
diff --git a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockRouteService.java b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockRouteService.java
index 7927636..bfeca1f 100644
--- a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockRouteService.java
+++ b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockRouteService.java
@@ -17,18 +17,15 @@
 package org.onosproject.segmentrouting;
 
 import com.google.common.collect.Sets;
-import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
-import org.onlab.packet.MacAddress;
-import org.onlab.packet.VlanId;
 import org.onosproject.routeservice.ResolvedRoute;
-import org.onosproject.routeservice.Route;
 import org.onosproject.routeservice.RouteInfo;
 import org.onosproject.routeservice.RouteServiceAdapter;
 import org.onosproject.routeservice.RouteTableId;
 
 import java.util.Collection;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -36,23 +33,19 @@
  * We assume there is only one routing table named "default".
  */
 public class MockRouteService extends RouteServiceAdapter {
-    private Map<MockRoutingTableKey, MockRoutingTableValue> routingTable;
+    private Map<IpPrefix, Set<ResolvedRoute>> routeStore;
 
-    MockRouteService(Map<MockRoutingTableKey, MockRoutingTableValue> routingTable) {
-        this.routingTable = routingTable;
+    MockRouteService(Map<IpPrefix, Set<ResolvedRoute>> routeStore) {
+        this.routeStore = routeStore;
     }
 
     @Override
     public Collection<RouteInfo> getRoutes(RouteTableId id) {
-        return routingTable.entrySet().stream().map(e -> {
-            IpPrefix prefix = e.getKey().ipPrefix;
-            IpAddress nextHop = IpAddress.valueOf(0); // dummy
-            MacAddress mac = e.getValue().macAddress;
-            VlanId vlan = e.getValue().vlanId;
-            Route route = new Route(Route.Source.STATIC, prefix, nextHop);
-            ResolvedRoute rr = new ResolvedRoute(route, mac, vlan);
-
-            return new RouteInfo(prefix, rr, Sets.newHashSet(rr));
+        return routeStore.entrySet().stream().map(e -> {
+            IpPrefix prefix = e.getKey();
+            Set<ResolvedRoute> resolvedRoutes = e.getValue();
+            ResolvedRoute bestRoute =  resolvedRoutes.stream().findFirst().orElse(null);
+            return new RouteInfo(prefix, bestRoute, resolvedRoutes);
         }).collect(Collectors.toSet());
     }
 
diff --git a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/RouteHandlerTest.java b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/RouteHandlerTest.java
index ef4c2aa..bdf056e 100644
--- a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/RouteHandlerTest.java
+++ b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/RouteHandlerTest.java
@@ -75,6 +75,7 @@
     private static final Map<ConnectPoint, Set<IpPrefix>> SUBNET_TABLE = Maps.newConcurrentMap();
     // Mocked Next Id
     private static final Map<Integer, TrafficTreatment> NEXT_TABLE = Maps.newConcurrentMap();
+    private static final Map<IpPrefix, Set<ResolvedRoute>> ROUTE_STORE = Maps.newConcurrentMap();
 
     private static final IpPrefix P1 = IpPrefix.valueOf("10.0.0.0/24");
 
@@ -167,7 +168,7 @@
         hostService = new MockHostService(HOSTS);
         srManager.hostService = hostService;
         srManager.cfgService = mockNetworkConfigRegistry;
-        srManager.routeService = new MockRouteService(ROUTING_TABLE);
+        srManager.routeService = new MockRouteService(ROUTE_STORE);
 
         routeHandler = new RouteHandler(srManager) {
             // routeEventCache is not necessary for unit tests
@@ -184,9 +185,7 @@
 
     @Test
     public void init() {
-        MockRoutingTableKey rtk = new MockRoutingTableKey(CP1.deviceId(), P1);
-        MockRoutingTableValue rtv = new MockRoutingTableValue(CP1.port(), M1, V1);
-        ROUTING_TABLE.put(rtk, rtv);
+        ROUTE_STORE.put(P1, Sets.newHashSet(RR1));
 
         routeHandler.init(CP1.deviceId());
 
@@ -201,6 +200,27 @@
     }
 
     @Test
+    public void initTwoNextHops() {
+        ROUTE_STORE.put(P1, Sets.newHashSet(RR1, RR2));
+
+        routeHandler.init(CP1.deviceId());
+
+        assertEquals(2, ROUTING_TABLE.size());
+        MockRoutingTableValue rtv1 = ROUTING_TABLE.get(new MockRoutingTableKey(CP1.deviceId(), P1));
+        assertEquals(M1, rtv1.macAddress);
+        assertEquals(V1, rtv1.vlanId);
+        assertEquals(CP1.port(), rtv1.portNumber);
+        MockRoutingTableValue rtv2 = ROUTING_TABLE.get(new MockRoutingTableKey(CP2.deviceId(), P1));
+        assertEquals(M2, rtv2.macAddress);
+        assertEquals(V2, rtv2.vlanId);
+        assertEquals(CP2.port(), rtv2.portNumber);
+
+        assertEquals(2, SUBNET_TABLE.size());
+        assertTrue(SUBNET_TABLE.get(CP1).contains(P1));
+        assertTrue(SUBNET_TABLE.get(CP2).contains(P1));
+    }
+
+    @Test
     public void processRouteAdded() {
         reset(srManager.deviceConfiguration);
         srManager.deviceConfiguration.addSubnet(CP1, P1);
@@ -390,6 +410,8 @@
     public void testDualHomedSingleLocationFail() {
         testOneDualHomedAdded();
 
+        ROUTE_STORE.put(P1, Sets.newHashSet(RR3));
+
         HostEvent he = new HostEvent(HostEvent.Type.HOST_MOVED, H3S, H3D);
         routeHandler.processHostMovedEvent(he);