Move routing from incubator to a separate app
Change-Id: I961d10af99c572b1f8d9b3d37c6f52dd04422007
diff --git a/apps/bgprouter/BUCK b/apps/bgprouter/BUCK
index 211ae7a..ac93991 100644
--- a/apps/bgprouter/BUCK
+++ b/apps/bgprouter/BUCK
@@ -20,5 +20,5 @@
url = 'http://onosproject.org',
description = 'BGP router application.',
included_bundles = BUNDLES,
- required_apps = [ 'org.onosproject.fibinstaller' ],
+ required_apps = [ 'org.onosproject.fibinstaller', 'org.onosproject.route-service' ],
)
diff --git a/apps/dhcprelay/BUCK b/apps/dhcprelay/BUCK
index ab78c6d..7ff6889 100644
--- a/apps/dhcprelay/BUCK
+++ b/apps/dhcprelay/BUCK
@@ -2,14 +2,14 @@
'//lib:CORE_DEPS',
'//lib:org.apache.karaf.shell.console',
'//cli:onos-cli',
- '//incubator/api:onos-incubator-api',
- '//core/store/serializers:onos-core-serializers'
+ '//core/store/serializers:onos-core-serializers',
+ '//apps/route-service/api:onos-apps-route-service-api',
]
TEST_DEPS = [
- '//lib:TEST_ADAPTERS',
+ '//lib:TEST',
+ '//apps/route-service/api:onos-apps-route-service-api-tests',
'//core/api:onos-api-tests',
- '//incubator/api:onos-incubator-api-tests',
]
osgi_jar_with_tests (
@@ -23,4 +23,5 @@
category = 'Utility',
url = 'http://onosproject.org',
description = 'DHCP Relay Agent Application.',
+ required_apps = [ 'org.onosproject.route-service' ],
)
diff --git a/apps/dhcprelay/pom.xml b/apps/dhcprelay/pom.xml
index 752de52..b3d879e 100644
--- a/apps/dhcprelay/pom.xml
+++ b/apps/dhcprelay/pom.xml
@@ -61,7 +61,7 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
-
+
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onlab-junit</artifactId>
@@ -114,5 +114,10 @@
<artifactId>onos-cli</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-app-route-service-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
</dependencies>
</project>
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
index 4475f54..e8f56d1 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
@@ -40,8 +40,8 @@
import org.onosproject.dhcprelay.store.DhcpRelayStore;
import org.onosproject.net.intf.Interface;
import org.onosproject.net.intf.InterfaceService;
-import org.onosproject.incubator.net.routing.Route;
-import org.onosproject.incubator.net.routing.RouteStore;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteStore;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Host;
import org.onosproject.net.HostId;
diff --git a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
index 72b4d84..cab0908 100644
--- a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
+++ b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
@@ -45,8 +45,8 @@
import org.onosproject.dhcprelay.store.DhcpRelayStoreEvent;
import org.onosproject.net.intf.Interface;
import org.onosproject.net.intf.InterfaceServiceAdapter;
-import org.onosproject.incubator.net.routing.Route;
-import org.onosproject.incubator.net.routing.RouteStoreAdapter;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteStoreAdapter;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultHost;
import org.onosproject.net.Host;
diff --git a/apps/evpnopenflow/BUCK b/apps/evpnopenflow/BUCK
index 6554cc7..a244b1f 100755
--- a/apps/evpnopenflow/BUCK
+++ b/apps/evpnopenflow/BUCK
@@ -6,6 +6,7 @@
'//core/store/serializers:onos-core-serializers',
'//apps/gluon:onos-apps-gluon',
'//apps/vtn/vtnrsc:onos-apps-vtn-vtnrsc',
+ '//apps/route-service/api:onos-apps-route-service-api',
]
TEST_DEPS = [
@@ -24,4 +25,5 @@
url = 'http://onosproject.org',
description = 'Ethernet VPN (EVPN) introduces a new model for Ethernet services delivery.' +
'It enables integrated Layer 2 service over Ethernet with multihoming.',
+ required_apps = [ 'org.onosproject.route-service' ],
)
diff --git a/apps/evpnopenflow/pom.xml b/apps/evpnopenflow/pom.xml
index aa4aab7..02a5baa 100755
--- a/apps/evpnopenflow/pom.xml
+++ b/apps/evpnopenflow/pom.xml
@@ -105,5 +105,10 @@
<artifactId>onos-app-vtn-rsc</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-app-route-service-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
</dependencies>
</project>
diff --git a/apps/evpnopenflow/src/main/java/org/onosproject/evpnopenflow/rsc/vpninstance/impl/VpnInstanceManager.java b/apps/evpnopenflow/src/main/java/org/onosproject/evpnopenflow/rsc/vpninstance/impl/VpnInstanceManager.java
index 361bad4..e0ec5a9 100755
--- a/apps/evpnopenflow/src/main/java/org/onosproject/evpnopenflow/rsc/vpninstance/impl/VpnInstanceManager.java
+++ b/apps/evpnopenflow/src/main/java/org/onosproject/evpnopenflow/rsc/vpninstance/impl/VpnInstanceManager.java
@@ -34,7 +34,7 @@
import org.onosproject.evpnopenflow.rsc.vpnafconfig.VpnAfConfigService;
import org.onosproject.evpnopenflow.rsc.vpninstance.VpnInstanceService;
import org.onosproject.incubator.net.routing.EvpnInstanceName;
-import org.onosproject.incubator.net.routing.RouteAdminService;
+import org.onosproject.routeservice.RouteAdminService;
import org.onosproject.incubator.net.routing.RouteDistinguisher;
import org.onosproject.incubator.net.routing.VpnRouteTarget;
import org.onosproject.store.serializers.KryoNamespaces;
@@ -287,4 +287,4 @@
vpnInstanceMap.put(id, vpnInstance);
return Collections.unmodifiableCollection(vpnInstanceMap.values());
}
-}
\ No newline at end of file
+}
diff --git a/apps/pim/BUCK b/apps/pim/BUCK
index 1126966..607978e 100644
--- a/apps/pim/BUCK
+++ b/apps/pim/BUCK
@@ -4,6 +4,7 @@
'//cli:onos-cli',
'//incubator/api:onos-incubator-api',
'//apps/routing-api:onos-apps-routing-api',
+ '//apps/route-service/api:onos-apps-route-service-api',
]
BUNDLES = [
@@ -23,4 +24,5 @@
url = 'http://onosproject.org',
description = 'Protocol independent multicast emulation.',
included_bundles = BUNDLES,
+ required_apps = [ 'org.onosproject.route-service' ],
)
diff --git a/apps/pim/pom.xml b/apps/pim/pom.xml
index b62dc0b..4ceef0e 100644
--- a/apps/pim/pom.xml
+++ b/apps/pim/pom.xml
@@ -85,6 +85,11 @@
<artifactId>org.apache.felix.scr.annotations</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-app-route-service-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
</dependencies>
</project>
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PimInterfaceManager.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PimInterfaceManager.java
index 13d6da3..3c75c93 100644
--- a/apps/pim/src/main/java/org/onosproject/pim/impl/PimInterfaceManager.java
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PimInterfaceManager.java
@@ -28,8 +28,8 @@
import org.onosproject.net.intf.InterfaceEvent;
import org.onosproject.net.intf.InterfaceListener;
import org.onosproject.net.intf.InterfaceService;
-import org.onosproject.incubator.net.routing.Route;
-import org.onosproject.incubator.net.routing.RouteService;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Host;
import org.onosproject.net.config.ConfigFactory;
diff --git a/apps/pom.xml b/apps/pom.xml
index bdefb5e..0fd13fc 100644
--- a/apps/pom.xml
+++ b/apps/pom.xml
@@ -89,8 +89,9 @@
<module>yang</module>
<module>openroadm</module>
<module>netconf/client</module>
- <module>gluon</module>
+ <module>gluon</module>
<module>evpnopenflow</module>
+ <module>route-service</module>
</modules>
<properties>
diff --git a/apps/reactive-routing/BUCK b/apps/reactive-routing/BUCK
index 9728498..6e81b7a 100644
--- a/apps/reactive-routing/BUCK
+++ b/apps/reactive-routing/BUCK
@@ -4,6 +4,7 @@
'//incubator/api:onos-incubator-api',
'//apps/routing-api:onos-apps-routing-api',
'//apps/intentsync:onos-apps-intentsync',
+ '//apps/route-service/api:onos-apps-route-service-api',
]
osgi_jar (
@@ -15,5 +16,5 @@
category = 'Traffic Steering',
url = 'http://onosproject.org',
description = 'SDN-IP reactive routing application.',
- required_apps = [ 'org.onosproject.intentsynchronizer', 'org.onosproject.sdnip' ],
+ required_apps = [ 'org.onosproject.intentsynchronizer', 'org.onosproject.sdnip', 'org.onosproject.route-service'],
)
diff --git a/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/ReactiveRoutingConfiguration.java b/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/ReactiveRoutingConfiguration.java
index b6145ad..904867d 100644
--- a/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/ReactiveRoutingConfiguration.java
+++ b/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/ReactiveRoutingConfiguration.java
@@ -51,7 +51,7 @@
import java.util.Set;
import java.util.stream.Collectors;
-import static org.onosproject.incubator.net.routing.RouteTools.createBinaryString;
+import static org.onosproject.routeservice.RouteTools.createBinaryString;
/**
* Reactive routing configuration manager.
diff --git a/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/SdnIpReactiveRouting.java b/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/SdnIpReactiveRouting.java
index 38917ed..5b6ad9c 100644
--- a/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/SdnIpReactiveRouting.java
+++ b/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/SdnIpReactiveRouting.java
@@ -33,8 +33,8 @@
import org.onosproject.core.CoreService;
import org.onosproject.net.intf.Interface;
import org.onosproject.net.intf.InterfaceService;
-import org.onosproject.incubator.net.routing.Route;
-import org.onosproject.incubator.net.routing.RouteService;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Host;
import org.onosproject.net.flow.DefaultTrafficSelector;
diff --git a/apps/route-service/BUCK b/apps/route-service/BUCK
new file mode 100644
index 0000000..bf6ff33
--- /dev/null
+++ b/apps/route-service/BUCK
@@ -0,0 +1,11 @@
+BUNDLES = [
+ '//apps/route-service/api:onos-apps-route-service-api',
+ '//apps/route-service/app:onos-apps-route-service-app',
+]
+
+onos_app (
+ title = 'Route Service Server App',
+ category = 'Utility',
+ url = 'http://onosproject.org',
+ included_bundles = BUNDLES,
+)
diff --git a/apps/route-service/api/BUCK b/apps/route-service/api/BUCK
new file mode 100644
index 0000000..68eeacf
--- /dev/null
+++ b/apps/route-service/api/BUCK
@@ -0,0 +1,13 @@
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+]
+
+TEST_DEPS = [
+ '//lib:TEST',
+ '//core/api:onos-api-tests',
+]
+
+osgi_jar_with_tests (
+ deps = COMPILE_DEPS,
+ test_deps = TEST_DEPS,
+)
diff --git a/apps/route-service/api/pom.xml b/apps/route-service/api/pom.xml
new file mode 100644
index 0000000..f136855
--- /dev/null
+++ b/apps/route-service/api/pom.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2015-present Open Networking Foundation
+ ~
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <artifactId>onos-app-route-service</artifactId>
+ <groupId>org.onosproject</groupId>
+ <version>1.11.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>onos-app-route-service-api</artifactId>
+ <packaging>bundle</packaging>
+
+ <url>http://onosproject.org</url>
+
+ <description>Route Service Application API</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-serializers</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-incubator-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+
+</project>
diff --git a/apps/route-service/api/src/main/java/org/onosproject/routeservice/InternalRouteEvent.java b/apps/route-service/api/src/main/java/org/onosproject/routeservice/InternalRouteEvent.java
new file mode 100644
index 0000000..899e54f
--- /dev/null
+++ b/apps/route-service/api/src/main/java/org/onosproject/routeservice/InternalRouteEvent.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.routeservice;
+
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Route event for signalling between the store and the manager.
+ */
+public class InternalRouteEvent extends
+ AbstractEvent<InternalRouteEvent.Type, RouteSet> {
+
+ /**
+ * Internal route event type.
+ */
+ public enum Type {
+ /**
+ * Indicates a route was added to the store.
+ */
+ ROUTE_ADDED,
+
+ /**
+ * Indicates a route was removed from the store.
+ */
+ ROUTE_REMOVED
+ }
+
+ /**
+ * Creates a new internal route event.
+ *
+ * @param type route event type
+ * @param subject route set
+ */
+ public InternalRouteEvent(Type type, RouteSet subject) {
+ super(type, subject);
+ }
+
+ protected InternalRouteEvent(Type type, RouteSet subject, long time) {
+ super(type, subject, time);
+ }
+}
diff --git a/apps/route-service/api/src/main/java/org/onosproject/routeservice/NextHopData.java b/apps/route-service/api/src/main/java/org/onosproject/routeservice/NextHopData.java
new file mode 100644
index 0000000..f3b7da7
--- /dev/null
+++ b/apps/route-service/api/src/main/java/org/onosproject/routeservice/NextHopData.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice;
+
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Host;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Stores next hop information.
+ */
+public class NextHopData {
+
+ private final MacAddress mac;
+ private final ConnectPoint location;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param mac MAC address
+ * @param location Connect point
+ */
+ public NextHopData(MacAddress mac, ConnectPoint location) {
+ this.mac = mac;
+ this.location = location;
+ }
+
+ /**
+ * Returns the MAC address.
+ *
+ * @return MAC address
+ */
+ public MacAddress mac() {
+ return mac;
+ }
+
+ /**
+ * Returns the location.
+ *
+ * @return Connect point
+ */
+ public ConnectPoint location() {
+ return location;
+ }
+
+ /**
+ * Creates a new instance from a host.
+ *
+ * @param host Host information
+ * @return NextHopData instance
+ */
+ public static NextHopData fromHost(Host host) {
+ return new NextHopData(host.mac(), host.location());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mac, location);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (!(other instanceof NextHopData)) {
+ return false;
+ }
+
+ NextHopData that = (NextHopData) other;
+
+ return Objects.equals(this.mac, that.mac) &&
+ Objects.equals(this.location, that.location);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("mac", mac)
+ .add("location", location)
+ .toString();
+ }
+}
diff --git a/apps/route-service/api/src/main/java/org/onosproject/routeservice/ResolvedRoute.java b/apps/route-service/api/src/main/java/org/onosproject/routeservice/ResolvedRoute.java
new file mode 100644
index 0000000..828827d
--- /dev/null
+++ b/apps/route-service/api/src/main/java/org/onosproject/routeservice/ResolvedRoute.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Represents a route with the next hop MAC address resolved.
+ */
+public class ResolvedRoute {
+
+ private final Route route;
+ private final MacAddress nextHopMac;
+ private final VlanId nextHopVlan;
+ private final ConnectPoint location;
+
+ /**
+ * Creates a new resolved route.
+ *
+ * @param route input route
+ * @param nextHopMac next hop MAC address
+ * @param location connect point where the next hop connects to
+ */
+ public ResolvedRoute(Route route, MacAddress nextHopMac, ConnectPoint location) {
+ this(route, nextHopMac, VlanId.NONE, location);
+ }
+
+ /**
+ * Creates a new resolved route.
+ *
+ * @param route input route
+ * @param nextHopMac next hop MAC address
+ * @param nextHopVlan next hop VLAN ID
+ * @param location connect point where the next hop connects to
+ */
+ public ResolvedRoute(Route route, MacAddress nextHopMac, VlanId nextHopVlan,
+ ConnectPoint location) {
+ this.route = route;
+ this.nextHopMac = nextHopMac;
+ this.nextHopVlan = nextHopVlan;
+ this.location = location;
+ }
+
+ /**
+ * Returns the original route.
+ *
+ * @return route
+ */
+ public Route route() {
+ return route;
+ }
+
+ /**
+ * Returns the IP prefix.
+ *
+ * @return IP prefix
+ */
+ public IpPrefix prefix() {
+ return route.prefix();
+ }
+
+ /**
+ * Returns the next hop IP address.
+ *
+ * @return IP address
+ */
+ public IpAddress nextHop() {
+ return route.nextHop();
+ }
+
+ /**
+ * Returns the next hop MAC address.
+ *
+ * @return MAC address
+ */
+ public MacAddress nextHopMac() {
+ return nextHopMac;
+ }
+
+ /**
+ * Returns the next hop VLAN ID.
+ *
+ * @return VLAN ID
+ */
+ public VlanId nextHopVlan() {
+ return nextHopVlan;
+ }
+
+ /**
+ * Returns the next hop location.
+ *
+ * @return connect point where the next hop attaches to
+ */
+ public ConnectPoint location() {
+ return location;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(route, nextHopMac, nextHopVlan, location);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (!(other instanceof ResolvedRoute)) {
+ return false;
+ }
+
+ ResolvedRoute that = (ResolvedRoute) other;
+
+ return Objects.equals(this.route, that.route) &&
+ Objects.equals(this.nextHopMac, that.nextHopMac) &&
+ Objects.equals(this.nextHopVlan, that.nextHopVlan) &&
+ Objects.equals(this.location, that.location);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("route", route)
+ .add("nextHopMac", nextHopMac)
+ .add("nextHopVlan", nextHopVlan)
+ .add("location", location)
+ .toString();
+ }
+}
diff --git a/apps/route-service/api/src/main/java/org/onosproject/routeservice/Route.java b/apps/route-service/api/src/main/java/org/onosproject/routeservice/Route.java
new file mode 100644
index 0000000..f542252
--- /dev/null
+++ b/apps/route-service/api/src/main/java/org/onosproject/routeservice/Route.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.cluster.NodeId;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Represents a route.
+ */
+public class Route {
+
+ private static final String VERSION_MISMATCH =
+ "Prefix and next hop must be in the same address family";
+
+ private static final NodeId UNDEFINED = new NodeId("-");
+
+ /**
+ * Source of the route.
+ */
+ public enum Source {
+ /**
+ * Route came from the iBGP route source.
+ */
+ BGP,
+
+ /**
+ * Route came from the FPM route source.
+ */
+ FPM,
+
+ /**
+ * Route can from the static route source.
+ */
+ STATIC,
+
+ /**
+ * Route source was not defined.
+ */
+ UNDEFINED
+ }
+
+ private final Source source;
+ private final IpPrefix prefix;
+ private final IpAddress nextHop;
+ private final NodeId sourceNode;
+
+ /**
+ * Creates a route.
+ *
+ * @param source route source
+ * @param prefix IP prefix
+ * @param nextHop next hop IP address
+ */
+ public Route(Source source, IpPrefix prefix, IpAddress nextHop) {
+ this(source, prefix, nextHop, UNDEFINED);
+ }
+
+ /**
+ * Creates a route.
+ *
+ * @param source route source
+ * @param prefix IP prefix
+ * @param nextHop next hop IP address
+ * @param sourceNode ONOS node the route was sourced from
+ */
+ public Route(Source source, IpPrefix prefix, IpAddress nextHop, NodeId sourceNode) {
+ checkNotNull(prefix);
+ checkNotNull(nextHop);
+ checkArgument(prefix.version().equals(nextHop.version()), VERSION_MISMATCH);
+
+ this.source = checkNotNull(source);
+ this.prefix = prefix;
+ this.nextHop = nextHop;
+ this.sourceNode = checkNotNull(sourceNode);
+ }
+
+ /**
+ * Returns the route source.
+ *
+ * @return route source
+ */
+ public Source source() {
+ return source;
+ }
+
+ /**
+ * Returns the IP prefix of the route.
+ *
+ * @return IP prefix
+ */
+ public IpPrefix prefix() {
+ return prefix;
+ }
+
+ /**
+ * Returns the next hop IP address.
+ *
+ * @return next hop
+ */
+ public IpAddress nextHop() {
+ return nextHop;
+ }
+
+ /**
+ * Returns the ONOS node the route was sourced from.
+ *
+ * @return ONOS node ID
+ */
+ public NodeId sourceNode() {
+ return sourceNode;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(prefix, nextHop);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (!(other instanceof Route)) {
+ return false;
+ }
+
+ Route that = (Route) other;
+
+ return Objects.equals(this.prefix, that.prefix) &&
+ Objects.equals(this.nextHop, that.nextHop);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("prefix", prefix)
+ .add("nextHop", nextHop)
+ .toString();
+ }
+}
diff --git a/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteAdminService.java b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteAdminService.java
new file mode 100644
index 0000000..8886169
--- /dev/null
+++ b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteAdminService.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice;
+
+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/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteConfig.java b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteConfig.java
new file mode 100644
index 0000000..cc77b59
--- /dev/null
+++ b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteConfig.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.config.Config;
+
+import java.util.Set;
+
+/**
+ * Route configuration object for Route Service.
+ */
+public class RouteConfig extends Config<ApplicationId> {
+ private static final String PREFIX = "prefix";
+ private static final String NEXTHOP = "nextHop";
+
+ /**
+ * Returns all routes in this configuration.
+ *
+ * @return A set of route.
+ */
+ public Set<Route> getRoutes() {
+ ImmutableSet.Builder<Route> routes = ImmutableSet.builder();
+ array.forEach(route -> {
+ try {
+ IpPrefix prefix = IpPrefix.valueOf(route.path(PREFIX).asText());
+ IpAddress nextHop = IpAddress.valueOf(route.path(NEXTHOP).asText());
+ routes.add(new Route(Route.Source.STATIC, prefix, nextHop));
+ } catch (IllegalArgumentException e) {
+ // Ignores routes that cannot be parsed correctly
+ }
+ });
+ return routes.build();
+ }
+
+ @Override
+ public boolean isValid() {
+ array.forEach(routeNode -> {
+ if (!routeNode.isObject()) {
+ throw new IllegalArgumentException("Not object node");
+ }
+ ObjectNode route = (ObjectNode) routeNode;
+ isIpPrefix(route, PREFIX, FieldPresence.MANDATORY);
+ isIpAddress(route, NEXTHOP, FieldPresence.MANDATORY);
+ });
+ return true;
+ }
+}
diff --git a/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteEvent.java b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteEvent.java
new file mode 100644
index 0000000..0428beb
--- /dev/null
+++ b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteEvent.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice;
+
+import org.joda.time.LocalDateTime;
+import org.onosproject.event.AbstractEvent;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Describes an event about a route.
+ */
+public class RouteEvent extends AbstractEvent<RouteEvent.Type, ResolvedRoute> {
+
+ private final ResolvedRoute prevSubject;
+ private final Collection<ResolvedRoute> alternativeRoutes;
+
+ /**
+ * Route event type.
+ */
+ public enum Type {
+
+ /**
+ * Route is new and the next hop is resolved.
+ * <p>
+ * The subject of this event should be the route being added.
+ * The prevSubject of this event should be null.
+ * </p>
+ */
+ ROUTE_ADDED,
+
+ /**
+ * Route has updated information.
+ * <p>
+ * The subject of this event should be the new route.
+ * The prevSubject of this event should be the old route.
+ * </p>
+ */
+ ROUTE_UPDATED,
+
+ /**
+ * Route was removed or the next hop becomes unresolved.
+ * <p>
+ * The subject of this event should be the route being removed.
+ * The prevSubject of this event should be null.
+ * </p>
+ */
+ ROUTE_REMOVED,
+
+ /**
+ * The set of alternative routes for the subject's prefix has changed,
+ * but the best route is still the same.
+ * <p>
+ * The subject is the best route for the prefix (which has already been
+ * notified in a previous event).
+ * The prevSubject of this event is null.
+ * The alternatives contains the new set of alternative routes.
+ * </p>
+ */
+ ALTERNATIVE_ROUTES_CHANGED
+ }
+
+ /**
+ * Creates a new route event without specifying previous subject.
+ *
+ * @param type event type
+ * @param subject event subject
+ */
+ public RouteEvent(Type type, ResolvedRoute subject) {
+ this(type, subject, null, Collections.emptySet());
+ }
+
+ /**
+ * Creates a new route event without specifying previous subject.
+ *
+ * @param type event type
+ * @param subject event subject
+ * @param alternatives alternative routes for subject's prefix
+ */
+ public RouteEvent(Type type, ResolvedRoute subject, Collection<ResolvedRoute> alternatives) {
+ this(type, subject, null, alternatives);
+ }
+
+ /**
+ * Creates a new route event.
+ *
+ * @param type event type
+ * @param subject event subject
+ * @param time event time
+ */
+ protected RouteEvent(Type type, ResolvedRoute subject, long time) {
+ super(type, subject, time);
+ this.prevSubject = null;
+
+ this.alternativeRoutes = Collections.emptySet();
+ }
+
+ /**
+ * Creates a new route event with previous subject.
+ *
+ * @param type event type
+ * @param subject event subject
+ * @param prevSubject previous subject
+ */
+ public RouteEvent(Type type, ResolvedRoute subject, ResolvedRoute prevSubject) {
+ this(type, subject, prevSubject, Collections.emptySet());
+ }
+
+ /**
+ * Creates a new route event with a previous subject and alternative routes.
+ *
+ * @param type event type
+ * @param subject event subject
+ * @param prevSubject previous subject
+ * @param alternatives alternative routes for subject's prefix
+ */
+ public RouteEvent(Type type, ResolvedRoute subject, ResolvedRoute prevSubject,
+ Collection<ResolvedRoute> alternatives) {
+ super(type, subject);
+ this.prevSubject = prevSubject;
+ this.alternativeRoutes = alternatives;
+ }
+
+ /**
+ * Returns the previous subject of the event.
+ *
+ * @return previous subject to which this event pertains
+ */
+ public ResolvedRoute prevSubject() {
+ return prevSubject;
+ }
+
+ /**
+ * Returns the set of alternative routes for the subject's prefix.
+ *
+ * @return alternative routes
+ */
+ public Collection<ResolvedRoute> alternatives() {
+ return alternativeRoutes;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(subject(), type(), prevSubject(), alternativeRoutes);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (!(other instanceof RouteEvent)) {
+ return false;
+ }
+
+ RouteEvent that = (RouteEvent) other;
+
+ return Objects.equals(this.subject(), that.subject()) &&
+ Objects.equals(this.type(), that.type()) &&
+ Objects.equals(this.prevSubject, that.prevSubject) &&
+ Objects.equals(this.alternativeRoutes, that.alternativeRoutes);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("time", new LocalDateTime(time()))
+ .add("type", type())
+ .add("subject", subject())
+ .add("prevSubject", prevSubject)
+ .add("alternatives", alternativeRoutes)
+ .toString();
+ }
+}
diff --git a/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteInfo.java b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteInfo.java
new file mode 100644
index 0000000..0969162
--- /dev/null
+++ b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteInfo.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.routeservice;
+
+import com.google.common.annotations.Beta;
+import org.onlab.packet.IpPrefix;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Routing information for a given prefix.
+ */
+@Beta
+public class RouteInfo {
+
+ private final IpPrefix prefix;
+ private final ResolvedRoute bestRoute;
+ private final Set<ResolvedRoute> allRoutes;
+
+ /**
+ * Creates a new route info object.
+ *
+ * @param prefix IP prefix
+ * @param bestRoute best route for this prefix if one exists
+ * @param allRoutes all known routes for this prefix
+ */
+ @Beta
+ public RouteInfo(IpPrefix prefix, ResolvedRoute bestRoute, Set<ResolvedRoute> allRoutes) {
+ this.prefix = checkNotNull(prefix);
+ this.bestRoute = bestRoute;
+ this.allRoutes = checkNotNull(allRoutes);
+ }
+
+ /**
+ * Returns the IP prefix.
+ *
+ * @return IP prefix
+ */
+ public IpPrefix prefix() {
+ return prefix;
+ }
+
+ /**
+ * Returns the best route for this prefix if one exists.
+ *
+ * @return optional best route
+ */
+ public Optional<ResolvedRoute> bestRoute() {
+ return Optional.ofNullable(bestRoute);
+ }
+
+ /**
+ * Returns all routes for this prefix.
+ *
+ * @return all routes
+ */
+ public Set<ResolvedRoute> allRoutes() {
+ return allRoutes;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(prefix, bestRoute, allRoutes);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (!(other instanceof RouteInfo)) {
+ return false;
+ }
+
+ RouteInfo that = (RouteInfo) other;
+
+ return Objects.equals(this.prefix, that.prefix) &&
+ Objects.equals(this.bestRoute, that.bestRoute) &&
+ Objects.equals(this.allRoutes, that.allRoutes);
+ }
+}
diff --git a/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteListener.java b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteListener.java
new file mode 100644
index 0000000..063f335
--- /dev/null
+++ b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteListener.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Listener for route events.
+ */
+public interface RouteListener extends EventListener<RouteEvent> {
+}
diff --git a/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteService.java b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteService.java
new file mode 100644
index 0000000..c626e47
--- /dev/null
+++ b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteService.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice;
+
+import org.onlab.packet.IpAddress;
+import org.onosproject.event.ListenerService;
+
+import java.util.Collection;
+import java.util.Optional;
+
+/**
+ * Unicast IP route service.
+ */
+public interface RouteService extends ListenerService<RouteEvent, RouteListener> {
+
+ /**
+ * Returns information about all routes in the given route table.
+ *
+ * @param id route table ID
+ * @return collection of route information
+ */
+ Collection<RouteInfo> getRoutes(RouteTableId id);
+
+ /**
+ * Returns the set of route tables in the system.
+ *
+ * @return collection of route table IDs.
+ */
+ Collection<RouteTableId> getRouteTables();
+
+ /**
+ * Performs a longest prefix lookup on the given IP address.
+ *
+ * @param ip IP address to look up
+ * @return most specific matching route, if one exists
+ */
+ Optional<ResolvedRoute> longestPrefixLookup(IpAddress ip);
+
+ /**
+ * 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 ip IP address
+ * @return longest prefix matched route
+ * @deprecated in Kingfisher release. Use {{@link #longestPrefixLookup(IpAddress)}}
+ * instead.
+ */
+ @Deprecated
+ Route longestPrefixMatch(IpAddress ip);
+}
diff --git a/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteSet.java b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteSet.java
new file mode 100644
index 0000000..f464e5d
--- /dev/null
+++ b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteSet.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.routeservice;
+
+import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.IpPrefix;
+
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A set of routes for a particular prefix in a route table.
+ */
+public class RouteSet {
+ private final RouteTableId tableId;
+
+ private final IpPrefix prefix;
+ private final Set<Route> routes;
+
+ /**
+ * Creates a new route set.
+ *
+ * @param tableId route table ID
+ * @param prefix IP prefix
+ * @param routes routes for the given prefix
+ */
+ public RouteSet(RouteTableId tableId, IpPrefix prefix, Set<Route> routes) {
+ this.tableId = checkNotNull(tableId);
+ this.prefix = checkNotNull(prefix);
+ this.routes = ImmutableSet.copyOf(checkNotNull(routes));
+ }
+
+ /**
+ * Returns the route table ID.
+ *
+ * @return route table ID
+ */
+ public RouteTableId tableId() {
+ return tableId;
+ }
+
+ /**
+ * Returns the IP prefix.
+ *
+ * @return IP prefix
+ */
+ public IpPrefix prefix() {
+ return prefix;
+ }
+
+ /**
+ * Returns the set of routes.
+ *
+ * @return routes
+ */
+ public Set<Route> routes() {
+ return routes;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(tableId, prefix, routes);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (!(other instanceof RouteSet)) {
+ return false;
+ }
+
+ RouteSet that = (RouteSet) other;
+
+ return Objects.equals(this.tableId, that.tableId) &&
+ Objects.equals(this.prefix, that.prefix) &&
+ Objects.equals(this.routes, that.routes);
+ }
+}
diff --git a/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteStore.java b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteStore.java
new file mode 100644
index 0000000..6866762
--- /dev/null
+++ b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteStore.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.routeservice;
+
+import com.google.common.annotations.Beta;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.store.Store;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Unicast route store.
+ */
+public interface RouteStore extends Store<InternalRouteEvent, 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 in the given route table, grouped by prefix.
+ *
+ * @param table route table ID
+ * @return routes
+ */
+ Collection<RouteSet> getRoutes(RouteTableId table);
+
+ /**
+ * Returns the routes that point to the given next hop IP address.
+ *
+ * @param ip IP address of the next hop
+ * @return routes for the given next hop
+ */
+ // TODO think about including route table info
+ Collection<Route> getRoutesForNextHop(IpAddress ip);
+
+ /**
+ * Returns the set of routes in the default route table for the given prefix.
+ *
+ * @param prefix IP prefix
+ * @return route set
+ */
+ // TODO needs to be generalizable across route tables
+ @Beta
+ RouteSet getRoutes(IpPrefix prefix);
+
+ /**
+ * Returns the name of this route store.
+ *
+ * @return the name of this route store
+ */
+ default String name() {
+ return getClass().getName();
+ }
+}
diff --git a/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteStoreDelegate.java b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteStoreDelegate.java
new file mode 100644
index 0000000..e9c5b00
--- /dev/null
+++ b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteStoreDelegate.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.routeservice;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Route store delegate abstraction.
+ */
+public interface RouteStoreDelegate extends StoreDelegate<InternalRouteEvent> {
+}
diff --git a/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteTableId.java b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteTableId.java
new file mode 100644
index 0000000..b522e6c
--- /dev/null
+++ b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteTableId.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice;
+
+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/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteTools.java b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteTools.java
new file mode 100644
index 0000000..5264719
--- /dev/null
+++ b/apps/route-service/api/src/main/java/org/onosproject/routeservice/RouteTools.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.routeservice;
+
+import org.onlab.packet.IpPrefix;
+
+/**
+ * Routing tools.
+ */
+public final class RouteTools {
+
+ private RouteTools() {
+ }
+
+ /**
+ * Creates a binary string representation of an IP prefix.
+ *
+ * For each string, we put a extra "0" in the front. The purpose of
+ * doing this is to store the default route inside InvertedRadixTree.
+ *
+ * @param ipPrefix the IP prefix to use
+ * @return the binary string representation
+ */
+ public 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();
+ }
+}
diff --git a/apps/route-service/api/src/main/java/org/onosproject/routeservice/package-info.java b/apps/route-service/api/src/main/java/org/onosproject/routeservice/package-info.java
new file mode 100644
index 0000000..5bfe5ba
--- /dev/null
+++ b/apps/route-service/api/src/main/java/org/onosproject/routeservice/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Unicast routing service.
+ */
+package org.onosproject.routeservice;
diff --git a/apps/route-service/api/src/test/java/org/onosproject/routeservice/RouteConfigTest.java b/apps/route-service/api/src/test/java/org/onosproject/routeservice/RouteConfigTest.java
new file mode 100644
index 0000000..1e48420
--- /dev/null
+++ b/apps/route-service/api/src/test/java/org/onosproject/routeservice/RouteConfigTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableSet;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.ConfigApplyDelegate;
+
+import java.io.InputStream;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.*;
+
+/**
+ * Tests for class {@link RouteConfigTest}.
+ */
+public class RouteConfigTest {
+ private static final String KEY = "org.onosproject.routing";
+
+ private static final IpPrefix PREFIX1 = IpPrefix.valueOf("10.0.0.1/24");
+ private static final IpPrefix PREFIX2 = IpPrefix.valueOf("20.0.0.1/24");
+ private static final IpPrefix PREFIX3 = IpPrefix.valueOf("30.0.0.1/24");
+ private static final IpAddress NEXTHOP1 = IpAddress.valueOf("192.168.1.1");
+ private static final IpAddress NEXTHOP2 = IpAddress.valueOf("192.168.2.1");
+ private static final Route ROUTE1 = new Route(Route.Source.STATIC, PREFIX1, NEXTHOP1);
+ private static final Route ROUTE2 = new Route(Route.Source.STATIC, PREFIX2, NEXTHOP1);
+ private static final Route ROUTE3 = new Route(Route.Source.STATIC, PREFIX3, NEXTHOP2);
+ private static final Set<Route> EXPECTED_ROUTES = ImmutableSet.of(ROUTE1, ROUTE2, ROUTE3);
+ private static final Set<Route> UNEXPECTED_ROUTES = ImmutableSet.of(ROUTE1, ROUTE2);
+
+ private RouteConfig config;
+
+ @Before
+ public void setUp() throws Exception {
+ InputStream jsonStream = RouteConfigTest.class
+ .getResourceAsStream("/route-config.json");
+ ApplicationId subject = new TestApplicationId(KEY);
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode jsonNode = mapper.readTree(jsonStream);
+ ConfigApplyDelegate delegate = new MockDelegate();
+
+ config = new RouteConfig();
+ config.init(subject, KEY, jsonNode, mapper, delegate);
+ }
+
+ @Test
+ public void getRoutes() throws Exception {
+ assertThat(config.getRoutes(), is(EXPECTED_ROUTES));
+ assertThat(config.getRoutes(), not(UNEXPECTED_ROUTES));
+ }
+
+ private class MockDelegate implements ConfigApplyDelegate {
+ @Override
+ public void onApply(Config config) {
+ }
+ }
+}
diff --git a/apps/route-service/api/src/test/java/org/onosproject/routeservice/RouteServiceAdapter.java b/apps/route-service/api/src/test/java/org/onosproject/routeservice/RouteServiceAdapter.java
new file mode 100644
index 0000000..1d6ec19
--- /dev/null
+++ b/apps/route-service/api/src/test/java/org/onosproject/routeservice/RouteServiceAdapter.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice;
+
+import org.onlab.packet.IpAddress;
+
+import java.util.Collection;
+import java.util.Optional;
+
+/**
+ * Adapter class for the route service.
+ */
+public class RouteServiceAdapter implements RouteAdminService {
+ @Override
+ public void update(Collection<Route> routes) {
+
+ }
+
+ @Override
+ public void withdraw(Collection<Route> routes) {
+
+ }
+
+ @Override
+ public Collection<RouteInfo> getRoutes(RouteTableId id) {
+ return null;
+ }
+
+ @Override
+ public Collection<RouteTableId> getRouteTables() {
+ return null;
+ }
+
+ @Override
+ public Route longestPrefixMatch(IpAddress ip) {
+ return null;
+ }
+
+ @Override
+ public Optional<ResolvedRoute> longestPrefixLookup(IpAddress ip) {
+ return null;
+ }
+
+ @Override
+ public void addListener(RouteListener listener) {
+
+ }
+
+ @Override
+ public void removeListener(RouteListener listener) {
+
+ }
+}
diff --git a/apps/route-service/api/src/test/java/org/onosproject/routeservice/RouteStoreAdapter.java b/apps/route-service/api/src/test/java/org/onosproject/routeservice/RouteStoreAdapter.java
new file mode 100644
index 0000000..bf9e527
--- /dev/null
+++ b/apps/route-service/api/src/test/java/org/onosproject/routeservice/RouteStoreAdapter.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.routeservice;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Adapter class for the route store.
+ */
+public class RouteStoreAdapter implements RouteStore {
+ @Override
+ public void updateRoute(Route route) {
+
+ }
+
+ @Override
+ public void removeRoute(Route route) {
+
+ }
+
+ @Override
+ public Set<RouteTableId> getRouteTables() {
+ return null;
+ }
+
+ @Override
+ public Collection<RouteSet> getRoutes(RouteTableId table) {
+ return null;
+ }
+
+ @Override
+ public Collection<Route> getRoutesForNextHop(IpAddress ip) {
+ return null;
+ }
+
+ @Override
+ public RouteSet getRoutes(IpPrefix prefix) {
+ return null;
+ }
+
+ @Override
+ public void setDelegate(RouteStoreDelegate delegate) {
+
+ }
+
+ @Override
+ public void unsetDelegate(RouteStoreDelegate delegate) {
+
+ }
+
+ @Override
+ public boolean hasDelegate() {
+ return false;
+ }
+}
diff --git a/apps/route-service/api/src/test/resources/route-config.json b/apps/route-service/api/src/test/resources/route-config.json
new file mode 100644
index 0000000..9675f12
--- /dev/null
+++ b/apps/route-service/api/src/test/resources/route-config.json
@@ -0,0 +1,14 @@
+[
+ {
+ "prefix": "10.0.0.1/24",
+ "nextHop": "192.168.1.1"
+ },
+ {
+ "prefix": "20.0.0.1/24",
+ "nextHop": "192.168.1.1"
+ },
+ {
+ "prefix": "30.0.0.1/24",
+ "nextHop": "192.168.2.1"
+ }
+]
\ No newline at end of file
diff --git a/apps/route-service/app/BUCK b/apps/route-service/app/BUCK
new file mode 100644
index 0000000..289fddc
--- /dev/null
+++ b/apps/route-service/app/BUCK
@@ -0,0 +1,19 @@
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+ '//lib:concurrent-trees',
+ '//core/store/serializers:onos-core-serializers',
+ '//apps/route-service/api:onos-apps-route-service-api',
+ '//cli:onos-cli',
+ '//lib:org.apache.karaf.shell.console',
+]
+
+TEST_DEPS = [
+ '//lib:TEST',
+ '//apps/route-service/api:onos-apps-route-service-api-tests',
+ '//core/api:onos-api-tests',
+]
+
+osgi_jar_with_tests (
+ deps = COMPILE_DEPS,
+ test_deps = TEST_DEPS,
+)
diff --git a/apps/route-service/app/app.xml b/apps/route-service/app/app.xml
new file mode 100644
index 0000000..a561826
--- /dev/null
+++ b/apps/route-service/app/app.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2015-present Open Networking Foundation
+ ~
+ ~ 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.
+ -->
+<app name="org.onosproject.routeservice" origin="ON.Lab" version="${project.version}"
+ category="Utility" url="http://onosproject.org" title="Route Service App"
+ featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features"
+ features="${project.artifactId}">
+ <description>${project.description}</description>
+ <artifact>mvn:${project.groupId}/${project.artifactId}/${project.version}</artifact>
+ <artifact>mvn:${project.groupId}/onos-app-route-service-api/${project.version}</artifact>
+</app>
diff --git a/apps/route-service/app/features.xml b/apps/route-service/app/features.xml
new file mode 100644
index 0000000..4cc99bd
--- /dev/null
+++ b/apps/route-service/app/features.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+ ~ Copyright 2015-present Open Networking Foundation
+ ~
+ ~ 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.
+ -->
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
+ <feature name="${project.artifactId}" version="${project.version}"
+ description="${project.description}">
+ <feature>onos-api</feature>
+ <bundle>mvn:${project.groupId}/onos-app-route-service-api/${project.version}</bundle>
+ <bundle>mvn:${project.groupId}/onos-app-route-service/${project.version}</bundle>
+ </feature>
+</features>
diff --git a/apps/route-service/app/pom.xml b/apps/route-service/app/pom.xml
new file mode 100644
index 0000000..9428fb2
--- /dev/null
+++ b/apps/route-service/app/pom.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2015-present Open Networking Foundation
+ ~
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <artifactId>onos-app-route-service</artifactId>
+ <groupId>org.onosproject</groupId>
+ <version>1.11.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>onos-app-route-service-app</artifactId>
+ <packaging>bundle</packaging>
+
+ <url>http://onosproject.org</url>
+
+ <description>Route Service Application</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-serializers</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-incubator-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-cli</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.karaf.shell</groupId>
+ <artifactId>org.apache.karaf.shell.console</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-app-route-service-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.googlecode.concurrent-trees</groupId>
+ <artifactId>concurrent-trees</artifactId>
+ <version>2.6.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>5.0.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+
+</project>
diff --git a/apps/route-service/app/src/main/java/org/onosproject/routeservice/cli/RouteAddCommand.java b/apps/route-service/app/src/main/java/org/onosproject/routeservice/cli/RouteAddCommand.java
new file mode 100644
index 0000000..1810a07
--- /dev/null
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/cli/RouteAddCommand.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice.cli;
+
+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.routeservice.Route;
+import org.onosproject.routeservice.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/apps/route-service/app/src/main/java/org/onosproject/routeservice/cli/RouteRemoveCommand.java b/apps/route-service/app/src/main/java/org/onosproject/routeservice/cli/RouteRemoveCommand.java
new file mode 100644
index 0000000..9fef2ec
--- /dev/null
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/cli/RouteRemoveCommand.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice.cli;
+
+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.routeservice.Route;
+import org.onosproject.routeservice.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;
+
+ @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.withdraw(Collections.singleton(new Route(Route.Source.STATIC, prefix, nextHop)));
+ }
+
+}
diff --git a/apps/route-service/app/src/main/java/org/onosproject/routeservice/cli/RouteStoreCommand.java b/apps/route-service/app/src/main/java/org/onosproject/routeservice/cli/RouteStoreCommand.java
new file mode 100644
index 0000000..2aefbf7
--- /dev/null
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/cli/RouteStoreCommand.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.routeservice.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.routeservice.RouteStore;
+
+/**
+ * Command to show the current route store implementation.
+ */
+@Command(scope = "onos", name = "route-store",
+ description = "Show the current route store implementation.")
+public class RouteStoreCommand extends AbstractShellCommand {
+
+ @Override
+ protected void execute() {
+ RouteStore routeStore = AbstractShellCommand.get(RouteStore.class);
+ print(routeStore.name());
+ }
+}
diff --git a/apps/route-service/app/src/main/java/org/onosproject/routeservice/cli/RoutesListCommand.java b/apps/route-service/app/src/main/java/org/onosproject/routeservice/cli/RoutesListCommand.java
new file mode 100644
index 0000000..9c7eae0
--- /dev/null
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/cli/RoutesListCommand.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.routeservice.cli;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.routeservice.ResolvedRoute;
+import org.onosproject.routeservice.RouteInfo;
+import org.onosproject.routeservice.RouteService;
+import org.onosproject.routeservice.RouteTableId;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Command to show the routes in the routing tables.
+ */
+@Command(scope = "onos", name = "routes",
+ description = "Lists routes in the route store")
+public class RoutesListCommand extends AbstractShellCommand {
+
+ private static final String NETWORK = "Network";
+ private static final String NEXTHOP = "Next Hop";
+ private static final String SOURCE = "Source";
+ private static final String NODE = "Node";
+
+ private static final String FORMAT_ROUTE = "%-1s %-18s %-15s %s (%s)";
+ private static final String FORMAT_ROUTE6 = "%-1s %-43s %-39s %s (%s)";
+
+ 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);
+
+ if (outputJson()) {
+ ObjectMapper mapper = new ObjectMapper();
+ ObjectNode result = mapper.createObjectNode();
+ result.set("routes4", json(service.getRoutes(new RouteTableId("ipv4"))));
+ result.set("routes6", json(service.getRoutes(new RouteTableId("ipv6"))));
+ print("%s", result);
+ } else {
+ service.getRouteTables().forEach(id -> {
+ Collection<RouteInfo> tableRoutes = service.getRoutes(id);
+
+ String format = tableRoutes.stream().anyMatch(route -> route.prefix().isIp6()) ?
+ FORMAT_ROUTE6 : FORMAT_ROUTE;
+
+ // Print header
+ print(FORMAT_TABLE, id);
+ print(format, "", NETWORK, NEXTHOP, SOURCE, NODE);
+
+ // Print routing entries
+ tableRoutes.stream()
+ .sorted(Comparator.comparing(r -> r.prefix().address()))
+ .forEach(route -> this.print(format, route));
+
+ print(FORMAT_TOTAL, tableRoutes.size());
+ print("");
+ });
+ }
+ }
+
+ private void print(String format, RouteInfo routeInfo) {
+ routeInfo.allRoutes().stream()
+ .sorted(Comparator.comparing(r -> r.nextHop()))
+ .forEach(r -> print(format, isBestRoute(routeInfo.bestRoute(), r) ? ">" : "",
+ r.prefix(), r.nextHop(), r.route().source(), r.route().sourceNode()));
+ }
+
+ private boolean isBestRoute(Optional<ResolvedRoute> bestRoute, ResolvedRoute route) {
+ return Objects.equals(bestRoute.orElse(null), route);
+ }
+
+ /**
+ * Produces a JSON array of routes.
+ *
+ * @param routes the routes with the data
+ * @return JSON array with the routes
+ */
+ private JsonNode json(Collection<RouteInfo> routes) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
+
+ routes.stream()
+ .flatMap(ri -> ri.allRoutes().stream())
+ .forEach(r -> result.add(json(mapper, r)));
+
+ return result;
+ }
+
+ /**
+ * Produces JSON object for a route.
+ *
+ * @param mapper the JSON object mapper to use
+ * @param route the route with the data
+ * @return JSON object for the route
+ */
+ private ObjectNode json(ObjectMapper mapper, ResolvedRoute route) {
+ ObjectNode result = mapper.createObjectNode();
+
+ result.put("prefix", route.prefix().toString());
+ result.put("nextHop", route.nextHop().toString());
+
+ return result;
+ }
+
+}
diff --git a/apps/route-service/app/src/main/java/org/onosproject/routeservice/cli/package-info.java b/apps/route-service/app/src/main/java/org/onosproject/routeservice/cli/package-info.java
new file mode 100644
index 0000000..3880e5b
--- /dev/null
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice.cli;
diff --git a/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/ConfigurationRouteSource.java b/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/ConfigurationRouteSource.java
new file mode 100644
index 0000000..eb6733f
--- /dev/null
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/ConfigurationRouteSource.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.routeservice.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.onosproject.core.ApplicationId;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteAdminService;
+import org.onosproject.routeservice.RouteConfig;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.basics.SubjectFactories;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Route source that installs static routes configured in the network configuration.
+ */
+@Component(immediate = true)
+public class ConfigurationRouteSource {
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected NetworkConfigRegistry netcfgRegistry;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected RouteAdminService routeService;
+
+ private final ConfigFactory<ApplicationId, RouteConfig> routeConfigFactory =
+ new ConfigFactory<ApplicationId, RouteConfig>(
+ SubjectFactories.APP_SUBJECT_FACTORY,
+ RouteConfig.class, "routes", true) {
+ @Override
+ public RouteConfig createConfig() {
+ return new RouteConfig();
+ }
+ };
+ private final InternalNetworkConfigListener netcfgListener =
+ new InternalNetworkConfigListener();
+
+ @Activate
+ protected void activate() {
+ netcfgRegistry.addListener(netcfgListener);
+ netcfgRegistry.registerConfigFactory(routeConfigFactory);
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ netcfgRegistry.removeListener(netcfgListener);
+ netcfgRegistry.unregisterConfigFactory(routeConfigFactory);
+ }
+
+ private void processRouteConfigAdded(NetworkConfigEvent event) {
+ Set<Route> routes = ((RouteConfig) event.config().get()).getRoutes();
+ routeService.update(routes);
+ }
+
+ private void processRouteConfigUpdated(NetworkConfigEvent event) {
+ Set<Route> routes = ((RouteConfig) event.config().get()).getRoutes();
+ Set<Route> prevRoutes = ((RouteConfig) event.prevConfig().get()).getRoutes();
+ Set<Route> pendingRemove = prevRoutes.stream()
+ .filter(prevRoute -> routes.stream()
+ .noneMatch(route -> route.prefix().equals(prevRoute.prefix())))
+ .collect(Collectors.toSet());
+ Set<Route> pendingUpdate = routes.stream()
+ .filter(route -> !pendingRemove.contains(route)).collect(Collectors.toSet());
+ routeService.update(pendingUpdate);
+ routeService.withdraw(pendingRemove);
+ }
+
+ private void processRouteConfigRemoved(NetworkConfigEvent event) {
+ Set<Route> prevRoutes = ((RouteConfig) event.prevConfig().get()).getRoutes();
+ routeService.withdraw(prevRoutes);
+ }
+
+ private class InternalNetworkConfigListener implements
+ NetworkConfigListener {
+ @Override
+ public void event(NetworkConfigEvent event) {
+ if (event.configClass().equals(RouteConfig.class)) {
+ switch (event.type()) {
+ case CONFIG_ADDED:
+ processRouteConfigAdded(event);
+ break;
+ case CONFIG_UPDATED:
+ processRouteConfigUpdated(event);
+ break;
+ case CONFIG_REMOVED:
+ processRouteConfigRemoved(event);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/DefaultResolvedRouteStore.java b/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/DefaultResolvedRouteStore.java
new file mode 100644
index 0000000..34aeaa7
--- /dev/null
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/DefaultResolvedRouteStore.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.routeservice.impl;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.googlecode.concurrenttrees.common.KeyValuePair;
+import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
+import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
+import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.util.GuavaCollectors;
+import org.onlab.util.Tools;
+import org.onosproject.routeservice.ResolvedRoute;
+import org.onosproject.routeservice.RouteEvent;
+import org.onosproject.routeservice.RouteTableId;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.onosproject.routeservice.RouteTools.createBinaryString;
+
+/**
+ * Stores routes that have been resolved.
+ */
+public class DefaultResolvedRouteStore implements ResolvedRouteStore {
+
+ private Map<RouteTableId, RouteTable> routeTables;
+ private static final RouteTableId IPV4 = new RouteTableId("ipv4");
+ private static final RouteTableId IPV6 = new RouteTableId("ipv6");
+
+ /**
+ * Creates a new resolved route store.
+ */
+ public DefaultResolvedRouteStore() {
+ routeTables = new ConcurrentHashMap<>();
+
+ routeTables.put(IPV4, new RouteTable());
+ routeTables.put(IPV6, new RouteTable());
+ }
+
+ @Override
+ public RouteEvent updateRoute(ResolvedRoute route, Set<ResolvedRoute> alternatives) {
+ return getDefaultRouteTable(route).update(route, alternatives);
+ }
+
+ @Override
+ public RouteEvent removeRoute(IpPrefix prefix) {
+ RouteTable table = getDefaultRouteTable(prefix.address());
+ return table.remove(prefix);
+ }
+
+ @Override
+ public Set<RouteTableId> getRouteTables() {
+ return routeTables.keySet();
+ }
+
+ @Override
+ public Collection<ResolvedRoute> getRoutes(RouteTableId table) {
+ RouteTable routeTable = routeTables.get(table);
+ if (routeTable == null) {
+ return Collections.emptySet();
+ }
+ return routeTable.getRoutes();
+ }
+
+ @Override
+ public Optional<ResolvedRoute> getRoute(IpPrefix prefix) {
+ return getDefaultRouteTable(prefix.address()).getRoute(prefix);
+ }
+
+ @Override
+ public Collection<ResolvedRoute> getAllRoutes(IpPrefix prefix) {
+ return getDefaultRouteTable(prefix.address()).getAllRoutes(prefix);
+ }
+
+ @Override
+ public Optional<ResolvedRoute> longestPrefixMatch(IpAddress ip) {
+ return getDefaultRouteTable(ip).longestPrefixMatch(ip);
+ }
+
+ private RouteTable getDefaultRouteTable(ResolvedRoute route) {
+ return getDefaultRouteTable(route.prefix().address());
+ }
+
+ private RouteTable getDefaultRouteTable(IpAddress ip) {
+ RouteTableId routeTableId = (ip.isIp4()) ? IPV4 : IPV6;
+ return routeTables.get(routeTableId);
+ }
+
+ /**
+ * Route table into which routes can be placed.
+ */
+ private class RouteTable {
+ private final InvertedRadixTree<ResolvedRoute> routeTable;
+ private final Map<IpPrefix, Set<ResolvedRoute>> alternativeRoutes;
+
+ /**
+ * Creates a new route table.
+ */
+ public RouteTable() {
+ routeTable = new ConcurrentInvertedRadixTree<>(
+ new DefaultByteArrayNodeFactory());
+
+ alternativeRoutes = Maps.newHashMap();
+ }
+
+ /**
+ * Adds or updates the route in the route table.
+ *
+ * @param route route to update
+ * @param alternatives alternative routes
+ */
+ public RouteEvent update(ResolvedRoute route, Set<ResolvedRoute> alternatives) {
+ Set<ResolvedRoute> immutableAlternatives = checkAlternatives(route, alternatives);
+
+ synchronized (this) {
+ ResolvedRoute oldRoute = routeTable.put(createBinaryString(route.prefix()), route);
+ Set<ResolvedRoute> oldRoutes = alternativeRoutes.put(route.prefix(), immutableAlternatives);
+
+ if (!route.equals(oldRoute)) {
+ if (oldRoute == null) {
+ return new RouteEvent(RouteEvent.Type.ROUTE_ADDED, route,
+ immutableAlternatives);
+ } else {
+ return new RouteEvent(RouteEvent.Type.ROUTE_UPDATED, route,
+ oldRoute, immutableAlternatives);
+ }
+ }
+
+ if (!immutableAlternatives.equals(oldRoutes)) {
+ return new RouteEvent(RouteEvent.Type.ALTERNATIVE_ROUTES_CHANGED,
+ route, immutableAlternatives);
+ }
+
+ return null;
+ }
+ }
+
+ /**
+ * Checks that the best route is present in the alternatives list and
+ * returns an immutable set of alternatives.
+ *
+ * @param route best route
+ * @param alternatives alternatives
+ * @return immutable set of alternative routes
+ */
+ private Set<ResolvedRoute> checkAlternatives(ResolvedRoute route, Set<ResolvedRoute> alternatives) {
+ if (!alternatives.contains(route)) {
+ return ImmutableSet.<ResolvedRoute>builder()
+ .addAll(alternatives)
+ .add(route)
+ .build();
+ } else {
+ return ImmutableSet.copyOf(alternatives);
+ }
+ }
+
+ /**
+ * Removes the route from the route table.
+ *
+ * @param prefix prefix to remove
+ */
+ public RouteEvent remove(IpPrefix prefix) {
+ synchronized (this) {
+ String key = createBinaryString(prefix);
+
+ ResolvedRoute route = routeTable.getValueForExactKey(key);
+ alternativeRoutes.remove(prefix);
+
+ if (route != null) {
+ routeTable.remove(key);
+ return new RouteEvent(RouteEvent.Type.ROUTE_REMOVED, route);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Returns all routes in the route table.
+ *
+ * @return all routes
+ */
+ public Collection<ResolvedRoute> getRoutes() {
+ return Tools.stream(routeTable.getKeyValuePairsForKeysStartingWith(""))
+ .map(KeyValuePair::getValue)
+ .collect(GuavaCollectors.toImmutableList());
+ }
+
+ /**
+ * Returns the best route for the given prefix, if one exists.
+ *
+ * @param prefix IP prefix
+ * @return best route
+ */
+ public Optional<ResolvedRoute> getRoute(IpPrefix prefix) {
+ return Optional.ofNullable(routeTable.getValueForExactKey(createBinaryString(prefix)));
+ }
+
+ public Collection<ResolvedRoute> getAllRoutes(IpPrefix prefix) {
+ return alternativeRoutes.getOrDefault(prefix, Collections.emptySet());
+ }
+
+ /**
+ * 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 Optional<ResolvedRoute> longestPrefixMatch(IpAddress ip) {
+ return Tools.stream(routeTable.getValuesForKeysPrefixing(createBinaryString(ip.toIpPrefix())))
+ .reduce((a, b) -> b); // reduces to the last element in the stream
+ }
+ }
+}
diff --git a/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/ListenerQueue.java b/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/ListenerQueue.java
new file mode 100644
index 0000000..93e80d5
--- /dev/null
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/ListenerQueue.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice.impl;
+
+import org.onosproject.routeservice.RouteEvent;
+
+/**
+ * Queues updates for a route listener to ensure they are received in the
+ * correct order.
+ */
+interface ListenerQueue {
+
+ /**
+ * Posts an event to the listener.
+ *
+ * @param event event
+ */
+ void post(RouteEvent event);
+
+ /**
+ * Initiates event delivery to the listener.
+ */
+ void start();
+
+ /**
+ * Halts event delivery to the listener.
+ */
+ void stop();
+}
diff --git a/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/ResolvedRouteStore.java b/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/ResolvedRouteStore.java
new file mode 100644
index 0000000..a6db108
--- /dev/null
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/ResolvedRouteStore.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.routeservice.impl;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.routeservice.ResolvedRoute;
+import org.onosproject.routeservice.RouteEvent;
+import org.onosproject.routeservice.RouteTableId;
+
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Stores resolved routes and best route decisions.
+ */
+public interface ResolvedRouteStore {
+
+ /**
+ * Adds or updates the best route for the given prefix.
+ *
+ * @param route new best route for this prefix
+ * @param alternatives alternative resolved routes
+ * @return event describing the change
+ */
+ RouteEvent updateRoute(ResolvedRoute route, Set<ResolvedRoute> alternatives);
+
+ /**
+ * Removes the best route for the given prefix.
+ *
+ * @param prefix IP prefix
+ * @return event describing the change
+ */
+ RouteEvent removeRoute(IpPrefix prefix);
+
+ /**
+ * Gets the set of route tables.
+ *
+ * @return set of route table IDs
+ */
+ Set<RouteTableId> getRouteTables();
+
+ /**
+ * Returns the best routes for a give route table.
+ *
+ * @param table route table ID
+ * @return collection of selected routes
+ */
+ Collection<ResolvedRoute> getRoutes(RouteTableId table);
+
+ /**
+ * Returns the best selected route for the given IP prefix.
+ *
+ * @param prefix IP prefix
+ * @return optional best route
+ */
+ Optional<ResolvedRoute> getRoute(IpPrefix prefix);
+
+ /**
+ * Returns all resolved routes stored for the given prefix, including the
+ * best selected route.
+ *
+ * @param prefix IP prefix to look up routes for
+ * @return all stored resolved routes for this prefix
+ */
+ Collection<ResolvedRoute> getAllRoutes(IpPrefix prefix);
+
+ /**
+ * Performs a longest prefix match of the best routes on the given IP address.
+ *
+ * @param ip IP address
+ * @return optional longest matching route
+ */
+ Optional<ResolvedRoute> longestPrefixMatch(IpAddress ip);
+}
diff --git a/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/RouteManager.java b/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/RouteManager.java
new file mode 100644
index 0000000..e5db364
--- /dev/null
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/RouteManager.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.routeservice.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.IpPrefix;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.routeservice.InternalRouteEvent;
+import org.onosproject.routeservice.ResolvedRoute;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteAdminService;
+import org.onosproject.routeservice.RouteEvent;
+import org.onosproject.routeservice.RouteInfo;
+import org.onosproject.routeservice.RouteListener;
+import org.onosproject.routeservice.RouteService;
+import org.onosproject.routeservice.RouteSet;
+import org.onosproject.routeservice.RouteStore;
+import org.onosproject.routeservice.RouteStoreDelegate;
+import org.onosproject.routeservice.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.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.concurrent.GuardedBy;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+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.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 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;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ private ResolvedRouteStore resolvedRouteStore;
+
+ private RouteMonitor routeMonitor;
+
+ @GuardedBy(value = "this")
+ private Map<RouteListener, ListenerQueue> listeners = new HashMap<>();
+
+ private ThreadFactory threadFactory;
+
+ @Activate
+ protected void activate() {
+ routeMonitor = new RouteMonitor(this, clusterService, storageService);
+ threadFactory = groupedThreads("onos/route", "listener-%d", log);
+
+ resolvedRouteStore = new DefaultResolvedRouteStore();
+
+ routeStore.setDelegate(delegate);
+ hostService.addListener(hostListener);
+
+ routeStore.getRouteTables().stream()
+ .flatMap(id -> routeStore.getRoutes(id).stream())
+ .forEach(this::resolve);
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ routeMonitor.shutdown();
+ listeners.values().forEach(ListenerQueue::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 = createListenerQueue(listener);
+ resolvedRouteStore.getRouteTables().stream()
+ .map(resolvedRouteStore::getRoutes)
+ .flatMap(Collection::stream)
+ .map(route -> new RouteEvent(RouteEvent.Type.ROUTE_ADDED, route,
+ resolvedRouteStore.getAllRoutes(route.prefix())))
+ .forEach(l::post);
+
+ 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) {
+ if (event != null) {
+ log.debug("Sending event {}", event);
+ synchronized (this) {
+ listeners.values().forEach(l -> l.post(event));
+ }
+ }
+ }
+
+ private Collection<Route> reformatRoutes(Collection<RouteSet> routeSets) {
+ return routeSets.stream().flatMap(r -> r.routes().stream()).collect(Collectors.toList());
+ }
+
+ public Collection<RouteTableId> getRouteTables() {
+ return routeStore.getRouteTables();
+ }
+
+ @Override
+ public Collection<RouteInfo> getRoutes(RouteTableId id) {
+ return routeStore.getRoutes(id).stream()
+ .map(routeSet -> new RouteInfo(routeSet.prefix(),
+ resolvedRouteStore.getRoute(routeSet.prefix()).orElse(null), resolveRouteSet(routeSet)))
+ .collect(Collectors.toList());
+ }
+
+ private Set<ResolvedRoute> resolveRouteSet(RouteSet routeSet) {
+ return routeSet.routes().stream()
+ .map(this::tryResolve)
+ .collect(Collectors.toSet());
+ }
+
+ private ResolvedRoute tryResolve(Route route) {
+ ResolvedRoute resolvedRoute = resolve(route);
+ if (resolvedRoute == null) {
+ resolvedRoute = new ResolvedRoute(route, null, null);
+ }
+ return resolvedRoute;
+ }
+
+ @Override
+ public Optional<ResolvedRoute> longestPrefixLookup(IpAddress ip) {
+ return resolvedRouteStore.longestPrefixMatch(ip);
+ }
+
+ @Override
+ public void update(Collection<Route> routes) {
+ synchronized (this) {
+ routes.forEach(route -> {
+ log.debug("Received update {}", route);
+ routeStore.updateRoute(route);
+ });
+ }
+ }
+
+ @Override
+ public void withdraw(Collection<Route> routes) {
+ synchronized (this) {
+ routes.forEach(route -> {
+ log.debug("Received withdraw {}", route);
+ routeStore.removeRoute(route);
+ });
+ }
+ }
+
+ @Override
+ public Route longestPrefixMatch(IpAddress ip) {
+ return longestPrefixLookup(ip)
+ .map(ResolvedRoute::route)
+ .orElse(null);
+ }
+
+ private ResolvedRoute resolve(Route route) {
+ hostService.startMonitoringIp(route.nextHop());
+ Set<Host> hosts = hostService.getHostsByIp(route.nextHop());
+
+ Optional<Host> host = hosts.stream().findFirst();
+ if (host.isPresent()) {
+ return new ResolvedRoute(route, host.get().mac(), host.get().vlan(),
+ host.get().location());
+ } else {
+ return null;
+ }
+ }
+
+ private ResolvedRoute decide(ResolvedRoute route1, ResolvedRoute route2) {
+ return Comparator.comparing(ResolvedRoute::nextHop)
+ .compare(route1, route2) <= 0 ? route1 : route2;
+ }
+
+ private void store(ResolvedRoute route, Set<ResolvedRoute> alternatives) {
+ post(resolvedRouteStore.updateRoute(route, alternatives));
+ }
+
+ private void remove(IpPrefix prefix) {
+ post(resolvedRouteStore.removeRoute(prefix));
+ }
+
+ private void resolve(RouteSet routes) {
+ Set<ResolvedRoute> resolvedRoutes = routes.routes().stream()
+ .map(this::resolve)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+
+ Optional<ResolvedRoute> bestRoute = resolvedRoutes.stream()
+ .reduce(this::decide);
+
+ if (bestRoute.isPresent()) {
+ store(bestRoute.get(), resolvedRoutes);
+ } else {
+ remove(routes.prefix());
+ }
+ }
+
+ private void hostUpdated(Host host) {
+ hostChanged(host);
+ }
+
+ private void hostRemoved(Host host) {
+ hostChanged(host);
+ }
+
+ private void hostChanged(Host host) {
+ synchronized (this) {
+ host.ipAddresses().stream()
+ .flatMap(ip -> routeStore.getRoutesForNextHop(ip).stream())
+ .map(route -> routeStore.getRoutes(route.prefix()))
+ .forEach(this::resolve);
+ }
+ }
+
+ /**
+ * Creates a new listener queue.
+ *
+ * @param listener route listener
+ * @return listener queue
+ */
+ ListenerQueue createListenerQueue(RouteListener listener) {
+ return new DefaultListenerQueue(listener);
+ }
+
+ /**
+ * Default route listener queue.
+ */
+ private class DefaultListenerQueue implements 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 DefaultListenerQueue(RouteListener listener) {
+ this.listener = listener;
+ queue = new LinkedBlockingQueue<>();
+ executorService = newSingleThreadExecutor(threadFactory);
+ }
+
+ @Override
+ public void post(RouteEvent event) {
+ queue.add(event);
+ }
+
+ @Override
+ public void start() {
+ executorService.execute(this::poll);
+ }
+
+ @Override
+ public void stop() {
+ executorService.shutdown();
+ }
+
+ private void poll() {
+ while (true) {
+ try {
+ listener.event(queue.take());
+ } catch (InterruptedException e) {
+ log.info("Route listener event thread shutting down: {}", e.getMessage());
+ break;
+ } catch (Exception e) {
+ log.warn("Exception during route event handler", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Delegate to receive events from the route store.
+ */
+ private class InternalRouteStoreDelegate implements RouteStoreDelegate {
+ @Override
+ public void notify(InternalRouteEvent event) {
+ switch (event.type()) {
+ case ROUTE_ADDED:
+ resolve(event.subject());
+ break;
+ case ROUTE_REMOVED:
+ resolve(event.subject());
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * 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/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/RouteMonitor.java b/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/RouteMonitor.java
new file mode 100644
index 0000000..24d2aff
--- /dev/null
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/RouteMonitor.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.routeservice.impl;
+
+import org.onosproject.cluster.ClusterEvent;
+import org.onosproject.cluster.ClusterEventListener;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.routeservice.ResolvedRoute;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteAdminService;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.DistributedPrimitive;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.WorkQueue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.stream.Collectors;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+
+/**
+ * Monitors cluster nodes and removes routes if a cluster node becomes unavailable.
+ */
+public class RouteMonitor {
+
+ private final Logger log = LoggerFactory.getLogger(this.getClass());
+
+ private static final String TOPIC = "route-reaper";
+ private static final int NUM_PARALLEL_JOBS = 10;
+
+ private RouteAdminService routeService;
+ private final ClusterService clusterService;
+ private StorageService storageService;
+
+ private WorkQueue<NodeId> queue;
+
+ private final InternalClusterListener clusterListener = new InternalClusterListener();
+
+ private final ScheduledExecutorService reaperExecutor =
+ newSingleThreadScheduledExecutor(groupedThreads("route/reaper", "", log));
+
+ /**
+ * Creates a new route monitor.
+ *
+ * @param routeService route service
+ * @param clusterService cluster service
+ * @param storageService storage service
+ */
+ public RouteMonitor(RouteAdminService routeService,
+ ClusterService clusterService, StorageService storageService) {
+ this.routeService = routeService;
+ this.clusterService = clusterService;
+ this.storageService = storageService;
+
+ clusterService.addListener(clusterListener);
+
+ queue = storageService.getWorkQueue(TOPIC, Serializer.using(KryoNamespaces.API));
+ queue.addStatusChangeListener(this::statusChange);
+
+ startProcessing();
+ }
+
+ /**
+ * Shuts down the route monitor.
+ */
+ public void shutdown() {
+ stopProcessing();
+ clusterService.removeListener(clusterListener);
+ }
+
+ private void statusChange(DistributedPrimitive.Status status) {
+ switch (status) {
+ case ACTIVE:
+ startProcessing();
+ break;
+ case SUSPENDED:
+ stopProcessing();
+ break;
+ case INACTIVE:
+ default:
+ break;
+ }
+ }
+
+ private void startProcessing() {
+ queue.registerTaskProcessor(this::cleanRoutes, NUM_PARALLEL_JOBS, reaperExecutor);
+ }
+
+ private void stopProcessing() {
+ queue.stopProcessing();
+ }
+
+ private void cleanRoutes(NodeId node) {
+ log.info("Cleaning routes from unavailable node {}", node);
+
+ Collection<Route> routes = routeService.getRouteTables().stream()
+ .flatMap(id -> routeService.getRoutes(id).stream())
+ .flatMap(route -> route.allRoutes().stream())
+ .map(ResolvedRoute::route)
+ .filter(r -> r.sourceNode().equals(node))
+ .collect(Collectors.toList());
+
+ log.debug("Withdrawing routes: {}", routes);
+
+ routeService.withdraw(routes);
+ }
+
+ private class InternalClusterListener implements ClusterEventListener {
+
+ @Override
+ public void event(ClusterEvent event) {
+ switch (event.type()) {
+ case INSTANCE_DEACTIVATED:
+ NodeId id = event.subject().id();
+ log.info("Node {} deactivated", id);
+ queue.addOne(id);
+ break;
+ case INSTANCE_ADDED:
+ case INSTANCE_REMOVED:
+ case INSTANCE_ACTIVATED:
+ case INSTANCE_READY:
+ default:
+ break;
+ }
+ }
+ }
+
+}
diff --git a/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/package-info.java b/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/package-info.java
new file mode 100644
index 0000000..037bde4
--- /dev/null
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice.impl;
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
new file mode 100644
index 0000000..d42a05a
--- /dev/null
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/DefaultRouteTable.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.routeservice.store;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.routeservice.InternalRouteEvent;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteSet;
+import org.onosproject.routeservice.RouteStoreDelegate;
+import org.onosproject.routeservice.RouteTableId;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.DistributedPrimitive;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Default implementation of a route table based on a consistent map.
+ */
+public class DefaultRouteTable implements RouteTable {
+
+ private final RouteTableId id;
+ private final ConsistentMap<IpPrefix, Set<Route>> routes;
+ private final RouteStoreDelegate delegate;
+ private final ExecutorService executor;
+ private final RouteTableListener listener = new RouteTableListener();
+
+ private final Consumer<DistributedPrimitive.Status> statusChangeListener;
+
+ /**
+ * Creates a new route table.
+ *
+ * @param id route table ID
+ * @param delegate route store delegate to notify of events
+ * @param storageService storage service
+ * @param executor executor service
+ */
+ public DefaultRouteTable(RouteTableId id, RouteStoreDelegate delegate,
+ StorageService storageService, ExecutorService executor) {
+ this.delegate = checkNotNull(delegate);
+ this.id = checkNotNull(id);
+ this.routes = buildRouteMap(checkNotNull(storageService));
+ this.executor = checkNotNull(executor);
+
+ statusChangeListener = status -> {
+ if (status.equals(DistributedPrimitive.Status.ACTIVE)) {
+ executor.execute(this::notifyExistingRoutes);
+ }
+ };
+ routes.addStatusChangeListener(statusChangeListener);
+
+ notifyExistingRoutes();
+
+ routes.addListener(listener);
+ }
+
+ private void notifyExistingRoutes() {
+ routes.entrySet().stream()
+ .map(e -> new InternalRouteEvent(InternalRouteEvent.Type.ROUTE_ADDED,
+ new RouteSet(id, e.getKey(), e.getValue().value())))
+ .forEach(delegate::notify);
+ }
+
+ private ConsistentMap<IpPrefix, Set<Route>> buildRouteMap(StorageService storageService) {
+ KryoNamespace routeTableSerializer = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(Route.class)
+ .register(Route.Source.class)
+ .build();
+ return storageService.<IpPrefix, Set<Route>>consistentMapBuilder()
+ .withName("onos-routes-" + id.name())
+ .withRelaxedReadConsistency()
+ .withSerializer(Serializer.using(routeTableSerializer))
+ .build();
+ }
+
+ @Override
+ public RouteTableId id() {
+ return id;
+ }
+
+ @Override
+ public void shutdown() {
+ routes.removeStatusChangeListener(statusChangeListener);
+ routes.removeListener(listener);
+ }
+
+ @Override
+ public void destroy() {
+ shutdown();
+ routes.destroy();
+ }
+
+ @Override
+ public void update(Route route) {
+ routes.compute(route.prefix(), (prefix, set) -> {
+ if (set == null) {
+ set = new HashSet<>();
+ }
+ set.add(route);
+ return set;
+ });
+ }
+
+ @Override
+ public void remove(Route route) {
+ routes.compute(route.prefix(), (prefix, set) -> {
+ if (set != null) {
+ set.remove(route);
+ if (set.isEmpty()) {
+ return null;
+ }
+ return set;
+ }
+ return null;
+ });
+ }
+
+ @Override
+ public Collection<RouteSet> getRoutes() {
+ return routes.entrySet().stream()
+ .map(e -> new RouteSet(id, e.getKey(), e.getValue().value()))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public RouteSet getRoutes(IpPrefix prefix) {
+ Versioned<Set<Route>> routeSet = routes.get(prefix);
+
+ if (routeSet != null) {
+ return new RouteSet(id, prefix, routeSet.value());
+ }
+ return null;
+ }
+
+ @Override
+ public Collection<Route> getRoutesForNextHop(IpAddress nextHop) {
+ // TODO index
+ return routes.values().stream()
+ .flatMap(v -> v.value().stream())
+ .filter(r -> r.nextHop().equals(nextHop))
+ .collect(Collectors.toSet());
+ }
+
+ private class RouteTableListener
+ implements MapEventListener<IpPrefix, Set<Route>> {
+
+ private InternalRouteEvent createRouteEvent(
+ InternalRouteEvent.Type type, MapEvent<IpPrefix, Set<Route>> event) {
+ Set<Route> currentRoutes =
+ (event.newValue() == null) ? Collections.emptySet() : event.newValue().value();
+ return new InternalRouteEvent(type, new RouteSet(id, event.key(), currentRoutes));
+ }
+
+ @Override
+ public void event(MapEvent<IpPrefix, Set<Route>> event) {
+ InternalRouteEvent ire = null;
+ switch (event.type()) {
+ case INSERT:
+ ire = createRouteEvent(InternalRouteEvent.Type.ROUTE_ADDED, event);
+ break;
+ case UPDATE:
+ if (event.newValue().value().size() > event.oldValue().value().size()) {
+ ire = createRouteEvent(InternalRouteEvent.Type.ROUTE_ADDED, event);
+ } else {
+ ire = createRouteEvent(InternalRouteEvent.Type.ROUTE_REMOVED, event);
+ }
+ break;
+ case REMOVE:
+ ire = createRouteEvent(InternalRouteEvent.Type.ROUTE_REMOVED, event);
+ break;
+ default:
+ break;
+ }
+ if (ire != null) {
+ delegate.notify(ire);
+ }
+ }
+ }
+
+}
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
new file mode 100644
index 0000000..feacb82
--- /dev/null
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/DistributedRouteStore.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice.store;
+
+import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.routeservice.InternalRouteEvent;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteSet;
+import org.onosproject.routeservice.RouteStore;
+import org.onosproject.routeservice.RouteStoreDelegate;
+import org.onosproject.routeservice.RouteTableId;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.service.DistributedSet;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.SetEvent;
+import org.onosproject.store.service.SetEventListener;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.onlab.util.Tools.groupedThreads;
+
+/**
+ * Route store based on distributed storage.
+ */
+public class DistributedRouteStore extends AbstractStore<InternalRouteEvent, RouteStoreDelegate>
+ implements RouteStore {
+
+ protected StorageService storageService;
+
+ private static final RouteTableId IPV4 = new RouteTableId("ipv4");
+ private static final RouteTableId IPV6 = new RouteTableId("ipv6");
+ private static final Logger log = LoggerFactory.getLogger(DistributedRouteStore.class);
+ private final SetEventListener<RouteTableId> masterRouteTableListener =
+ new MasterRouteTableListener();
+ private final RouteStoreDelegate ourDelegate = new InternalRouteStoreDelegate();
+
+ // Stores the route tables that have been created
+ private DistributedSet<RouteTableId> masterRouteTable;
+ // Local memory map to store route table object
+ private Map<RouteTableId, RouteTable> routeTables;
+
+ private ExecutorService executor;
+
+ public DistributedRouteStore(StorageService storageService) {
+ this.storageService = storageService;
+ }
+
+ /**
+ * Sets up distributed route store.
+ */
+ public void activate() {
+ routeTables = new ConcurrentHashMap<>();
+ executor = Executors.newSingleThreadExecutor(groupedThreads("onos/route", "store", log));
+
+ KryoNamespace masterRouteTableSerializer = KryoNamespace.newBuilder()
+ .register(RouteTableId.class)
+ .build();
+
+ masterRouteTable = storageService.<RouteTableId>setBuilder()
+ .withName("onos-master-route-table")
+ .withSerializer(Serializer.using(masterRouteTableSerializer))
+ .build()
+ .asDistributedSet();
+
+ masterRouteTable.addListener(masterRouteTableListener);
+
+ // Add default tables (add is idempotent)
+ masterRouteTable.add(IPV4);
+ masterRouteTable.add(IPV6);
+
+ masterRouteTable.forEach(this::createRouteTable);
+
+ log.info("Started");
+ }
+
+ /**
+ * Cleans up distributed route store.
+ */
+ public void deactivate() {
+ masterRouteTable.removeListener(masterRouteTableListener);
+
+ routeTables.values().forEach(RouteTable::shutdown);
+
+ log.info("Stopped");
+ }
+
+ @Override
+ public void updateRoute(Route route) {
+ getDefaultRouteTable(route).update(route);
+ }
+
+ @Override
+ public void removeRoute(Route route) {
+ getDefaultRouteTable(route).remove(route);
+ }
+
+ @Override
+ public Set<RouteTableId> getRouteTables() {
+ return ImmutableSet.copyOf(masterRouteTable);
+ }
+
+ @Override
+ public Collection<RouteSet> getRoutes(RouteTableId table) {
+ RouteTable routeTable = routeTables.get(table);
+ if (routeTable == null) {
+ return Collections.emptySet();
+ } else {
+ return ImmutableSet.copyOf(routeTable.getRoutes());
+ }
+ }
+
+ @Override
+ public Collection<Route> getRoutesForNextHop(IpAddress ip) {
+ return getDefaultRouteTable(ip).getRoutesForNextHop(ip);
+ }
+
+ @Override
+ public RouteSet getRoutes(IpPrefix prefix) {
+ return getDefaultRouteTable(prefix.address()).getRoutes(prefix);
+ }
+
+ private void createRouteTable(RouteTableId tableId) {
+ routeTables.computeIfAbsent(tableId, id -> new DefaultRouteTable(id, ourDelegate, storageService, executor));
+ }
+
+ private void destroyRouteTable(RouteTableId tableId) {
+ RouteTable table = routeTables.remove(tableId);
+ if (table != null) {
+ table.destroy();
+ }
+ }
+
+ private RouteTable getDefaultRouteTable(Route route) {
+ return getDefaultRouteTable(route.prefix().address());
+ }
+
+ private RouteTable getDefaultRouteTable(IpAddress ip) {
+ RouteTableId routeTableId = (ip.isIp4()) ? IPV4 : IPV6;
+ return routeTables.getOrDefault(routeTableId, EmptyRouteTable.instance());
+ }
+
+ private class InternalRouteStoreDelegate implements RouteStoreDelegate {
+ @Override
+ public void notify(InternalRouteEvent event) {
+ executor.execute(() -> DistributedRouteStore.this.notifyDelegate(event));
+ }
+ }
+
+ private class MasterRouteTableListener implements SetEventListener<RouteTableId> {
+ @Override
+ public void event(SetEvent<RouteTableId> event) {
+ switch (event.type()) {
+ case ADD:
+ executor.execute(() -> createRouteTable(event.entry()));
+ break;
+ case REMOVE:
+ executor.execute(() -> destroyRouteTable(event.entry()));
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
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
new file mode 100644
index 0000000..3786675
--- /dev/null
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/EmptyRouteTable.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.routeservice.store;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteSet;
+import org.onosproject.routeservice.RouteTableId;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Route table that contains no routes.
+ */
+public final class EmptyRouteTable implements RouteTable {
+
+ private final RouteTableId id = new RouteTableId("empty");
+
+ private static final EmptyRouteTable INSTANCE = new EmptyRouteTable();
+
+ /**
+ * Returns the instance of the empty route table.
+ *
+ * @return empty route table
+ */
+ public static EmptyRouteTable instance() {
+ return INSTANCE;
+ }
+
+ private EmptyRouteTable() {
+ }
+
+ @Override
+ public void update(Route route) {
+
+ }
+
+ @Override
+ public void remove(Route route) {
+
+ }
+
+ @Override
+ public RouteTableId id() {
+ return id;
+ }
+
+ @Override
+ public Collection<RouteSet> getRoutes() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public RouteSet getRoutes(IpPrefix prefix) {
+ return null;
+ }
+
+ @Override
+ public Collection<Route> getRoutesForNextHop(IpAddress nextHop) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void shutdown() {
+
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+}
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
new file mode 100644
index 0000000..7884783
--- /dev/null
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/LocalRouteStore.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice.store;
+
+import com.googlecode.concurrenttrees.common.KeyValuePair;
+import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
+import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
+import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.routeservice.InternalRouteEvent;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteSet;
+import org.onosproject.routeservice.RouteStore;
+import org.onosproject.routeservice.RouteStoreDelegate;
+import org.onosproject.routeservice.RouteTableId;
+import org.onosproject.routeservice.RouteTools;
+import org.onosproject.store.AbstractStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Route store based on in-memory storage.
+ */
+public class LocalRouteStore extends AbstractStore<InternalRouteEvent, RouteStoreDelegate>
+ implements RouteStore {
+
+ private Logger log = LoggerFactory.getLogger(getClass());
+
+ private Map<RouteTableId, RouteTable> routeTables;
+ private static final RouteTableId IPV4 = new RouteTableId("ipv4");
+ private static final RouteTableId IPV6 = new RouteTableId("ipv6");
+
+ /**
+ * Sets up local route store.
+ */
+ public void activate() {
+ routeTables = new ConcurrentHashMap<>();
+
+ routeTables.put(IPV4, new RouteTable(IPV4));
+ routeTables.put(IPV6, new RouteTable(IPV6));
+
+ log.info("Started");
+ }
+
+ /**
+ * Cleans up local route store.
+ */
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ @Override
+ public void updateRoute(Route route) {
+ getDefaultRouteTable(route).update(route);
+ }
+
+ @Override
+ public void removeRoute(Route route) {
+ getDefaultRouteTable(route).remove(route);
+ }
+
+ @Override
+ public Set<RouteTableId> getRouteTables() {
+ return routeTables.keySet();
+ }
+
+ @Override
+ public Collection<RouteSet> getRoutes(RouteTableId table) {
+ RouteTable routeTable = routeTables.get(table);
+ if (routeTable != null) {
+ return routeTable.getRouteSets();
+ }
+ return null;
+ }
+
+ @Override
+ public Collection<Route> getRoutesForNextHop(IpAddress ip) {
+ return getDefaultRouteTable(ip).getRoutesForNextHop(ip);
+ }
+
+ @Override
+ public RouteSet getRoutes(IpPrefix prefix) {
+ return getDefaultRouteTable(prefix.address()).getRoutes(prefix);
+ }
+
+ 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);
+ }
+
+ /**
+ * 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 RouteTableId id;
+
+ /**
+ * Creates a new route table.
+ */
+ public RouteTable(RouteTableId id) {
+ this.id = checkNotNull(id);
+ 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);
+
+ // No need to proceed if the new route is the same
+ if (route.equals(oldRoute)) {
+ return;
+ }
+
+ routeTable.put(RouteTools.createBinaryString(route.prefix()), route);
+
+ notifyDelegate(new InternalRouteEvent(
+ InternalRouteEvent.Type.ROUTE_ADDED, singletonRouteSet(route)));
+ }
+ }
+
+ /**
+ * 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(RouteTools.createBinaryString(route.prefix()));
+
+ if (removed != null) {
+ notifyDelegate(new InternalRouteEvent(
+ InternalRouteEvent.Type.ROUTE_REMOVED, emptyRouteSet(route.prefix())));
+ }
+ }
+ }
+
+ /**
+ * 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 routes.values()
+ .stream()
+ .filter(route -> route.nextHop().equals(ip))
+ .collect(Collectors.toSet());
+ }
+
+ public RouteSet getRoutes(IpPrefix prefix) {
+ Route route = routes.get(prefix);
+ if (route != null) {
+ return singletonRouteSet(route);
+ }
+ return null;
+ }
+
+ public Collection<RouteSet> getRouteSets() {
+ return routes.values().stream()
+ .map(this::singletonRouteSet)
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * Returns all routes in the route table.
+ *
+ * @return all routes
+ */
+ public Collection<Route> getRoutes() {
+ Iterator<KeyValuePair<Route>> it =
+ routeTable.getKeyValuePairsForKeysStartingWith("").iterator();
+
+ List<Route> routes = new LinkedList<>();
+
+ while (it.hasNext()) {
+ KeyValuePair<Route> entry = it.next();
+ routes.add(entry.getValue());
+ }
+
+ return routes;
+ }
+
+ /**
+ * 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(RouteTools.createBinaryString(ip.toIpPrefix()));
+
+ Iterator<Route> it = prefixes.iterator();
+
+ Route route = null;
+ while (it.hasNext()) {
+ route = it.next();
+ }
+
+ return route;
+ }
+
+ private RouteSet singletonRouteSet(Route route) {
+ return new RouteSet(id, route.prefix(), Collections.singleton(route));
+ }
+
+ private RouteSet emptyRouteSet(IpPrefix prefix) {
+ return new RouteSet(id, prefix, Collections.emptySet());
+ }
+ }
+
+}
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
new file mode 100644
index 0000000..5c18b54
--- /dev/null
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/RouteStoreImpl.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.routeservice.store;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
+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.IpPrefix;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.routeservice.InternalRouteEvent;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteSet;
+import org.onosproject.routeservice.RouteStore;
+import org.onosproject.routeservice.RouteStoreDelegate;
+import org.onosproject.routeservice.RouteTableId;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.service.StorageService;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Set;
+
+/**
+ * An implementation of RouteStore that is backed by either LocalRouteStore or
+ * DistributedRouteStore according to configuration.
+ */
+@Service
+@Component
+public class RouteStoreImpl extends AbstractStore<InternalRouteEvent, RouteStoreDelegate>
+ implements RouteStore {
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ComponentConfigService componentConfigService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ public StorageService storageService;
+
+ @Property(name = "distributed", boolValue = false,
+ label = "Enable distributed route store")
+ private boolean distributed;
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+ private RouteStore currentRouteStore;
+
+ private DistributedRouteStore distributedRouteStore;
+ private LocalRouteStore localRouteStore;
+
+ @Activate
+ public void activate(ComponentContext context) {
+ distributedRouteStore = new DistributedRouteStore(storageService);
+ distributedRouteStore.activate();
+ localRouteStore = new LocalRouteStore();
+ localRouteStore.activate();
+
+ componentConfigService.registerProperties(getClass());
+ modified(context);
+ }
+
+ @Deactivate
+ public void deactivate() {
+ localRouteStore.deactivate();
+ distributedRouteStore.deactivate();
+
+ componentConfigService.unregisterProperties(getClass(), false);
+ }
+
+ @Modified
+ public void modified(ComponentContext context) {
+ Dictionary<?, ?> properties = context.getProperties();
+ if (properties == null) {
+ return;
+ }
+
+ String strDistributed = Tools.get(properties, "distributed");
+ boolean expectDistributed = Boolean.parseBoolean(strDistributed);
+
+ // Start route store during first start or config change
+ // NOTE: new route store will be empty
+ if (currentRouteStore == null || expectDistributed != distributed) {
+ if (expectDistributed) {
+ currentRouteStore = distributedRouteStore;
+ } else {
+ currentRouteStore = localRouteStore;
+ }
+
+ this.distributed = expectDistributed;
+ log.info("Switched to {} route store", distributed ? "distributed" : "local");
+ }
+
+ }
+
+ @Override
+ public void setDelegate(RouteStoreDelegate delegate) {
+ super.setDelegate(delegate);
+
+ // Set the delegate of underlying route store implementations
+ localRouteStore.setDelegate(delegate);
+ distributedRouteStore.setDelegate(delegate);
+ }
+
+ @Override
+ public void unsetDelegate(RouteStoreDelegate delegate) {
+ super.unsetDelegate(delegate);
+
+ // Unset the delegate of underlying route store implementations
+ localRouteStore.unsetDelegate(delegate);
+ distributedRouteStore.unsetDelegate(delegate);
+ }
+
+ @Override
+ public void updateRoute(Route route) {
+ currentRouteStore.updateRoute(route);
+ }
+
+ @Override
+ public void removeRoute(Route route) {
+ currentRouteStore.removeRoute(route);
+ }
+
+ @Override
+ public Set<RouteTableId> getRouteTables() {
+ return currentRouteStore.getRouteTables();
+ }
+
+ @Override
+ public Collection<RouteSet> getRoutes(RouteTableId table) {
+ return currentRouteStore.getRoutes(table);
+ }
+
+ @Override
+ public Collection<Route> getRoutesForNextHop(IpAddress ip) {
+ return currentRouteStore.getRoutesForNextHop(ip);
+ }
+
+ @Override
+ public RouteSet getRoutes(IpPrefix prefix) {
+ return currentRouteStore.getRoutes(prefix);
+ }
+
+ @Override
+ public String name() {
+ return currentRouteStore.name();
+ }
+}
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
new file mode 100644
index 0000000..fbe32c0
--- /dev/null
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/RouteTable.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.routeservice.store;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteSet;
+import org.onosproject.routeservice.RouteTableId;
+
+import java.util.Collection;
+
+/**
+ * Represents a route table that stores routes.
+ */
+public interface RouteTable {
+
+ /**
+ * Adds a route to the route table.
+ *
+ * @param route route
+ */
+ void update(Route route);
+
+ /**
+ * Removes a route from the route table.
+ *
+ * @param route route
+ */
+ void remove(Route route);
+
+ /**
+ * Returns the route table ID.
+ *
+ * @return route table ID
+ */
+ RouteTableId id();
+
+ /**
+ * Returns all routes in the route table.
+ *
+ * @return collection of routes, grouped by prefix
+ */
+ Collection<RouteSet> getRoutes();
+
+ /**
+ * Returns the routes in this table pertaining to a given prefix.
+ *
+ * @param prefix IP prefix
+ * @return routes for the prefix
+ */
+ RouteSet getRoutes(IpPrefix prefix);
+
+ /**
+ * Returns all routes that have the given next hop.
+ *
+ * @param nextHop next hop IP address
+ * @return collection of routes
+ */
+ Collection<Route> getRoutesForNextHop(IpAddress nextHop);
+
+ /**
+ * Releases route table resources held locally.
+ */
+ void shutdown();
+
+ /**
+ * Releases route table resources across the entire cluster.
+ */
+ void destroy();
+
+}
diff --git a/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/package-info.java b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/package-info.java
new file mode 100644
index 0000000..fbf31a3
--- /dev/null
+++ b/apps/route-service/app/src/main/java/org/onosproject/routeservice/store/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice.store;
diff --git a/apps/route-service/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/route-service/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..a020c3b
--- /dev/null
+++ b/apps/route-service/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,35 @@
+<!--
+ ~ Copyright 2014-present Open Networking Foundation
+ ~
+ ~ 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.
+ -->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+
+ <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+
+ <command>
+ <action class="org.onosproject.routeservice.cli.RoutesListCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.routeservice.cli.RouteAddCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.routeservice.cli.RouteRemoveCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.routeservice.cli.RouteStoreCommand"/>
+ </command>
+
+ </command-bundle>
+
+</blueprint>
diff --git a/apps/route-service/app/src/test/java/org/onosproject/routeservice/impl/RouteManagerTest.java b/apps/route-service/app/src/test/java/org/onosproject/routeservice/impl/RouteManagerTest.java
new file mode 100644
index 0000000..8febe68
--- /dev/null
+++ b/apps/route-service/app/src/test/java/org/onosproject/routeservice/impl/RouteManagerTest.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.routeservice.impl;
+
+import java.util.Collections;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.Ip6Prefix;
+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.RouteEvent;
+import org.onosproject.routeservice.RouteListener;
+import org.onosproject.routeservice.store.LocalRouteStore;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.HostServiceAdapter;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.WorkQueue;
+
+import com.google.common.collect.Sets;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.easymock.EasyMock.verify;
+
+/**
+ * Unit tests for the route manager.
+ */
+public class RouteManagerTest {
+
+ private static final ConnectPoint CP1 = new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000001"),
+ PortNumber.portNumber(1));
+
+ private static final IpPrefix V4_PREFIX1 = Ip4Prefix.valueOf("1.1.1.0/24");
+ private static final IpPrefix V4_PREFIX2 = Ip4Prefix.valueOf("2.2.2.0/24");
+ private static final IpPrefix V6_PREFIX1 = Ip6Prefix.valueOf("4000::/64");
+
+ private static final IpAddress V4_NEXT_HOP1 = Ip4Address.valueOf("192.168.10.1");
+ private static final IpAddress V4_NEXT_HOP2 = Ip4Address.valueOf("192.168.20.1");
+ private static final IpAddress V6_NEXT_HOP1 = Ip6Address.valueOf("1000::1");
+ private static final IpAddress V6_NEXT_HOP2 = Ip6Address.valueOf("2000::1");
+
+ private static final MacAddress MAC1 = MacAddress.valueOf("00:00:00:00:00:01");
+ private static final MacAddress MAC2 = MacAddress.valueOf("00:00:00:00:00:02");
+ private static final MacAddress MAC3 = MacAddress.valueOf("00:00:00:00:00:03");
+ private static final MacAddress MAC4 = MacAddress.valueOf("00:00:00:00:00:04");
+
+ private HostService hostService;
+
+ private RouteListener routeListener;
+ private HostListener hostListener;
+
+ private RouteManager routeManager;
+
+ @Before
+ public void setUp() throws Exception {
+ setUpHostService();
+
+ routeListener = createMock(RouteListener.class);
+
+ routeManager = new TestRouteManager();
+ routeManager.hostService = hostService;
+
+ routeManager.clusterService = createNiceMock(ClusterService.class);
+ replay(routeManager.clusterService);
+ routeManager.storageService = createNiceMock(StorageService.class);
+ expect(routeManager.storageService.getWorkQueue(anyString(), anyObject()))
+ .andReturn(createNiceMock(WorkQueue.class));
+ replay(routeManager.storageService);
+
+ LocalRouteStore routeStore = new LocalRouteStore();
+ routeStore.activate();
+ routeManager.routeStore = routeStore;
+ routeManager.activate();
+
+ routeManager.addListener(routeListener);
+ }
+
+ /**
+ * Sets up the host service with details of some hosts.
+ */
+ private void setUpHostService() {
+ hostService = createMock(HostService.class);
+
+ hostService.addListener(anyObject(HostListener.class));
+ expectLastCall().andDelegateTo(new TestHostService()).anyTimes();
+
+ Host host1 = createHost(MAC1, V4_NEXT_HOP1);
+ expectHost(host1);
+
+ Host host2 = createHost(MAC2, V4_NEXT_HOP2);
+ expectHost(host2);
+
+ Host host3 = createHost(MAC3, V6_NEXT_HOP1);
+ expectHost(host3);
+
+ Host host4 = createHost(MAC4, V6_NEXT_HOP2);
+ expectHost(host4);
+
+ replay(hostService);
+ }
+
+ /**
+ * Sets expectations on the host service for a given host.
+ *
+ * @param host host
+ */
+ private void expectHost(Host host) {
+ // Assume the host only has one IP address
+ IpAddress ip = host.ipAddresses().iterator().next();
+
+ expect(hostService.getHostsByIp(ip))
+ .andReturn(Sets.newHashSet(host)).anyTimes();
+ hostService.startMonitoringIp(ip);
+ expectLastCall().anyTimes();
+ }
+
+ /**
+ * Creates a host with the given parameters.
+ *
+ * @param macAddress MAC address
+ * @param ipAddress IP address
+ * @return new host
+ */
+ private Host createHost(MacAddress macAddress, IpAddress ipAddress) {
+ return new DefaultHost(ProviderId.NONE, HostId.NONE, macAddress,
+ VlanId.NONE, new HostLocation(CP1, 1),
+ Sets.newHashSet(ipAddress));
+ }
+
+ /**
+ * Adds a route to the route service without expecting any specific events
+ * to be sent to the route listeners.
+ *
+ * @param route route to add
+ */
+ private void addRoute(Route route) {
+ reset(routeListener);
+
+ routeListener.event(anyObject(RouteEvent.class));
+ expectLastCall().once();
+ replay(routeListener);
+
+ routeManager.update(Collections.singleton(route));
+
+ reset(routeListener);
+ }
+
+ /**
+ * Tests adding routes to the route manager.
+ */
+ @Test
+ public void testRouteAdd() {
+ Route route = new Route(Route.Source.STATIC, V4_PREFIX1, V4_NEXT_HOP1);
+ ResolvedRoute resolvedRoute = new ResolvedRoute(route, MAC1, CP1);
+
+ verifyRouteAdd(route, resolvedRoute);
+
+ route = new Route(Route.Source.STATIC, V6_PREFIX1, V6_NEXT_HOP1);
+ resolvedRoute = new ResolvedRoute(route, MAC3, CP1);
+
+ verifyRouteAdd(route, resolvedRoute);
+ }
+
+ /**
+ * Tests adding a new route and verifies that the correct event was sent
+ * to the route listener.
+ *
+ * @param route route to add
+ * @param resolvedRoute resolved route that should be sent to the route
+ * listener
+ */
+ private void verifyRouteAdd(Route route, ResolvedRoute resolvedRoute) {
+ reset(routeListener);
+
+ routeListener.event(event(RouteEvent.Type.ROUTE_ADDED, resolvedRoute));
+
+ replay(routeListener);
+
+ routeManager.update(Collections.singleton(route));
+
+ verify(routeListener);
+ }
+
+ /**
+ * Tests updating routes in the route manager.
+ */
+ @Test
+ public void testRouteUpdate() {
+ Route route = new Route(Route.Source.STATIC, V4_PREFIX1, V4_NEXT_HOP1);
+ Route updatedRoute = new Route(Route.Source.STATIC, V4_PREFIX1, V4_NEXT_HOP2);
+ ResolvedRoute resolvedRoute = new ResolvedRoute(route, MAC1, CP1);
+ ResolvedRoute updatedResolvedRoute = new ResolvedRoute(updatedRoute, MAC2, CP1);
+
+ verifyRouteUpdated(route, updatedRoute, resolvedRoute, updatedResolvedRoute);
+
+ // Different prefix pointing to the same next hop.
+ // In this case we expect to receive a ROUTE_UPDATED event.
+ route = new Route(Route.Source.STATIC, V4_PREFIX2, V4_NEXT_HOP1);
+ updatedRoute = new Route(Route.Source.STATIC, V4_PREFIX2, V4_NEXT_HOP2);
+ resolvedRoute = new ResolvedRoute(route, MAC1, CP1);
+ updatedResolvedRoute = new ResolvedRoute(updatedRoute, MAC2, CP1);
+
+ verifyRouteUpdated(route, updatedRoute, resolvedRoute, updatedResolvedRoute);
+
+ route = new Route(Route.Source.STATIC, V6_PREFIX1, V6_NEXT_HOP1);
+ updatedRoute = new Route(Route.Source.STATIC, V6_PREFIX1, V6_NEXT_HOP2);
+ resolvedRoute = new ResolvedRoute(route, MAC3, CP1);
+ updatedResolvedRoute = new ResolvedRoute(updatedRoute, MAC4, CP1);
+
+ verifyRouteUpdated(route, updatedRoute, resolvedRoute, updatedResolvedRoute);
+ }
+
+ /**
+ * Tests updating a route and verifies that the route listener receives a
+ * route updated event.
+ *
+ * @param original original route
+ * @param updated updated route
+ * @param resolvedRoute resolved route before update
+ * @param updatedResolvedRoute resolved route that is expected to be sent to
+ * the route listener
+ */
+ private void verifyRouteUpdated(Route original, Route updated,
+ ResolvedRoute resolvedRoute,
+ ResolvedRoute updatedResolvedRoute) {
+ // First add the original route
+ addRoute(original);
+
+ routeListener.event(event(RouteEvent.Type.ROUTE_UPDATED,
+ updatedResolvedRoute, resolvedRoute));
+ expectLastCall().once();
+
+ replay(routeListener);
+
+ routeManager.update(Collections.singleton(updated));
+
+ verify(routeListener);
+ }
+
+ /**
+ * Tests deleting routes from the route manager.
+ */
+ @Test
+ public void testRouteDelete() {
+ Route route = new Route(Route.Source.STATIC, V4_PREFIX1, V4_NEXT_HOP1);
+ ResolvedRoute removedResolvedRoute = new ResolvedRoute(route, MAC1, CP1);
+
+ verifyDelete(route, removedResolvedRoute);
+
+ route = new Route(Route.Source.STATIC, V6_PREFIX1, V6_NEXT_HOP1);
+ removedResolvedRoute = new ResolvedRoute(route, MAC3, CP1);
+
+ verifyDelete(route, removedResolvedRoute);
+ }
+
+ /**
+ * Tests deleting a route and verifies that the correct event is sent to
+ * the route listener.
+ *
+ * @param route route to delete
+ * @param removedResolvedRoute the resolved route being removed
+ */
+ private void verifyDelete(Route route, ResolvedRoute removedResolvedRoute) {
+ addRoute(route);
+
+ RouteEvent withdrawRouteEvent = new RouteEvent(RouteEvent.Type.ROUTE_REMOVED,
+ removedResolvedRoute);
+
+ reset(routeListener);
+ routeListener.event(withdrawRouteEvent);
+
+ replay(routeListener);
+
+ routeManager.withdraw(Collections.singleton(route));
+
+ verify(routeListener);
+ }
+
+ /**
+ * Tests adding a route entry where the HostService does not immediately
+ * know the MAC address of the next hop, but this is learnt later.
+ */
+ @Test
+ public void testAsyncRouteAdd() {
+ Route route = new Route(Route.Source.STATIC, V4_PREFIX1, V4_NEXT_HOP1);
+
+ // Host service will reply with no hosts when asked
+ reset(hostService);
+ expect(hostService.getHostsByIp(anyObject(IpAddress.class))).andReturn(
+ Collections.emptySet()).anyTimes();
+ hostService.startMonitoringIp(V4_NEXT_HOP1);
+ expectLastCall().anyTimes();
+ replay(hostService);
+
+ // Initially when we add the route, no route event will be sent because
+ // the host is not known
+ replay(routeListener);
+
+ routeManager.update(Collections.singleton(route));
+
+ verify(routeListener);
+
+ // Now when we send the event, we expect the FIB update to be sent
+ reset(routeListener);
+ routeListener.event(event(RouteEvent.Type.ROUTE_ADDED,
+ new ResolvedRoute(route, MAC1, CP1)));
+ replay(routeListener);
+
+ Host host = createHost(MAC1, V4_NEXT_HOP1);
+
+ // Set up the host service with a host
+ reset(hostService);
+ expect(hostService.getHostsByIp(V4_NEXT_HOP1)).andReturn(
+ Collections.singleton(host)).anyTimes();
+ hostService.startMonitoringIp(V4_NEXT_HOP1);
+ expectLastCall().anyTimes();
+ replay(hostService);
+
+ // Send in the host event
+ hostListener.event(new HostEvent(HostEvent.Type.HOST_ADDED, host));
+
+ verify(routeListener);
+ }
+
+ private static RouteEvent event(RouteEvent.Type type, ResolvedRoute subject) {
+ return event(type, subject, null);
+ }
+
+ private static RouteEvent event(RouteEvent.Type type, ResolvedRoute subject, ResolvedRoute prevSubject) {
+ return new RouteEvent(type, subject, prevSubject, Collections.singleton(subject));
+ }
+
+ /**
+ * Test host service that stores a reference to the host listener.
+ */
+ private class TestHostService extends HostServiceAdapter {
+ @Override
+ public void addListener(HostListener listener) {
+ hostListener = listener;
+ }
+ }
+
+ /**
+ * Test route manager that extends the real route manager and injects a test
+ * listener queue instead of the real listener queue.
+ */
+ private static class TestRouteManager extends RouteManager {
+ @Override
+ ListenerQueue createListenerQueue(RouteListener listener) {
+ return new TestListenerQueue(listener);
+ }
+ }
+
+ /**
+ * Test listener queue that simply passes route events straight through to
+ * the route listener on the caller's thread.
+ */
+ private static class TestListenerQueue implements ListenerQueue {
+
+ private final RouteListener listener;
+
+ public TestListenerQueue(RouteListener listener) {
+ this.listener = listener;
+ }
+
+ @Override
+ public void post(RouteEvent event) {
+ listener.event(event);
+ }
+
+ @Override
+ public void start() {
+ }
+
+ @Override
+ public void stop() {
+ }
+ }
+
+}
diff --git a/apps/route-service/pom.xml b/apps/route-service/pom.xml
new file mode 100644
index 0000000..d9383df
--- /dev/null
+++ b/apps/route-service/pom.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2015-present Open Networking Foundation
+ ~
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-apps</artifactId>
+ <version>1.11.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>onos-app-route-service</artifactId>
+ <packaging>pom</packaging>
+
+ <description>Route Service Application</description>
+
+ <modules>
+ <module>api</module>
+ <module>app</module>
+ </modules>
+
+</project>
diff --git a/apps/routing/common/BUCK b/apps/routing/common/BUCK
index 8483685..38167dc 100644
--- a/apps/routing/common/BUCK
+++ b/apps/routing/common/BUCK
@@ -5,6 +5,7 @@
'//cli:onos-cli',
'//incubator/api:onos-incubator-api',
'//apps/routing-api:onos-apps-routing-api',
+ '//apps/route-service/api:onos-apps-route-service-api',
'//core/common:onos-core-common',
]
diff --git a/apps/routing/common/pom.xml b/apps/routing/common/pom.xml
index c87bba2..47840c9 100644
--- a/apps/routing/common/pom.xml
+++ b/apps/routing/common/pom.xml
@@ -86,6 +86,11 @@
<artifactId>concurrent-trees</artifactId>
<scope>compile</scope>
</dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-app-route-service-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
</dependencies>
</project>
diff --git a/apps/routing/common/src/main/java/org/onosproject/routing/bgp/BgpRouteSelector.java b/apps/routing/common/src/main/java/org/onosproject/routing/bgp/BgpRouteSelector.java
index cc4a578..259ae3c 100644
--- a/apps/routing/common/src/main/java/org/onosproject/routing/bgp/BgpRouteSelector.java
+++ b/apps/routing/common/src/main/java/org/onosproject/routing/bgp/BgpRouteSelector.java
@@ -18,7 +18,7 @@
import org.onlab.packet.IpPrefix;
import org.onosproject.cluster.ClusterService;
-import org.onosproject.incubator.net.routing.Route;
+import org.onosproject.routeservice.Route;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/apps/routing/common/src/main/java/org/onosproject/routing/bgp/BgpSessionManager.java b/apps/routing/common/src/main/java/org/onosproject/routing/bgp/BgpSessionManager.java
index 7d2875f..a2521ba 100644
--- a/apps/routing/common/src/main/java/org/onosproject/routing/bgp/BgpSessionManager.java
+++ b/apps/routing/common/src/main/java/org/onosproject/routing/bgp/BgpSessionManager.java
@@ -38,8 +38,8 @@
import org.onlab.packet.Ip6Prefix;
import org.onlab.packet.IpPrefix;
import org.onosproject.cluster.ClusterService;
-import org.onosproject.incubator.net.routing.Route;
-import org.onosproject.incubator.net.routing.RouteAdminService;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteAdminService;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/apps/routing/common/src/test/java/org/onosproject/routing/bgp/BgpSessionManagerTest.java b/apps/routing/common/src/test/java/org/onosproject/routing/bgp/BgpSessionManagerTest.java
index 7fbbefb..7d3414d 100644
--- a/apps/routing/common/src/test/java/org/onosproject/routing/bgp/BgpSessionManagerTest.java
+++ b/apps/routing/common/src/test/java/org/onosproject/routing/bgp/BgpSessionManagerTest.java
@@ -38,7 +38,7 @@
import org.onosproject.cluster.ClusterService;
import org.onosproject.cluster.DefaultControllerNode;
import org.onosproject.cluster.NodeId;
-import org.onosproject.incubator.net.routing.RouteAdminService;
+import org.onosproject.routeservice.RouteAdminService;
import org.osgi.service.component.ComponentContext;
import java.net.InetAddress;
diff --git a/apps/routing/fibinstaller/BUCK b/apps/routing/fibinstaller/BUCK
index b77e51b..ee7bf98 100644
--- a/apps/routing/fibinstaller/BUCK
+++ b/apps/routing/fibinstaller/BUCK
@@ -2,12 +2,14 @@
'//lib:CORE_DEPS',
'//incubator/api:onos-incubator-api',
'//apps/routing-api:onos-apps-routing-api',
+ '//apps/route-service/api:onos-apps-route-service-api',
]
TEST_DEPS = [
'//lib:TEST_ADAPTERS',
'//incubator/api:onos-incubator-api-tests',
'//apps/routing-api:onos-apps-routing-api-tests',
+ '//apps/route-service/api:onos-apps-route-service-api-tests',
]
osgi_jar_with_tests (
@@ -27,4 +29,5 @@
url = 'http://onosproject.org',
description = 'Installs routing rules into switches',
included_bundles = BUNDLES,
+ required_apps = [ 'org.onosproject.route-service' ],
)
diff --git a/apps/routing/fibinstaller/pom.xml b/apps/routing/fibinstaller/pom.xml
index f9f7f4b..91213f7 100644
--- a/apps/routing/fibinstaller/pom.xml
+++ b/apps/routing/fibinstaller/pom.xml
@@ -26,6 +26,13 @@
<artifactId>onos-apps-routing-fibinstaller</artifactId>
<packaging>bundle</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-app-route-service-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
</project>
diff --git a/apps/routing/fibinstaller/src/main/java/org/onosproject/routing/fibinstaller/FibInstaller.java b/apps/routing/fibinstaller/src/main/java/org/onosproject/routing/fibinstaller/FibInstaller.java
index 65d35e2..e5e74c7 100644
--- a/apps/routing/fibinstaller/src/main/java/org/onosproject/routing/fibinstaller/FibInstaller.java
+++ b/apps/routing/fibinstaller/src/main/java/org/onosproject/routing/fibinstaller/FibInstaller.java
@@ -39,11 +39,11 @@
import org.onosproject.incubator.net.config.basics.McastConfig;
import org.onosproject.net.intf.Interface;
import org.onosproject.net.intf.InterfaceService;
-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.RouteListener;
-import org.onosproject.incubator.net.routing.RouteService;
+import org.onosproject.routeservice.ResolvedRoute;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteEvent;
+import org.onosproject.routeservice.RouteListener;
+import org.onosproject.routeservice.RouteService;
import org.onosproject.net.DeviceId;
import org.onosproject.net.config.ConfigFactory;
import org.onosproject.net.config.NetworkConfigEvent;
diff --git a/apps/routing/fibinstaller/src/test/java/org/onosproject/routing/fibinstaller/FibInstallerTest.java b/apps/routing/fibinstaller/src/test/java/org/onosproject/routing/fibinstaller/FibInstallerTest.java
index 32cfd59..d591d6d 100644
--- a/apps/routing/fibinstaller/src/test/java/org/onosproject/routing/fibinstaller/FibInstallerTest.java
+++ b/apps/routing/fibinstaller/src/test/java/org/onosproject/routing/fibinstaller/FibInstallerTest.java
@@ -35,11 +35,11 @@
import org.onosproject.net.intf.InterfaceListener;
import org.onosproject.net.intf.InterfaceService;
import org.onosproject.net.intf.InterfaceServiceAdapter;
-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.RouteListener;
-import org.onosproject.incubator.net.routing.RouteServiceAdapter;
+import org.onosproject.routeservice.ResolvedRoute;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteEvent;
+import org.onosproject.routeservice.RouteListener;
+import org.onosproject.routeservice.RouteServiceAdapter;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
diff --git a/apps/routing/fpm/BUCK b/apps/routing/fpm/BUCK
index a4396f7..7ea89b9 100644
--- a/apps/routing/fpm/BUCK
+++ b/apps/routing/fpm/BUCK
@@ -4,6 +4,7 @@
'//cli:onos-cli',
'//incubator/api:onos-incubator-api',
'//apps/routing-api:onos-apps-routing-api',
+ '//apps/route-service/api:onos-apps-route-service-api',
'//core/store/serializers:onos-core-serializers',
]
@@ -29,4 +30,5 @@
url = 'http://onosproject.org',
description = 'Receives routes from external routing daemon over FPM protocol',
included_bundles = BUNDLES,
+ required_apps = [ 'org.onosproject.route-service' ],
)
diff --git a/apps/routing/fpm/pom.xml b/apps/routing/fpm/pom.xml
index 972297f..37c81d8 100644
--- a/apps/routing/fpm/pom.xml
+++ b/apps/routing/fpm/pom.xml
@@ -49,6 +49,11 @@
<artifactId>onos-core-serializers</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-app-route-service-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
</dependencies>
</project>
diff --git a/apps/routing/fpm/src/main/java/org/onosproject/routing/fpm/FpmManager.java b/apps/routing/fpm/src/main/java/org/onosproject/routing/fpm/FpmManager.java
index fee01e8..178b489 100644
--- a/apps/routing/fpm/src/main/java/org/onosproject/routing/fpm/FpmManager.java
+++ b/apps/routing/fpm/src/main/java/org/onosproject/routing/fpm/FpmManager.java
@@ -44,8 +44,8 @@
import org.onosproject.cluster.ClusterService;
import org.onosproject.cluster.NodeId;
import org.onosproject.core.CoreService;
-import org.onosproject.incubator.net.routing.Route;
-import org.onosproject.incubator.net.routing.RouteAdminService;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteAdminService;
import org.onosproject.routing.fpm.protocol.FpmHeader;
import org.onosproject.routing.fpm.protocol.Netlink;
import org.onosproject.routing.fpm.protocol.RouteAttribute;
diff --git a/apps/sdnip/BUCK b/apps/sdnip/BUCK
index 0067e19..08f02a4 100644
--- a/apps/sdnip/BUCK
+++ b/apps/sdnip/BUCK
@@ -2,6 +2,7 @@
'//lib:CORE_DEPS',
'//incubator/api:onos-incubator-api',
'//apps/routing-api:onos-apps-routing-api',
+ '//apps/route-service/api:onos-apps-route-service-api',
'//apps/intentsync:onos-apps-intentsync',
'//lib:org.apache.karaf.shell.console',
'//cli:onos-cli'
@@ -15,8 +16,8 @@
TEST_DEPS = [
'//lib:TEST_ADAPTERS',
- '//incubator/api:onos-incubator-api-tests',
'//apps/routing-api:onos-apps-routing-api-tests',
+ '//apps/route-service/api:onos-apps-route-service-api-tests',
]
osgi_jar_with_tests (
@@ -30,5 +31,5 @@
url = 'http://onosproject.org',
included_bundles = BUNDLES,
description = 'SDN-IP peering application',
- required_apps = [ 'org.onosproject.intentsynchronizer' ],
+ required_apps = [ 'org.onosproject.intentsynchronizer', 'org.onosproject.route-service' ],
)
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpFib.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpFib.java
index 8488924..9a4369e 100644
--- a/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpFib.java
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpFib.java
@@ -34,10 +34,10 @@
import org.onosproject.net.intf.InterfaceEvent;
import org.onosproject.net.intf.InterfaceListener;
import org.onosproject.net.intf.InterfaceService;
-import org.onosproject.incubator.net.routing.ResolvedRoute;
-import org.onosproject.incubator.net.routing.RouteEvent;
-import org.onosproject.incubator.net.routing.RouteListener;
-import org.onosproject.incubator.net.routing.RouteService;
+import org.onosproject.routeservice.ResolvedRoute;
+import org.onosproject.routeservice.RouteEvent;
+import org.onosproject.routeservice.RouteListener;
+import org.onosproject.routeservice.RouteService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.EncapsulationType;
import org.onosproject.net.FilteredConnectPoint;
diff --git a/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java b/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java
index d50aebc..8a5868a 100644
--- a/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java
+++ b/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java
@@ -37,11 +37,11 @@
import org.onosproject.net.intf.InterfaceListener;
import org.onosproject.net.intf.InterfaceService;
import org.onosproject.net.intf.InterfaceServiceAdapter;
-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.RouteListener;
-import org.onosproject.incubator.net.routing.RouteServiceAdapter;
+import org.onosproject.routeservice.ResolvedRoute;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteEvent;
+import org.onosproject.routeservice.RouteListener;
+import org.onosproject.routeservice.RouteServiceAdapter;
import org.onosproject.intentsync.IntentSynchronizationService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
diff --git a/apps/segmentrouting/BUCK b/apps/segmentrouting/BUCK
index 3f4e968..d068897 100644
--- a/apps/segmentrouting/BUCK
+++ b/apps/segmentrouting/BUCK
@@ -6,6 +6,7 @@
'//core/store/serializers:onos-core-serializers',
'//incubator/api:onos-incubator-api',
'//utils/rest:onlab-rest',
+ '//apps/route-service/api:onos-apps-route-service-api',
]
BUNDLES = [
@@ -29,4 +30,5 @@
url = 'http://onosproject.org',
included_bundles = BUNDLES,
description = 'Segment routing application.',
+ required_apps = [ 'org.onosproject.route-service' ],
)
diff --git a/apps/segmentrouting/pom.xml b/apps/segmentrouting/pom.xml
index c37c80a..50149a0 100644
--- a/apps/segmentrouting/pom.xml
+++ b/apps/segmentrouting/pom.xml
@@ -107,6 +107,11 @@
<scope>test</scope>
<classifier>tests</classifier>
</dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-app-route-service-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
</dependencies>
<build>
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
index 9402af5..017e7b0 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
@@ -29,7 +29,7 @@
import org.onlab.packet.ndp.NeighborSolicitation;
import org.onosproject.net.neighbour.NeighbourMessageContext;
import org.onosproject.net.neighbour.NeighbourMessageType;
-import org.onosproject.incubator.net.routing.ResolvedRoute;
+import org.onosproject.routeservice.ResolvedRoute;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.flow.DefaultTrafficTreatment;
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RouteHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RouteHandler.java
index 3f7b8ad..c82b39b 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RouteHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RouteHandler.java
@@ -22,8 +22,8 @@
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
-import org.onosproject.incubator.net.routing.ResolvedRoute;
-import org.onosproject.incubator.net.routing.RouteEvent;
+import org.onosproject.routeservice.ResolvedRoute;
+import org.onosproject.routeservice.RouteEvent;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.slf4j.Logger;
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index 1f63502..0d1d68b 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -40,9 +40,9 @@
import org.onosproject.incubator.net.config.basics.McastConfig;
import org.onosproject.net.intf.Interface;
import org.onosproject.net.intf.InterfaceService;
-import org.onosproject.incubator.net.routing.RouteEvent;
-import org.onosproject.incubator.net.routing.RouteListener;
-import org.onosproject.incubator.net.routing.RouteService;
+import org.onosproject.routeservice.RouteEvent;
+import org.onosproject.routeservice.RouteListener;
+import org.onosproject.routeservice.RouteService;
import org.onosproject.net.neighbour.NeighbourResolutionService;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.ConnectPoint;