[AETHER-72] Refactoring RouteService

- to use bulk updates interface
- to use new getRoutesForNextHops API
- to use multi-thread resolver
- to use multi-thread hostexec
- to use a concurrent hashmap instead of synchronized
- to use a non-blocking resolved store

Additionally updates unit tests

Change-Id: Id960abd0f2a1b03066ce34b6a2f72b76566bb58c
diff --git a/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/DefaultRouteTable.java b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/DefaultRouteTable.java
index f052f04..c24f005 100644
--- a/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/DefaultRouteTable.java
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/DefaultRouteTable.java
@@ -18,6 +18,7 @@
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.ExecutorService;
@@ -43,6 +44,7 @@
 import org.onosproject.store.service.StorageService;
 import org.onosproject.store.service.Versioned;
 
+
 import static com.google.common.base.MoreObjects.toStringHelper;
 import static com.google.common.base.Preconditions.checkNotNull;
 
@@ -133,6 +135,14 @@
     }
 
     @Override
+    public void update(Collection<Route> routesAdded) {
+        Map<String, Collection<? extends RawRoute>> computedRoutes = new HashMap<>();
+        computeRoutesToAdd(routesAdded).forEach((prefix, routes) -> computedRoutes.computeIfAbsent(
+                prefix, k -> Sets.newHashSet(routes)));
+        routes.putAll(computedRoutes);
+    }
+
+    @Override
     public void remove(Route route) {
         getRoutes(route.prefix())
             .routes()
@@ -145,6 +155,14 @@
     }
 
     @Override
+    public void remove(Collection<Route> routesRemoved) {
+        Map<String, Collection<? extends RawRoute>> computedRoutes = new HashMap<>();
+        computeRoutesToRemove(routesRemoved).forEach((prefix, routes) -> computedRoutes.computeIfAbsent(
+                prefix, k -> Sets.newHashSet(routes)));
+        routes.removeAll(computedRoutes);
+    }
+
+    @Override
     public void replace(Route route) {
         routes.replaceValues(route.prefix().toString(), Sets.newHashSet(new RawRoute(route)));
     }
@@ -180,6 +198,53 @@
             .collect(Collectors.toSet());
     }
 
+    @Override
+    public Collection<RouteSet> getRoutesForNextHops(Collection<IpAddress> nextHops) {
+        // First create a reduced snapshot of the store iterating one time the map
+        Map<String, Collection<? extends RawRoute>> filteredRouteStore = new HashMap<>();
+        routes.values().stream()
+                .filter(r -> nextHops.contains(IpAddress.valueOf(r.nextHop())))
+                .forEach(r -> filteredRouteStore.computeIfAbsent(r.prefix, k -> {
+                    // We need to get all the routes because the resolve logic
+                    // will use the alternatives as well
+                    Versioned<Collection<? extends RawRoute>> routeSet = routes.get(k);
+                    if (routeSet != null) {
+                        return routeSet.value();
+                    }
+                    return null;
+                }));
+        // Return the collection of the routeSet we have to resolve
+        return filteredRouteStore.entrySet().stream()
+                .map(entry -> new RouteSet(id, IpPrefix.valueOf(entry.getKey()),
+                                           entry.getValue().stream().map(RawRoute::route).collect(Collectors.toSet())))
+                .collect(Collectors.toSet());
+    }
+
+    private Map<String, Collection<RawRoute>> computeRoutesToAdd(Collection<Route> routesAdded) {
+        Map<String, Collection<RawRoute>> computedRoutes = new HashMap<>();
+        routesAdded.forEach(route -> {
+            Collection<RawRoute> tempRoutes = computedRoutes.computeIfAbsent(
+                    route.prefix().toString(), k -> Sets.newHashSet());
+            tempRoutes.add(new RawRoute(route));
+        });
+        return computedRoutes;
+    }
+
+    private Map<String, Collection<RawRoute>> computeRoutesToRemove(Collection<Route> routesRemoved) {
+        Map<String, Collection<RawRoute>> computedRoutes = new HashMap<>();
+        routesRemoved.forEach(route -> getRoutes(route.prefix())
+                .routes()
+                .stream()
+                .filter(r -> r.equals(route))
+                .findAny()
+                .ifPresent(matchRoute -> {
+                    Collection<RawRoute> tempRoutes = computedRoutes.computeIfAbsent(
+                            matchRoute.prefix().toString(), k -> Sets.newHashSet());
+                    tempRoutes.add(new RawRoute(matchRoute));
+                }));
+        return computedRoutes;
+    }
+
     private class RouteTableListener
             implements MultimapEventListener<String, RawRoute> {
 
diff --git a/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/DistributedRouteStore.java b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/DistributedRouteStore.java
index efdb792..f2661a1 100644
--- a/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/DistributedRouteStore.java
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/DistributedRouteStore.java
@@ -17,6 +17,7 @@
 package org.onosproject.routeservice.store;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
 import org.onlab.util.KryoNamespace;
@@ -37,11 +38,13 @@
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
 
 import static org.onlab.util.Tools.groupedThreads;
 
@@ -116,11 +119,27 @@
     }
 
     @Override
