Implementation of the route service

Change-Id: I4e905cf868ad69c426e4f4155dfd83e1f8b00335
diff --git a/cli/src/main/java/org/onosproject/cli/net/RouteAddCommand.java b/cli/src/main/java/org/onosproject/cli/net/RouteAddCommand.java
new file mode 100644
index 0000000..d2d718f
--- /dev/null
+++ b/cli/src/main/java/org/onosproject/cli/net/RouteAddCommand.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016 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.cli.net;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.routing.Route;
+import org.onosproject.incubator.net.routing.RouteAdminService;
+
+import java.util.Collections;
+
+/**
+ * Command to add a route to the routing table.
+ */
+@Command(scope = "onos", name = "route-add",
+        description = "Adds a route to the route table")
+public class RouteAddCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "prefix", description = "IP prefix of the route",
+            required = true)
+    String prefixString = null;
+
+    @Argument(index = 1, name = "nextHop", description = "IP address of the next hop",
+            required = true)
+    String nextHopString = null;
+
+    @Override
+    protected void execute() {
+        RouteAdminService service = AbstractShellCommand.get(RouteAdminService.class);
+
+        IpPrefix prefix = IpPrefix.valueOf(prefixString);
+        IpAddress nextHop = IpAddress.valueOf(nextHopString);
+
+        service.update(Collections.singleton(new Route(Route.Source.STATIC, prefix, nextHop)));
+    }
+
+}
diff --git a/cli/src/main/java/org/onosproject/cli/net/RouteRemoveCommand.java b/cli/src/main/java/org/onosproject/cli/net/RouteRemoveCommand.java
new file mode 100644
index 0000000..3684743
--- /dev/null
+++ b/cli/src/main/java/org/onosproject/cli/net/RouteRemoveCommand.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016 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.cli.net;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.routing.Route;
+import org.onosproject.incubator.net.routing.RouteAdminService;
+
+import java.util.Collections;
+
+/**
+ * Command to remove a route from the routing table.
+ */
+@Command(scope = "onos", name = "route-remove",
+        description = "Removes a route from the route table")
+public class RouteRemoveCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "prefix", description = "IP prefix of the route",
+            required = true)
+    String prefixString = null;
+
+    @Override
+    protected void execute() {
+        RouteAdminService service = AbstractShellCommand.get(RouteAdminService.class);
+
+        IpPrefix prefix = IpPrefix.valueOf(prefixString);
+
+        service.withdraw(Collections.singleton(new Route(Route.Source.STATIC, prefix, null)));
+    }
+
+}
diff --git a/cli/src/main/java/org/onosproject/cli/net/RoutesListCommand.java b/cli/src/main/java/org/onosproject/cli/net/RoutesListCommand.java
new file mode 100644
index 0000000..b2f235a
--- /dev/null
+++ b/cli/src/main/java/org/onosproject/cli/net/RoutesListCommand.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2016 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.cli.net;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.routing.Route;
+import org.onosproject.incubator.net.routing.RouteService;
+import org.onosproject.incubator.net.routing.RouteTableId;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Command to show the routes in the routing tables.
+ */
+// TODO update command name when we switch over to new rib
+@Command(scope = "onos", name = "routes2",
+        description = "Lists all routes in the route store")
+public class RoutesListCommand extends AbstractShellCommand {
+
+    private static final String FORMAT_HEADER =
+        "   Network            Next Hop";
+    private static final String FORMAT_ROUTE =
+        "   %-18s %-15s";
+
+    private static final String FORMAT_TABLE = "Table: %s";
+    private static final String FORMAT_TOTAL = "   Total: %d";
+
+    @Override
+    protected void execute() {
+        RouteService service = AbstractShellCommand.get(RouteService.class);
+
+        Map<RouteTableId, Collection<Route>> allRoutes = service.getAllRoutes();
+
+        allRoutes.forEach((id, routes) -> {
+            print(FORMAT_TABLE, id);
+            print(FORMAT_HEADER);
+            routes.forEach(r -> print(FORMAT_ROUTE, r.prefix(), r.nextHop()));
+            print(FORMAT_TOTAL, routes.size());
+            print("");
+        });
+
+    }
+
+}
diff --git a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index a6771eb..6e48bde 100644
--- a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -497,6 +497,16 @@
         </command>
 
         <command>