+    public void updateRoutes(Collection<Route> routes) {
+        Map<RouteTableId, Set<Route>> computedTables = computeRouteTablesFromRoutes(routes);
+        computedTables.forEach(
+                ((routeTableId, routesToAdd) -> getDefaultRouteTable(routeTableId).update(routesToAdd))
+        );
+    }
+
+    @Override
     public void removeRoute(Route route) {
         getDefaultRouteTable(route).remove(route);
     }
 
     @Override
+    public void removeRoutes(Collection<Route> routes) {
+        Map<RouteTableId, Set<Route>> computedTables = computeRouteTablesFromRoutes(routes);
+        computedTables.forEach(
+                ((routeTableId, routesToRemove) -> getDefaultRouteTable(routeTableId).remove(routesToRemove))
+        );
+    }
+
+    @Override
     public void replaceRoute(Route route) {
         getDefaultRouteTable(route).replace(route);
     }
@@ -146,6 +165,15 @@
     }
 
     @Override
+    public Collection<RouteSet> getRoutesForNextHops(Collection<IpAddress> nextHops) {
+        Map<RouteTableId, Set<IpAddress>> computedTables = computeRouteTablesFromIps(nextHops);
+        return computedTables.entrySet().stream()
+                .map(entry -> getDefaultRouteTable(entry.getKey()).getRoutesForNextHops(entry.getValue()))
+                .flatMap(Collection::stream)
+                .collect(Collectors.toList());
+    }
+
+    @Override
     public RouteSet getRoutes(IpPrefix prefix) {
         return getDefaultRouteTable(prefix.address()).getRoutes(prefix);
     }
@@ -170,6 +198,30 @@
         return routeTables.getOrDefault(routeTableId, EmptyRouteTable.instance());
     }
 
+    private RouteTable getDefaultRouteTable(RouteTableId routeTableId) {
+        return routeTables.getOrDefault(routeTableId, EmptyRouteTable.instance());
+    }
+
+    private Map<RouteTableId, Set<Route>> computeRouteTablesFromRoutes(Collection<Route> routes) {
+        Map<RouteTableId, Set<Route>> computedTables = new HashMap<>();
+        routes.forEach(route -> {
+            RouteTableId routeTableId = (route.prefix().address().isIp4()) ? IPV4 : IPV6;
+            Set<Route> tempRoutes = computedTables.computeIfAbsent(routeTableId, k -> Sets.newHashSet());
+            tempRoutes.add(route);
+        });
+        return computedTables;
+    }
+
+    private Map<RouteTableId, Set<IpAddress>> computeRouteTablesFromIps(Collection<IpAddress> ipAddresses) {
+        Map<RouteTableId, Set<IpAddress>> computedTables = new HashMap<>();
+        ipAddresses.forEach(ipAddress -> {
+            RouteTableId routeTableId = (ipAddress.isIp4()) ? IPV4 : IPV6;
+            Set<IpAddress> tempIpAddresses = computedTables.computeIfAbsent(routeTableId, k -> Sets.newHashSet());
+            tempIpAddresses.add(ipAddress);
+        });
+        return computedTables;
+    }
+
     private class InternalRouteStoreDelegate implements RouteStoreDelegate {
         @Override
         public void notify(InternalRouteEvent event) {
diff --git a/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/EmptyRouteTable.java b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/EmptyRouteTable.java
index 8bee10e..9201c1a 100644
--- a/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/EmptyRouteTable.java
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/EmptyRouteTable.java
@@ -52,11 +52,21 @@
     }
 
     @Override
+    public void update(Collection<Route> routes) {
+
+    }
+
+    @Override
     public void remove(Route route) {
 
     }
 
     @Override
+    public void remove(Collection<Route> routes) {
+
+    }
+
+    @Override
     public void replace(Route route) {
 
     }
@@ -82,6 +92,11 @@
     }
 
     @Override
+    public Collection<RouteSet> getRoutesForNextHops(Collection<IpAddress> nextHops) {
+        return Collections.emptyList();
+    }
+
+    @Override
     public void shutdown() {
 
     }
diff --git a/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/LocalRouteStore.java b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/LocalRouteStore.java
index 04b0f38..c286ab1 100644
--- a/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/LocalRouteStore.java
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/LocalRouteStore.java
@@ -16,6 +16,7 @@
 
 package org.onosproject.routeservice.store;
 
+import com.google.common.collect.Sets;
 import com.googlecode.concurrenttrees.common.KeyValuePair;
 import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
 import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
@@ -35,6 +36,7 @@
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -82,11 +84,27 @@
     }
 
     @Override
+    public void updateRoutes(Collection<Route> routes) {
+        Map<RouteTableId, Set<Route>> computedTables = computeRouteTablesFromRoutes(routes);
+        computedTables.forEach(
+                ((routeTableId, routesToAdd) -> getDefaultRouteTable(routeTableId).update(routesToAdd))
+        );
+    }
+
+    @Override
     public void removeRoute(Route route) {
         getDefaultRouteTable(route).remove(route);
     }
 
     @Override
+    public void removeRoutes(Collection<Route> routes) {
+        Map<RouteTableId, Set<Route>> computedTables = computeRouteTablesFromRoutes(routes);
+        computedTables.forEach(
+                ((routeTableId, routesToRemove) -> getDefaultRouteTable(routeTableId).remove(routesToRemove))
+        );
+    }
+
+    @Override
     public void replaceRoute(Route route) {
         getDefaultRouteTable(route).replace(route);
     }
@@ -111,6 +129,15 @@
     }
 
     @Override
+    public Collection<RouteSet> getRoutesForNextHops(Collection<IpAddress> nextHops) {
+        Map<RouteTableId, Set<IpAddress>> computedTables = computeRouteTablesFromIps(nextHops);
+        return computedTables.entrySet().stream()
+                .map(entry -> getDefaultRouteTable(entry.getKey()).getRoutesForNextHops(entry.getValue()))
+                .flatMap(Collection::stream)
+                .collect(Collectors.toList());
+    }
+
+    @Override
     public RouteSet getRoutes(IpPrefix prefix) {
         return getDefaultRouteTable(prefix.address()).getRoutes(prefix);
     }
@@ -124,6 +151,30 @@
         return routeTables.get(routeTableId);
     }
 
+    private RouteTable getDefaultRouteTable(RouteTableId routeTableId) {
+        return routeTables.get(routeTableId);
+    }
+
+    private Map<RouteTableId, Set<Route>> computeRouteTablesFromRoutes(Collection<Route> routes) {
+        Map<RouteTableId, Set<Route>> computedTables = new HashMap<>();
+        routes.forEach(route -> {
+            RouteTableId routeTableId = (route.prefix().address().isIp4()) ? IPV4 : IPV6;
+            Set<Route> tempRoutes = computedTables.computeIfAbsent(routeTableId, k -> Sets.newHashSet());
+            tempRoutes.add(route);
+        });
+        return computedTables;
+    }
+
+    private Map<RouteTableId, Set<IpAddress>> computeRouteTablesFromIps(Collection<IpAddress> ipAddresses) {
+        Map<RouteTableId, Set<IpAddress>> computedTables = new HashMap<>();
+        ipAddresses.forEach(ipAddress -> {
+            RouteTableId routeTableId = (ipAddress.isIp4()) ? IPV4 : IPV6;
+            Set<IpAddress> tempIpAddresses = computedTables.computeIfAbsent(routeTableId, k -> Sets.newHashSet());
+            tempIpAddresses.add(ipAddress);
+        });
+        return computedTables;
+    }
+
     /**
      * Route table into which routes can be placed.
      */