+            <action class="org.onosproject.cli.net.RoutesListCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.cli.net.RouteAddCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.cli.net.RouteRemoveCommand"/>
+        </command>
+
+        <command>
             <action class="org.onosproject.cli.net.GlobalLabelCommand"/>
         </command>
         <command>
diff --git a/core/api/src/main/java/org/onosproject/net/host/HostStoreDelegate.java b/core/api/src/main/java/org/onosproject/net/host/HostStoreDelegate.java
index efc8423..d028fa4 100644
--- a/core/api/src/main/java/org/onosproject/net/host/HostStoreDelegate.java
+++ b/core/api/src/main/java/org/onosproject/net/host/HostStoreDelegate.java
@@ -18,7 +18,7 @@
 import org.onosproject.store.StoreDelegate;
 
 /**
- * Infrastructure link store delegate abstraction.
+ * Host store delegate abstraction.
  */
 public interface HostStoreDelegate extends StoreDelegate<HostEvent> {
 }
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/routing/Route.java b/incubator/api/src/main/java/org/onosproject/incubator/net/routing/Route.java
index 857d69b..590b218 100644
--- a/incubator/api/src/main/java/org/onosproject/incubator/net/routing/Route.java
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/routing/Route.java
@@ -70,8 +70,7 @@
      */
     public Route(Source source, IpPrefix prefix, IpAddress nextHop) {
         checkNotNull(prefix);
-        checkNotNull(nextHop);
-        checkArgument(prefix.version().equals(nextHop.version()), VERSION_MISMATCH);
+        checkArgument(nextHop == null || prefix.version().equals(nextHop.version()), VERSION_MISMATCH);
 
         this.source = checkNotNull(source);
         this.prefix = prefix;
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteAdminService.java b/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteAdminService.java
new file mode 100644
index 0000000..da579c5
--- /dev/null
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteAdminService.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016 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.incubator.net.routing;
+
+import java.util.Collection;
+
+/**
+ * Service allowing mutation of unicast routing state.
+ */
+public interface RouteAdminService extends RouteService {
+
+    /**
+     * Updates the given routes in the route service.
+     *
+     * @param routes collection of routes to update
+     */
+    void update(Collection<Route> routes);
+
+    /**
+     * Withdraws the given routes from the route service.
+     *
+     * @param routes collection of routes to withdraw
+     */
+    void withdraw(Collection<Route> routes);
+}
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteService.java b/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteService.java
index 3fd9e69..d4d6892 100644
--- a/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteService.java
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteService.java
@@ -20,47 +20,28 @@
 import org.onosproject.event.ListenerService;
 
 import java.util.Collection;
+import java.util.Map;
 
 /**
- * IP unicast routing service.
+ * Unicast IP route service.
  */
 public interface RouteService extends ListenerService<RouteEvent, RouteListener> {
 
     /**
-     * Gets all routes.
+     * Returns all routes for all route tables in the system.
      *
-     * @return collection of all routes
+     * @return map of route table name to routes in that table
      */
-    Collection<Route> getRoutes();
+    Map<RouteTableId, Collection<Route>> getAllRoutes();
 
     /**
-     * Gets routes learnt from a particular source.
+     * Performs a longest prefix match on the given IP address. The call will
+     * return the route with the most specific prefix that contains the given
+     * IP address.
      *
-     * @param source route source
-     * @return collection of routes
-     */
-    Collection<Route> getRoutesFromSource(Route.Source source);
-
-    /**
-     * Gets the longest prefix route that matches the given IP address.
-     *
-     * @param ip IP addres
+     * @param ip IP address
      * @return longest prefix matched route
      */
     Route longestPrefixMatch(IpAddress ip);
 
-    //TODO should mutation methods be pushed to a different interface?
-    /**
-     * Updates the given routes in the route service.
-     *
-     * @param routes collection of routes to update
-     */
-    void update(Collection<Route> routes);
-
-    /**
-     * Withdraws the given routes from the route service.
-     *
-     * @param routes collection of routes to withdraw
-     */
-    void withdraw(Collection<Route> routes);
 }
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteStore.java b/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteStore.java
new file mode 100644
index 0000000..1905d1b
--- /dev/null
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteStore.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016 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.incubator.net.routing;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onosproject.store.Store;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Unicast route store.
+ */
+public interface RouteStore extends Store<RouteEvent, RouteStoreDelegate> {
+
+    /**
+     * Adds or updates the given route in the store.
+     *
+     * @param route route to add or update
+     */
+    void updateRoute(Route route);
+
+    /**
+     * Removes the given route from the store.
+     *
+     * @param route route to remove
+     */
+    void removeRoute(Route route);
+
+    /**
+     * Returns the IDs for all route tables in the store.
+     *
+     * @return route table IDs
+     */
+    Set<RouteTableId> getRouteTables();
+
+    /**
+     * Returns the routes for a particular route table.
+     *
+     * @param table route table
+     * @return collection of route in the table
+     */
+    Collection<Route> getRoutes(RouteTableId table);
+
+    /**
+     * Performs a longest prefix match with the given IP address.
+     *
+     * @param ip IP to look up
+     * @return longest prefix match route
+     */
+    Route longestPrefixMatch(IpAddress ip);
+
+    /**
+     * Updates a next hop IP and MAC in the store.
+     *
+     * @param ip IP address
+     * @param mac MAC address
+     */
+    void updateNextHop(IpAddress ip, MacAddress mac);
+
+    /**
+     * Removes a next hop IP and MAC from the store.
+     *
+     * @param ip IP address
+     * @param mac MAC address
+     */
+    void removeNextHop(IpAddress ip, MacAddress mac);
+
+    /**
+     * Returns the MAC address of the given next hop.
+     *
+     * @param ip next hop IP
+     * @return MAC address
+     */
+    MacAddress getNextHop(IpAddress ip);
+}
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteStoreDelegate.java b/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteStoreDelegate.java
new file mode 100644
index 0000000..b79b483
--- /dev/null
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteStoreDelegate.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016 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.incubator.net.routing;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Route store delegate abstraction.
+ */
+public interface RouteStoreDelegate extends StoreDelegate<RouteEvent> {
+}
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteTableId.java b/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteTableId.java
new file mode 100644
index 0000000..1a8bccb
--- /dev/null
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteTableId.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016 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.incubator.net.routing;
+
+import java.util.Objects;
+
+/**
+ * Identifier for a routing table.
+ */
+public class RouteTableId {
+    private final String name;
+
+    /**
+     * Creates a new route table ID.
+     *
+     * @param name unique name for the route table
+     */
+    public RouteTableId(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Returns the name of the route table.
+     *
+     * @return table name
+     */
+    public String name() {
+        return name;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj instanceof RouteTableId) {
+            RouteTableId that = (RouteTableId) obj;
+
+            return Objects.equals(this.name, that.name);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(name);
+    }
+
+    @Override
+    public String toString() {
+        return name;
+    }
+}
diff --git a/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/RouteManager.java b/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/RouteManager.java
new file mode 100644
index 0000000..0c89f94
--- /dev/null
+++ b/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/RouteManager.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2016 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.incubator.net.routing.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onosproject.event.ListenerService;
+import org.onosproject.incubator.net.routing.ResolvedRoute;
+import org.onosproject.incubator.net.routing.Route;
+import org.onosproject.incubator.net.routing.RouteAdminService;
+import org.onosproject.incubator.net.routing.RouteEvent;
+import org.onosproject.incubator.net.routing.RouteListener;
+import org.onosproject.incubator.net.routing.RouteService;
+import org.onosproject.incubator.net.routing.RouteStore;
+import org.onosproject.incubator.net.routing.RouteStoreDelegate;
+import org.onosproject.incubator.net.routing.RouteTableId;
+import org.onosproject.net.Host;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.concurrent.GuardedBy;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+
+/**
+ * Implementation of the unicast route service.
+ */
+@Service
+@Component
+public class RouteManager implements ListenerService<RouteEvent, RouteListener>,
+        RouteService, RouteAdminService {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private RouteStoreDelegate delegate = new InternalRouteStoreDelegate();
+    private InternalHostListener hostListener = new InternalHostListener();
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected RouteStore routeStore;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @GuardedBy(value = "this")
+    private Map<RouteListener, ListenerQueue> listeners = new HashMap<>();
+
+    private ThreadFactory threadFactory;
+
+    @Activate
+    protected void activate() {
+        threadFactory = groupedThreads("onos/route", "listener-%d");
+
+        routeStore.setDelegate(delegate);
+        hostService.addListener(hostListener);
+
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        listeners.values().forEach(l -> l.stop());
+
+        routeStore.unsetDelegate(delegate);
+        hostService.removeListener(hostListener);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * In a departure from other services in ONOS, calling addListener will
+     * cause all current routes to be pushed to the listener before any new
+     * events are sent. This allows a listener to easily get the exact set of
+     * routes without worrying about missing any.
+     *
+     * @param listener listener to be added
+     */
+    @Override
+    public void addListener(RouteListener listener) {
+        synchronized (this) {
+            log.debug("Synchronizing current routes to new listener");
+            ListenerQueue l = new ListenerQueue(listener);
+            routeStore.getRouteTables().forEach(table -> {
+                Collection<Route> routes = routeStore.getRoutes(table);
+                if (routes != null) {
+                    routes.forEach(route ->
+                        l.post(new RouteEvent(RouteEvent.Type.ROUTE_UPDATED,
+                                        new ResolvedRoute(route, routeStore.getNextHop(route.nextHop())))));
+                }
+            });
+
+            listeners.put(listener, l);
+
+            l.start();
+            log.debug("Route synchronization complete");
+        }
+    }
+
+    @Override
+    public void removeListener(RouteListener listener) {
+        synchronized (this) {
+            ListenerQueue l = listeners.remove(listener);
+            if (l != null) {
+                l.stop();
+            }
+        }
+    }
+
+    /**
+     * Posts an event to all listeners.
+     *
+     * @param event event
+     */
+    private void post(RouteEvent event) {
+        synchronized (this) {
+            listeners.values().forEach(l -> l.post(event));
+        }
+    }
+
+    @Override
+    public Map<RouteTableId, Collection<Route>> getAllRoutes() {
+        return routeStore.getRouteTables().stream()
+                .collect(Collectors.toMap(Function.identity(),
+                        table -> (table == null) ?
+                                 Collections.emptySet() : routeStore.getRoutes(table)));
+    }
+
+    @Override
+    public Route longestPrefixMatch(IpAddress ip) {
+        return routeStore.longestPrefixMatch(ip);
+    }
+
+    @Override
+    public void update(Collection<Route> routes) {
+        synchronized (this) {
+            routes.forEach(route -> {
+                routeStore.updateRoute(route);
+                resolve(route);
+            });
+        }
+    }
+
+    @Override
+    public void withdraw(Collection<Route> routes) {
+        synchronized (this) {
+            routes.forEach(route -> routeStore.removeRoute(route));
+        }
+    }
+
+    private void resolve(Route route) {
+        // Monitor the IP address for updates of the MAC address
+        hostService.startMonitoringIp(route.nextHop());
+
+        MacAddress nextHopMac = routeStore.getNextHop(route.nextHop());
+        if (nextHopMac == null) {
+            Set<Host> hosts = hostService.getHostsByIp(route.nextHop());
+            Optional<Host> host = hosts.stream().findFirst();
+            if (host.isPresent()) {
+                nextHopMac = host.get().mac();
+            }
+        }
+
+        if (nextHopMac != null) {
+            routeStore.updateNextHop(route.nextHop(), nextHopMac);
+        }
+    }
+
+    private void hostUpdated(Host host) {
+        synchronized (this) {
+            for (IpAddress ip : host.ipAddresses()) {
+                routeStore.updateNextHop(ip, host.mac());
+            }
+        }
+    }
+
+    private void hostRemoved(Host host) {
+        synchronized (this) {
+            for (IpAddress ip : host.ipAddresses()) {
+                routeStore.removeNextHop(ip, host.mac());
+            }
+        }
+    }
+
+    /**
+     * Queues updates for a route listener to ensure they are received in the
+     * correct order.
+     */
+    private class ListenerQueue {
+
+        private final ExecutorService executorService;
+        private final BlockingQueue<RouteEvent> queue;
+        private final RouteListener listener;
+
+        /**
+         * Creates a new listener queue.
+         *
+         * @param listener route listener to queue updates for
+         */
+        public ListenerQueue(RouteListener listener) {
+            this.listener = listener;
+            queue = new LinkedBlockingQueue<>();
+            executorService = newSingleThreadExecutor(threadFactory);
+        }
+
+        /**
+         * Posts and event to the listener.
+         *
+         * @param event event
+         */
+        public void post(RouteEvent event) {
+            queue.add(event);
+        }
+
+        /**
+         * Initiates event delivery to the listener.
+         */
+        public void start() {
+            executorService.execute(this::poll);
+        }
+
+        /**
+         * Halts event delivery to the listener.
+         */
+        public void stop() {
+            executorService.shutdown();
+        }
+
+        private void poll() {
+            try {
+                while (true) {
+                    listener.event(queue.take());
+                }
+            } catch (InterruptedException e) {
+                log.info("Route listener event thread shutting down: {}", e.getMessage());
+            }
+        }
+
+    }
+
+    /**
+     * Delegate to receive events from the route store.
+     */
+    private class InternalRouteStoreDelegate implements RouteStoreDelegate {
+        @Override
+        public void notify(RouteEvent event) {
+            post(event);
+        }
+    }
+
+    /**
+     * Internal listener for host events.
+     */
+    private class InternalHostListener implements HostListener {
+        @Override
+        public void event(HostEvent event) {
+            switch (event.type()) {
+            case HOST_ADDED:
+            case HOST_UPDATED:
+                hostUpdated(event.subject());
+                break;
+            case HOST_REMOVED:
+                hostRemoved(event.subject());
+                break;
+            case HOST_MOVED:
+                break;
+            default:
+                break;
+            }
+        }
+    }
+
+}
diff --git a/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/package-info.java b/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/package-info.java
new file mode 100644
index 0000000..24ae1f4
--- /dev/null
+++ b/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016 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.
+ */
+
+/**
+ * Implementation of route service.
+ */
+package org.onosproject.incubator.net.routing.impl;
diff --git a/incubator/store/pom.xml b/incubator/store/pom.xml
index 4a18d2d..b539997 100644
--- a/incubator/store/pom.xml
+++ b/incubator/store/pom.xml
@@ -53,6 +53,13 @@
             <version>${project.version}</version>
         </dependency>
 
+        <!-- FIXME: not OSGi-ready -->
+        <dependency>
+            <groupId>com.googlecode.concurrent-trees</groupId>
+            <artifactId>concurrent-trees</artifactId>
+            <scope>compile</scope>
+        </dependency>
+
         <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava-testlib</artifactId>
diff --git a/incubator/store/src/main/java/org/onosproject/incubator/store/routing/impl/LocalRouteStore.java b/incubator/store/src/main/java/org/onosproject/incubator/store/routing/impl/LocalRouteStore.java
new file mode 100644
index 0000000..ad60577
--- /dev/null
+++ b/incubator/store/src/main/java/org/onosproject/incubator/store/routing/impl/LocalRouteStore.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2016 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.incubator.store.routing.impl;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
+import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
+import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.incubator.net.routing.ResolvedRoute;
+import org.onosproject.incubator.net.routing.Route;
+import org.onosproject.incubator.net.routing.RouteEvent;
+import org.onosproject.incubator.net.routing.RouteStore;
+import org.onosproject.incubator.net.routing.RouteStoreDelegate;
+import org.onosproject.incubator.net.routing.RouteTableId;
+import org.onosproject.store.AbstractStore;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Route store based on in-memory storage.
+ */
+@Service
+@Component
+public class LocalRouteStore extends AbstractStore<RouteEvent, RouteStoreDelegate>
+        implements RouteStore {
+
+    private Map<RouteTableId, RouteTable> routeTables;
+    private static final RouteTableId IPV4 = new RouteTableId("ipv4");
+    private static final RouteTableId IPV6 = new RouteTableId("ipv6");
+
+    private Map<IpAddress, MacAddress> nextHops = new ConcurrentHashMap<>();
+
+    @Activate
+    protected void activate() {
+        routeTables = new ConcurrentHashMap<>();
+
+        routeTables.put(IPV4, new RouteTable());
+        routeTables.put(IPV6, new RouteTable());
+    }
+
+    @Override
+    public void updateRoute(Route route) {
+        getDefaultRouteTable(route).update(route);
+    }
+
+    @Override
+    public void removeRoute(Route route) {
+        RouteTable table = getDefaultRouteTable(route);
+        table.remove(route);
+        if (table.getRoutesForNextHop(route.nextHop()).isEmpty()) {
+            nextHops.remove(route.nextHop());
+        }
+    }
+
+    @Override
+    public Set<RouteTableId> getRouteTables() {
+        return routeTables.keySet();
+    }
+
+    @Override
+    public Collection<Route> getRoutes(RouteTableId table) {
+        RouteTable routeTable = routeTables.get(table);
+        if (routeTable == null) {
+            return Collections.emptySet();
+        }
+        return routeTable.getRoutes();
+    }
+
+    @Override
+    public Route longestPrefixMatch(IpAddress ip) {
+        return getDefaultRouteTable(ip).longestPrefixMatch(ip);
+    }
+
+    @Override
+    public void updateNextHop(IpAddress ip, MacAddress mac) {
+        Collection<Route> routes = getDefaultRouteTable(ip).getRoutesForNextHop(ip);
+        if (!routes.isEmpty() && !mac.equals(nextHops.get(ip))) {
+            nextHops.put(ip, mac);
+
+            for (Route route : routes) {
+                notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_UPDATED, new ResolvedRoute(route, mac)));
+            }
+        }
+    }
+
+    @Override
+    public void removeNextHop(IpAddress ip, MacAddress mac) {
+        if (nextHops.remove(ip, mac)) {
+            Collection<Route> routes = getDefaultRouteTable(ip).getRoutesForNextHop(ip);
+            for (Route route : routes) {
+                notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_REMOVED, new ResolvedRoute(route, null)));
+            }
+        }
+    }
+
+    @Override
+    public MacAddress getNextHop(IpAddress ip) {
+        return nextHops.get(ip);
+    }
+
+    private RouteTable getDefaultRouteTable(Route route) {
+        return getDefaultRouteTable(route.prefix().address());
+    }
+
+    private RouteTable getDefaultRouteTable(IpAddress ip) {
+        RouteTableId routeTableId = (ip.isIp4()) ? IPV4 : IPV6;
+        return routeTables.get(routeTableId);
+    }
+
+    private static String createBinaryString(IpPrefix ipPrefix) {
+        byte[] octets = ipPrefix.address().toOctets();
+        StringBuilder result = new StringBuilder(ipPrefix.prefixLength());
+        result.append("0");
+        for (int i = 0; i < ipPrefix.prefixLength(); i++) {
+            int byteOffset = i / Byte.SIZE;
+            int bitOffset = i % Byte.SIZE;
+            int mask = 1 << (Byte.SIZE - 1 - bitOffset);
+            byte value = octets[byteOffset];
+            boolean isSet = ((value & mask) != 0);
+            result.append(isSet ? "1" : "0");
+        }
+
+        return result.toString();
+    }
+
+    /**
+     * Route table into which routes can be placed.
+     */
+    private class RouteTable {
+        private final InvertedRadixTree<Route> routeTable;
+
+        private final Map<IpPrefix, Route> routes = new ConcurrentHashMap<>();
+        private final Multimap<IpAddress, Route> reverseIndex =
+                Multimaps.synchronizedMultimap(HashMultimap.create());
+
+        /**
+         * Creates a new route table.
+         */
+        public RouteTable() {
+            routeTable = new ConcurrentInvertedRadixTree<>(
+                    new DefaultByteArrayNodeFactory());
+        }
+
+        /**
+         * Adds or updates the route in the route table.
+         *
+         * @param route route to update
+         */
+        public void update(Route route) {
+            synchronized (this) {
+                Route oldRoute = routes.put(route.prefix(), route);
+                routeTable.put(createBinaryString(route.prefix()), route);
+
+                // TODO manage routes from multiple providers
+                reverseIndex.remove(route.nextHop(), oldRoute);
+                reverseIndex.put(route.nextHop(), route);
+
+                if (oldRoute != null && !oldRoute.nextHop().equals(route.nextHop())) {
+                    // Remove old route because new one is different
+                    // TODO ROUTE_UPDATED?
+                    notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_REMOVED, new ResolvedRoute(oldRoute, null)));
+                }
+            }
+        }
+
+        /**
+         * Removes the route from the route table.
+         *
+         * @param route route to remove
+         */
+        public void remove(Route route) {
+            synchronized (this) {
+                Route removed = routes.remove(route.prefix());
+                routeTable.remove(createBinaryString(route.prefix()));
+                reverseIndex.remove(route.nextHop(), route);
+
+                if (removed != null) {
+                    notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_REMOVED, new ResolvedRoute(route, null)));
+                }
+            }
+        }
+
+        /**
+         * Returns the routes pointing to a particular next hop.
+         *
+         * @param ip next hop IP address
+         * @return routes for the next hop
+         */
+        public Collection<Route> getRoutesForNextHop(IpAddress ip) {
+            return reverseIndex.get(ip);
+        }
+
+        /**
+         * Returns all routes in the route table.
+         *
+         * @return all routes
+         */
+        public Collection<Route> getRoutes() {
+            return routes.values();
+        }
+
+        /**
+         * Performs a longest prefix match with the given IP in the route table.
+         *
+         * @param ip IP address to look up
+         * @return most specific prefix containing the given
+         */
+        public Route longestPrefixMatch(IpAddress ip) {
+            Iterable<Route> prefixes =
+                    routeTable.getValuesForKeysPrefixing(createBinaryString(ip.toIpPrefix()));
+
+            Iterator<Route> it = prefixes.iterator();
+
+            Route route = null;
+            while (it.hasNext()) {
+                route = it.next();
+            }
+
+            return route;
+        }
+    }
+
+}
diff --git a/incubator/store/src/main/java/org/onosproject/incubator/store/routing/impl/package-info.java b/incubator/store/src/main/java/org/onosproject/incubator/store/routing/impl/package-info.java
new file mode 100644
index 0000000..12096da
--- /dev/null
+++ b/incubator/store/src/main/java/org/onosproject/incubator/store/routing/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016 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.
+ */
+
+/**
+ * Implementation of the unicast routing service.
+ */
+package org.onosproject.incubator.store.routing.impl;