@@ -163,6 +214,17 @@
         }
 
         /**
+         * Adds or updates the routes in the route table.
+         *
+         * @param routes routes to update
+         */
+        public void update(Collection<Route> routes) {
+            synchronized (this) {
+                routes.forEach(this::update);
+            }
+        }
+
+        /**
          * Removes the route from the route table.
          *
          * @param route route to remove
@@ -180,6 +242,17 @@
         }
 
         /**
+         * Adds or updates the routes in the route table.
+         *
+         * @param routes routes to update
+         */
+        public void remove(Collection<Route> routes) {
+            synchronized (this) {
+                routes.forEach(this::remove);
+            }
+        }
+
+        /**
          * Replace the route in the route table.
          */
         public void replace(Route route) {
@@ -199,6 +272,28 @@
                     .collect(Collectors.toSet());
         }
 
+        /**
+         * Returns the routes pointing to the next hops.
+         *
+         * @param ips next hops IP addresses
+         * @return routes for the next hop
+         */
+        public Collection<RouteSet> getRoutesForNextHops(Collection<IpAddress> ips) {
+            // First create a reduced snapshot of the store iterating one time the map
+            Map<IpPrefix, Set<Route>> filteredRouteStore = new HashMap<>();
+            routes.values().stream()
+                    .filter(r -> ips.contains(r.nextHop()))
+                    .forEach(r -> {
+                        Collection<Route> tempRoutes = filteredRouteStore.computeIfAbsent(
+                                r.prefix(), k -> Sets.newHashSet());
+                        tempRoutes.add(r);
+                    });
+            // Return the collection of the routeSet we have to resolve
+            return filteredRouteStore.entrySet().stream()
+                    .map(entry -> new RouteSet(id, entry.getKey(), entry.getValue()))
+                    .collect(Collectors.toSet());
+        }
+
         public RouteSet getRoutes(IpPrefix prefix) {
             Route route = routes.get(prefix);
             if (route != null) {
diff --git a/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/RouteStoreImpl.java b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/RouteStoreImpl.java
index edd22c3..f5c9d04 100644
--- a/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/RouteStoreImpl.java
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/RouteStoreImpl.java
@@ -141,11 +141,21 @@
     }
 
     @Override
+    public void updateRoutes(Collection<Route> routes) {
+        currentRouteStore.updateRoutes(routes);
+    }
+
+    @Override
     public void removeRoute(Route route) {
         currentRouteStore.removeRoute(route);
     }
 
     @Override
+    public void removeRoutes(Collection<Route> routes) {
+        currentRouteStore.removeRoutes(routes);
+    }
+
+    @Override
     public void replaceRoute(Route route) {
         currentRouteStore.replaceRoute(route);
     }
@@ -166,6 +176,11 @@
     }
 
     @Override
+    public Collection<RouteSet> getRoutesForNextHops(Collection<IpAddress> ips) {
+        return currentRouteStore.getRoutesForNextHops(ips);
+    }
+
+    @Override
     public RouteSet getRoutes(IpPrefix prefix) {
         return currentRouteStore.getRoutes(prefix);
     }
diff --git a/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/RouteTable.java b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/RouteTable.java
index 130654b..fddd116 100644
--- a/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/RouteTable.java
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/RouteTable.java
@@ -37,6 +37,13 @@
     void update(Route route);
 
     /**
+     * Adds the routes to the route table.
+     *
+     * @param routes routes
+     */
+    void update(Collection<Route> routes);
+
+    /**
      * Removes a route from the route table.
      *
      * @param route route
@@ -44,6 +51,13 @@
     void remove(Route route);
 
     /**
+     * Removes the routes from the route table.
+     *
+     * @param routes routes
+     */
+    void remove(Collection<Route> routes);
+
+    /**
      * Replaces a route in the route table.
      *
      * @param route route
@@ -81,6 +95,14 @@
     Collection<Route> getRoutesForNextHop(IpAddress nextHop);
 
     /**
+     * Returns all routes that have the given next hops.
+     *
+     * @param nextHops next hops IP addresses
+     * @return collection of routes sets
+     */
+    Collection<RouteSet> getRoutesForNextHops(Collection<IpAddress> nextHops);
+
+    /**
      * Releases route table resources held locally.
      */
     void shutdown();