Extend ControlPlaneRedirect (vRouter) to allow multiple redirects

CORD-787

Change-Id: I9e5a6f74b7239eb0dcb42b52e562698ce415debc
diff --git a/apps/routing-api/src/main/java/org/onosproject/routing/AsyncDeviceFetcher.java b/apps/routing-api/src/main/java/org/onosproject/routing/AsyncDeviceFetcher.java
index fd5dfc3..65ffec0 100644
--- a/apps/routing-api/src/main/java/org/onosproject/routing/AsyncDeviceFetcher.java
+++ b/apps/routing-api/src/main/java/org/onosproject/routing/AsyncDeviceFetcher.java
@@ -25,6 +25,8 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 /**
  * Provides a means of asynchronously waiting on devices.
  */
@@ -37,7 +39,7 @@
     private Map<DeviceId, CompletableFuture<DeviceId>> devices = new ConcurrentHashMap();
 
     private AsyncDeviceFetcher(DeviceService deviceService) {
-        this.deviceService = deviceService;
+        this.deviceService = checkNotNull(deviceService);
         deviceService.addListener(listener);
     }
 
diff --git a/apps/routing-api/src/main/java/org/onosproject/routing/InterfaceProvisionRequest.java b/apps/routing-api/src/main/java/org/onosproject/routing/InterfaceProvisionRequest.java
new file mode 100644
index 0000000..4815236
--- /dev/null
+++ b/apps/routing-api/src/main/java/org/onosproject/routing/InterfaceProvisionRequest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.routing;
+
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.net.ConnectPoint;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encapsulates information needed to provision a router interface.
+ */
+public final class InterfaceProvisionRequest {
+
+    private final RouterInfo info;
+    private final Interface intf;
+
+    private InterfaceProvisionRequest(RouterInfo info, Interface intf) {
+        this.info = checkNotNull(info);
+        this.intf = checkNotNull(intf);
+    }
+
+    /**
+     * Retrieves the router's control plane connect point.
+     *
+     * @return connect point
+     */
+    public ConnectPoint controlPlaneConnectPoint() {
+        return info.controlPlaneConnectPoint();
+    }
+
+    /**
+     * Retrieves the router configuration info.
+     *
+     * @return router configuration info
+     */
+    public RouterInfo info() {
+        return info;
+    }
+
+    /**
+     * Retrieves the interface to be (un)provisioned.
+     *
+     * @return interface
+     */
+    public Interface intf() {
+        return intf;
+    }
+
+    /**
+     * Creates a new provision request from a router configuration and an
+     * interface.
+     *
+     * @param info router configuration info
+     * @param intf interface
+     * @return provision request
+     */
+    public static InterfaceProvisionRequest of(RouterInfo info, Interface intf) {
+        return new InterfaceProvisionRequest(info, intf);
+    }
+}
diff --git a/apps/routing-api/src/main/java/org/onosproject/routing/RouterInterfaceManager.java b/apps/routing-api/src/main/java/org/onosproject/routing/Router.java
similarity index 65%
rename from apps/routing-api/src/main/java/org/onosproject/routing/RouterInterfaceManager.java
rename to apps/routing-api/src/main/java/org/onosproject/routing/Router.java
index 8b1deff..97fee88 100644
--- a/apps/routing-api/src/main/java/org/onosproject/routing/RouterInterfaceManager.java
+++ b/apps/routing-api/src/main/java/org/onosproject/routing/Router.java
@@ -16,17 +16,16 @@
 
 package org.onosproject.routing;
 
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import org.onosproject.incubator.net.intf.Interface;
 import org.onosproject.incubator.net.intf.InterfaceEvent;
 import org.onosproject.incubator.net.intf.InterfaceListener;
 import org.onosproject.incubator.net.intf.InterfaceService;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -35,46 +34,58 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Manages which interfaces are part of the router when the configuration is
- * updated, and handles the provisioning/unprovisioning of interfaces when they
+ * Manages the configuration and provisioning of a single-device router.
+ * It maintains which interfaces are part of the router when the configuration
+ * changes, and handles the provisioning/unprovisioning of interfaces when they
  * are added/removed.
  */
-public class RouterInterfaceManager {
+public class Router {
 
     private final Logger log = LoggerFactory.getLogger(getClass());
 
-    private final Consumer<Interface> provisioner;
-    private final Consumer<Interface> unprovisioner;
+    private final Consumer<InterfaceProvisionRequest> provisioner;
+    private final Consumer<InterfaceProvisionRequest> unprovisioner;
 
-    private Set<String> configuredInterfaces = Collections.emptySet();
+    private RouterInfo info;
+
     private Set<Interface> provisioned = new HashSet<>();
 
     private InterfaceService interfaceService;
     private InterfaceListener listener = new InternalInterfaceListener();
 
-    private final DeviceId routerDeviceId;
+    private AsyncDeviceFetcher asyncDeviceFetcher;
+
+    private volatile boolean deviceAvailable = false;
 
     /**
      * Creates a new router interface manager.
      *
-     * @param deviceId router device ID
-     * @param configuredInterfaces names of interfaces configured for this router
+     * @param info router configuration information
      * @param interfaceService interface service
+     * @param deviceService device service
      * @param provisioner consumer that will provision new interfaces
      * @param unprovisioner consumer that will unprovision old interfaces
      */
-    public RouterInterfaceManager(DeviceId deviceId,
-                                  Set<String> configuredInterfaces,
-                                  InterfaceService interfaceService,
-                                  Consumer<Interface> provisioner,
-                                  Consumer<Interface> unprovisioner) {
-        this.routerDeviceId = checkNotNull(deviceId);
+    public Router(RouterInfo info,
+                  InterfaceService interfaceService,
+                  DeviceService deviceService,
+                  Consumer<InterfaceProvisionRequest> provisioner,
+                  Consumer<InterfaceProvisionRequest> unprovisioner) {
+        this.info = checkNotNull(info);
         this.provisioner = checkNotNull(provisioner);
         this.unprovisioner = checkNotNull(unprovisioner);
         this.interfaceService = checkNotNull(interfaceService);
-        this.configuredInterfaces = checkNotNull(configuredInterfaces);
 
-        provision();
+        this.asyncDeviceFetcher = AsyncDeviceFetcher.create(deviceService);
+        asyncDeviceFetcher.getDevice(info.deviceId())
+                .thenAccept(deviceId1 -> {
+                    deviceAvailable = true;
+                    provision();
+                }).whenComplete((v, t) -> {
+                    if (t != null) {
+                        log.error("Error provisioning: ", t);
+                    }
+                });
 
         interfaceService.addListener(listener);
     }
@@ -89,33 +100,34 @@
     }
 
     /**
-     * Retrieves the set of configured interface names.
+     * Retrieves the router configuration information.
      *
-     * @return interface names
+     * @return router configuration information
      */
-    public Set<String> configuredInterfaces() {
-        return configuredInterfaces;
+    public RouterInfo info() {
+        return info;
     }
 
     /**
-     * Changes the set of interfaces configured on the router.
+     * Changes the router configuration.
      *
-     * @param newConfiguredInterfaces new set of router interfaces
+     * @param newConfig new configuration
      */
-    public void changeConfiguredInterfaces(Set<String> newConfiguredInterfaces) {
-        Set<String> oldConfiguredInterfaces = configuredInterfaces;
-        configuredInterfaces = ImmutableSet.copyOf(newConfiguredInterfaces);
+    public void changeConfiguration(RouterInfo newConfig) {
+        Set<String> oldConfiguredInterfaces = info.interfaces();
+        info = newConfig;
+        Set<String> newConfiguredInterfaces = info.interfaces();
 
         if (newConfiguredInterfaces.isEmpty() && !oldConfiguredInterfaces.isEmpty()) {
             // Reverted to using all interfaces. Provision interfaces that
             // weren't previously in the configured list
-            getInterfacesForDevice(routerDeviceId)
+            getInterfacesForDevice(info.deviceId())
                     .filter(intf -> !oldConfiguredInterfaces.contains(intf.name()))
                     .forEach(this::provision);
         } else if (!newConfiguredInterfaces.isEmpty() && oldConfiguredInterfaces.isEmpty()) {
             // Began using an interface list. Unprovision interfaces that
             // are not in the new interface list.
-            getInterfacesForDevice(routerDeviceId)
+            getInterfacesForDevice(info.deviceId())
                     .filter(intf -> !newConfiguredInterfaces.contains(intf.name()))
                     .forEach(this::unprovision);
         } else {
@@ -124,39 +136,37 @@
             Set<String> toProvision = Sets.difference(newConfiguredInterfaces, oldConfiguredInterfaces);
 
             toUnprovision.forEach(name ->
-                    getInterfacesForDevice(routerDeviceId)
+                    getInterfacesForDevice(info.deviceId())
                             .filter(intf -> intf.name().equals(name))
                             .findFirst()
                             .ifPresent(this::unprovision)
             );
 
             toProvision.forEach(name ->
-                    getInterfacesForDevice(routerDeviceId)
+                    getInterfacesForDevice(info.deviceId())
                             .filter(intf -> intf.name().equals(name))
                             .findFirst()
                             .ifPresent(this::provision)
             );
         }
-
-        configuredInterfaces = newConfiguredInterfaces;
     }
 
     private void provision() {
-        getInterfacesForDevice(routerDeviceId)
-                .filter(this::shouldUse)
+        getInterfacesForDevice(info.deviceId())
+                .filter(this::shouldProvision)
                 .forEach(this::provision);
     }
 
     private void unprovision() {
-        getInterfacesForDevice(routerDeviceId)
-                .filter(this::shouldUse)
+        getInterfacesForDevice(info.deviceId())
+                .filter(this::shouldProvision)
                 .forEach(this::unprovision);
     }
 
     private void provision(Interface intf) {
-        if (!provisioned.contains(intf) && shouldUse(intf)) {
+        if (!provisioned.contains(intf) && shouldProvision(intf)) {
             log.info("Provisioning interface {}", intf);
-            provisioner.accept(intf);
+            provisioner.accept(InterfaceProvisionRequest.of(info, intf));
             provisioned.add(intf);
         }
     }
@@ -164,13 +174,14 @@
     private void unprovision(Interface intf) {
         if (provisioned.contains(intf)) {
             log.info("Unprovisioning interface {}", intf);
-            unprovisioner.accept(intf);
+            unprovisioner.accept(InterfaceProvisionRequest.of(info, intf));
             provisioned.remove(intf);
         }
     }
 
-    private boolean shouldUse(Interface intf) {
-        return configuredInterfaces.isEmpty() || configuredInterfaces.contains(intf.name());
+    private boolean shouldProvision(Interface intf) {
+        return deviceAvailable &&
+                (info.interfaces().isEmpty() || info.interfaces().contains(intf.name()));
     }
 
     private Stream<Interface> getInterfacesForDevice(DeviceId deviceId) {
diff --git a/apps/routing-api/src/main/java/org/onosproject/routing/RouterInfo.java b/apps/routing-api/src/main/java/org/onosproject/routing/RouterInfo.java
new file mode 100644
index 0000000..df44a37
--- /dev/null
+++ b/apps/routing-api/src/main/java/org/onosproject/routing/RouterInfo.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.routing;
+
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.routing.config.RoutersConfig;
+
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Stores configuration information about a router.
+ */
+public class RouterInfo {
+
+    private final ConnectPoint controlPlaneConnectPoint;
+    private final boolean ospfEnabled;
+    private final Set<String> interfaces;
+
+    /**
+     * Creates a new router info.
+     *
+     * @param controlPlaneConnectPoint control plane connect point
+     * @param ospfEnabled whether OSPF is enabled
+     * @param interfaces set of interface names
+     */
+    public RouterInfo(ConnectPoint controlPlaneConnectPoint, boolean ospfEnabled, Set<String> interfaces) {
+        this.controlPlaneConnectPoint = checkNotNull(controlPlaneConnectPoint);
+        this.ospfEnabled = checkNotNull(ospfEnabled);
+        this.interfaces = ImmutableSet.copyOf(checkNotNull(interfaces));
+    }
+
+    /**
+     * Returns the control plane connect point.
+     *
+     * @return connect point
+     */
+    public ConnectPoint controlPlaneConnectPoint() {
+        return controlPlaneConnectPoint;
+    }
+
+    /**
+     * Returns the router device ID.
+     *
+     * @return device ID
+     */
+    public DeviceId deviceId() {
+        return controlPlaneConnectPoint.deviceId();
+    }
+
+    /**
+     * Returns whether OSPF is enabled on the router.
+     *
+     * @return OSPF enabled
+     */
+    public boolean ospfEnabled() {
+        return ospfEnabled;
+    }
+
+    /**
+     * Returns the set of interfaces belonging to the router.
+     *
+     * @return set of interface names
+     */
+    public Set<String> interfaces() {
+        return interfaces;
+    }
+
+    /**
+     * Creates a router info from a router config.
+     *
+     * @param config router config
+     * @return new router info object
+     */
+    public static RouterInfo from(RoutersConfig.Router config) {
+        return new RouterInfo(config.controlPlaneConnectPoint(), config.isOspfEnabled(), config.interfaces());
+    }
+}
diff --git a/apps/routing-api/src/main/java/org/onosproject/routing/config/RouterConfigHelper.java b/apps/routing-api/src/main/java/org/onosproject/routing/config/RouterConfigHelper.java
new file mode 100644
index 0000000..ddfd1db
--- /dev/null
+++ b/apps/routing-api/src/main/java/org/onosproject/routing/config/RouterConfigHelper.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.routing.config;
+
+import com.google.common.collect.Sets;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.routing.RoutingService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Helper class to manage retrieving config from multiple Config sections.
+ * Should be unnecessary once old config is removed.
+ */
+public final class RouterConfigHelper {
+
+    private static final String WARNING =
+            "Config apps/org.onosproject.routing/router is deprecated "
+                    + "and will be removed in a future release.";
+    private static final String WARNING2 =
+            "Use apps/org.onosproject.routing/routers instead";
+
+    private static final Logger log = LoggerFactory.getLogger(RouterConfigHelper.class);
+
+    private RouterConfigHelper() {
+        // make checkstyle happy
+    }
+
+    /**
+     * Retrieves the router configurations.
+     *
+     * @param configService network config service
+     * @param routingAppId routing app ID
+     * @return set of router configurations
+     */
+    public static Set<RoutersConfig.Router> getRouterConfigurations(
+            NetworkConfigService configService, ApplicationId routingAppId) {
+
+        RouterConfig config = configService.getConfig(
+                routingAppId, RoutingService.ROUTER_CONFIG_CLASS);
+        RoutersConfig multiConfig = configService.getConfig(routingAppId, RoutersConfig.class);
+
+        if (config != null) {
+            log.warn(WARNING);
+            log.warn(WARNING2);
+
+            return Collections.singleton(
+                    new RoutersConfig.Router(config.getControlPlaneConnectPoint(),
+                            config.getOspfEnabled(),
+                            Sets.newHashSet(config.getInterfaces())));
+        } else if (multiConfig != null) {
+            return multiConfig.getRouters();
+        } else {
+            return Collections.emptySet();
+        }
+    }
+}
diff --git a/apps/routing-api/src/main/java/org/onosproject/routing/config/RoutersConfig.java b/apps/routing-api/src/main/java/org/onosproject/routing/config/RoutersConfig.java
index 940a727..0a60023 100644
--- a/apps/routing-api/src/main/java/org/onosproject/routing/config/RoutersConfig.java
+++ b/apps/routing-api/src/main/java/org/onosproject/routing/config/RoutersConfig.java
@@ -19,6 +19,7 @@
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.config.Config;
@@ -50,19 +51,18 @@
 
 
             JsonNode intfNode = routerNode.path(INTERFACES);
-            if (intfNode.isMissingNode() || !intfNode.isArray()) {
-                continue;
-            }
-            ArrayNode array = (ArrayNode) intfNode;
             Set<String> interfaces = new HashSet<>(array.size());
-            for (JsonNode intf : array) {
-                interfaces.add(intf.asText());
+            if (!intfNode.isMissingNode()) {
+                ArrayNode array = (ArrayNode) intfNode;
+                for (JsonNode intf : array) {
+                    interfaces.add(intf.asText());
+                }
             }
 
             routers.add(new Router(connectPoint, ospfEnabled, interfaces));
         }
 
-        return routers;
+        return ImmutableSet.copyOf(routers);
     }
 
     @Override
diff --git a/apps/routing/cpr/src/main/java/org/onosproject/routing/cpr/ControlPlaneRedirectManager.java b/apps/routing/cpr/src/main/java/org/onosproject/routing/cpr/ControlPlaneRedirectManager.java
index 920c21b..0c97bb1 100644
--- a/apps/routing/cpr/src/main/java/org/onosproject/routing/cpr/ControlPlaneRedirectManager.java
+++ b/apps/routing/cpr/src/main/java/org/onosproject/routing/cpr/ControlPlaneRedirectManager.java
@@ -19,7 +19,6 @@
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -37,7 +36,6 @@
 import org.onosproject.incubator.net.intf.Interface;
 import org.onosproject.incubator.net.intf.InterfaceService;
 import org.onosproject.mastership.MastershipService;
-import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
 import org.onosproject.net.PortNumber;
@@ -58,10 +56,13 @@
 import org.onosproject.net.host.HostListener;
 import org.onosproject.net.host.HostService;
 import org.onosproject.net.host.InterfaceIpAddress;
-import org.onosproject.routing.AsyncDeviceFetcher;
-import org.onosproject.routing.RouterInterfaceManager;
+import org.onosproject.routing.RouterInfo;
+import org.onosproject.routing.InterfaceProvisionRequest;
+import org.onosproject.routing.Router;
 import org.onosproject.routing.RoutingService;
-import org.onosproject.routing.config.RouterConfig;
+import org.onosproject.routing.config.RouterConfigHelper;
+import org.onosproject.routing.config.RoutersConfig;
+import org.onosproject.routing.config.RoutingConfigurationService;
 import org.slf4j.Logger;
 
 import java.util.Iterator;
@@ -69,6 +70,7 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 import static com.google.common.base.Preconditions.checkState;
 import static org.onlab.packet.Ethernet.TYPE_ARP;
@@ -122,15 +124,15 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ApplicationService applicationService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected RoutingConfigurationService rs;
+
     private static final String APP_NAME = "org.onosproject.cpr";
     private ApplicationId appId;
 
-    private ConnectPoint controlPlaneConnectPoint;
-    private boolean ospfEnabled = false;
     private Map<Host, Set<Integer>> peerNextId = Maps.newConcurrentMap();
 
-    private RouterInterfaceManager interfaceManager;
-    private AsyncDeviceFetcher asyncDeviceFetcher;
+    private Map<DeviceId, Router> routers = new ConcurrentHashMap<>();
 
     private final InternalNetworkConfigListener networkConfigListener =
             new InternalNetworkConfigListener();
@@ -143,22 +145,16 @@
         networkConfigService.addListener(networkConfigListener);
         hostService.addListener(hostListener);
 
-        asyncDeviceFetcher = AsyncDeviceFetcher.create(deviceService);
-
         processRouterConfig();
 
-        applicationService.registerDeactivateHook(this.appId, () -> {
-            if (interfaceManager != null) {
-                interfaceManager.cleanup();
-            }
-        });
+        applicationService.registerDeactivateHook(this.appId,
+                () -> routers.forEach((d, r) -> r.cleanup()));
     }
 
     @Deactivate
     protected void deactivate() {
         networkConfigService.removeListener(networkConfigListener);
         hostService.removeListener(hostListener);
-        asyncDeviceFetcher.shutdown();
     }
 
     /**
@@ -168,52 +164,48 @@
         ApplicationId routingAppId =
                 coreService.registerApplication(RoutingService.ROUTER_APP_ID);
 
-        RouterConfig config = networkConfigService.getConfig(
-                routingAppId, RoutingService.ROUTER_CONFIG_CLASS);
+        Set<RoutersConfig.Router> routerConfigs =
+                RouterConfigHelper.getRouterConfigurations(networkConfigService, routingAppId);
 
-        if (config == null) {
-            log.warn("Router config not available");
-            return;
+        for (RoutersConfig.Router router : routerConfigs) {
+            DeviceId deviceId = router.controlPlaneConnectPoint().deviceId();
+
+            routers.compute(deviceId, (d, r) -> {
+                if (r == null) {
+                    return createRouter(RouterInfo.from(router));
+                } else {
+                    r.changeConfiguration(RouterInfo.from(router));
+                    return r;
+                }
+            });
         }
 
-        if (interfaceManager == null) {
-            controlPlaneConnectPoint = config.getControlPlaneConnectPoint();
-            ospfEnabled = config.getOspfEnabled();
-
-            DeviceId deviceId = config.getControlPlaneConnectPoint().deviceId();
-
-            asyncDeviceFetcher.getDevice(deviceId)
-                    .thenAccept(deviceId1 ->
-                            interfaceManager = createRouter(deviceId,
-                                    Sets.newHashSet(config.getInterfaces())));
-
-        } else {
-            interfaceManager.changeConfiguredInterfaces(Sets.newHashSet(config.getInterfaces()));
+        for (DeviceId deviceId : routers.keySet()) {
+            if (!configExists(deviceId, routerConfigs)) {
+                Router router = routers.remove(deviceId);
+                router.cleanup();
+            }
         }
     }
 
-    /**
-     * Cleans up after router config was removed.
-     */
-    private void removeRouterConfig() {
-        if (interfaceManager != null) {
-            interfaceManager.cleanup();
-        }
+    private boolean configExists(DeviceId deviceId, Set<RoutersConfig.Router> config) {
+        return config.stream()
+                .anyMatch(r -> r.controlPlaneConnectPoint().deviceId().equals(deviceId));
     }
 
-    private RouterInterfaceManager createRouter(DeviceId deviceId, Set<String> configuredInterfaces) {
-        return new RouterInterfaceManager(deviceId,
-                configuredInterfaces,
+    private Router createRouter(RouterInfo info) {
+        return new Router(info,
                 interfaceService,
+                deviceService,
                 this::provisionInterface,
                 this::unprovisionInterface);
     }
 
-    private void provisionInterface(Interface intf) {
+    private void provisionInterface(InterfaceProvisionRequest intf) {
         updateInterfaceObjectives(intf, true);
     }
 
-    private void unprovisionInterface(Interface intf) {
+    private void unprovisionInterface(InterfaceProvisionRequest intf) {
         updateInterfaceObjectives(intf, false);
     }
 
@@ -223,7 +215,7 @@
      * @param intf interface to change objectives for
      * @param install true to install the objectives, false to remove them
      */
-    private void updateInterfaceObjectives(Interface intf, boolean install) {
+    private void updateInterfaceObjectives(InterfaceProvisionRequest intf, boolean install) {
         updateInterfaceForwarding(intf, install);
         updateOspfForwarding(intf, install);
     }
@@ -231,14 +223,16 @@
     /**
      * Installs or removes the basic forwarding flows for each interface.
      *
-     * @param intf the Interface on which event is received
+     * @param request provisioning request containing router and interface
      * @param install true to install the objectives, false to remove them
      */
-    private void updateInterfaceForwarding(Interface intf, boolean install) {
+    private void updateInterfaceForwarding(InterfaceProvisionRequest request, boolean install) {
+        Interface intf = request.intf();
         log.debug("{} interface objectives for {}", operation(install), intf);
 
         DeviceId deviceId = intf.connectPoint().deviceId();
-        PortNumber controlPlanePort = controlPlaneConnectPoint.port();
+
+        PortNumber controlPlanePort = request.controlPlaneConnectPoint().port();
         for (InterfaceIpAddress ip : intf.ipAddresses()) {
             // create nextObjectives for forwarding to this interface and the
             // controlPlaneConnectPoint
@@ -426,13 +420,13 @@
     /**
      * Installs or removes OSPF forwarding rules.
      *
-     * @param intf the interface on which event is received
+     * @param request provisioning request containing router and interface
      * @param install true to create an add objective, false to create a remove
      *            objective
      */
-    private void updateOspfForwarding(Interface intf, boolean install) {
+    private void updateOspfForwarding(InterfaceProvisionRequest request, boolean install) {
         // TODO IPv6 support has not been implemented yet
-
+        Interface intf = request.intf();
         log.debug("{} OSPF flows for {}", operation(install), intf);
 
         // OSPF to router
@@ -445,7 +439,7 @@
 
         // create nextObjectives for forwarding to the controlPlaneConnectPoint
         DeviceId deviceId = intf.connectPoint().deviceId();
-        PortNumber controlPlanePort = controlPlaneConnectPoint.port();
+        PortNumber controlPlanePort = request.controlPlaneConnectPoint().port();
         int cpNextId;
         if (intf.vlan() == VlanId.NONE) {
             cpNextId = modifyNextObjective(deviceId, controlPlanePort,
@@ -457,7 +451,7 @@
         }
         flowObjectiveService.forward(intf.connectPoint().deviceId(),
                 buildForwardingObjective(toSelector, null, cpNextId,
-                        install ? ospfEnabled : install, ACL_PRIORITY));
+                        install ? request.info().ospfEnabled() : install, ACL_PRIORITY));
     }
 
     /**
@@ -640,19 +634,17 @@
 
         @Override
         public void event(NetworkConfigEvent event) {
-            if (event.configClass().equals(RoutingService.ROUTER_CONFIG_CLASS)) {
+            if (event.configClass().equals(RoutingService.ROUTER_CONFIG_CLASS) ||
+                    event.configClass().equals(RoutersConfig.class)) {
                 switch (event.type()) {
                     case CONFIG_ADDED:
                     case CONFIG_UPDATED:
+                    case CONFIG_REMOVED:
                         processRouterConfig();
                         break;
                     case CONFIG_REGISTERED:
-                        break;
                     case CONFIG_UNREGISTERED:
                         break;
-                    case CONFIG_REMOVED:
-                        removeRouterConfig();
-                        break;
                 default:
                     break;
                 }
@@ -666,16 +658,19 @@
     private class InternalHostListener implements HostListener {
 
         private Optional<Interface> getPeerInterface(Host peer) {
+            Router router = routers.get(peer.location().deviceId());
+
             return interfaceService.getInterfacesByPort(peer.location()).stream()
-                    .filter(intf -> interfaceManager.configuredInterfaces().isEmpty()
-                            || interfaceManager.configuredInterfaces().contains(intf.name()))
+                    .filter(intf -> router.info().interfaces().isEmpty()
+                            || router.info().interfaces().contains(intf.name()))
                     .filter(intf -> peer.vlan().equals(intf.vlan()))
                     .findFirst();
         }
 
         private void peerAdded(HostEvent event) {
             Host peer = event.subject();
-            if (interfaceManager == null) {
+            Router routerInfo = routers.get(peer.location().deviceId());
+            if (routerInfo == null) {
                 return;
             }
 
@@ -689,7 +684,7 @@
             // Generate L3 Unicast group for the traffic towards vRouter
             // XXX This approach will change with the HA design
             int toRouterL3Unicast = createPeerGroup(peer.mac(), peerIntf.get().mac(),
-                    peer.vlan(), peer.location().deviceId(), controlPlaneConnectPoint.port());
+                    peer.vlan(), peer.location().deviceId(), routerInfo.info().controlPlaneConnectPoint().port());
             // Generate L3 Unicast group for the traffic towards the upStream
             // XXX This approach will change with the HA design
             int toPeerL3Unicast = createPeerGroup(peerIntf.get().mac(), peer.mac(),
@@ -712,6 +707,9 @@
 
         private void peerRemoved(HostEvent event) {
             Host peer = event.subject();
+            if (routers.get(peer.location().deviceId()) == null) {
+                return;
+            }
             Optional<Interface> peerIntf = getPeerInterface(peer);
             if (!peerIntf.isPresent()) {
                 log.debug("Removing peer {}/{} on {} but the interface is not configured",
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 6401679..04b4352 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
@@ -19,7 +19,6 @@
 import com.google.common.collect.ConcurrentHashMultiset;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multiset;
-import com.google.common.collect.Sets;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -44,7 +43,6 @@
 import org.onosproject.incubator.net.routing.RouteEvent;
 import org.onosproject.incubator.net.routing.RouteListener;
 import org.onosproject.incubator.net.routing.RouteService;
-import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.config.ConfigFactory;
 import org.onosproject.net.config.NetworkConfigEvent;
@@ -70,9 +68,13 @@
 import org.onosproject.routing.AsyncDeviceFetcher;
 import org.onosproject.routing.NextHop;
 import org.onosproject.routing.NextHopGroupKey;
-import org.onosproject.routing.RouterInterfaceManager;
+import org.onosproject.routing.RouterInfo;
+import org.onosproject.routing.InterfaceProvisionRequest;
+import org.onosproject.routing.Router;
 import org.onosproject.routing.RoutingService;
-import org.onosproject.routing.config.RouterConfig;
+import org.onosproject.routing.config.RouterConfigHelper;
+import org.onosproject.routing.config.RoutersConfig;
+import org.onosproject.routing.config.RoutingConfigurationService;
 import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -124,6 +126,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ApplicationService applicationService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected RoutingConfigurationService rs;
+
     @Property(name = "routeToNextHop", boolValue = false,
             label = "Install a /32 or /128 route to each next hop")
     private boolean routeToNextHop = false;
@@ -131,9 +136,7 @@
     // Device id of data-plane switch - should be learned from config
     private DeviceId deviceId;
 
-    private ConnectPoint controlPlaneConnectPoint;
-
-    private RouterInterfaceManager interfaceManager;
+    private Router interfaceManager;
     private AsyncDeviceFetcher asyncDeviceFetcher;
 
     private ApplicationId coreAppId;
@@ -207,32 +210,27 @@
     }
 
     private void processRouterConfig() {
-        RouterConfig routerConfig =
-                networkConfigService.getConfig(routerAppId, RoutingService.ROUTER_CONFIG_CLASS);
-
-        if (routerConfig == null) {
+        Set<RoutersConfig.Router> routerConfigs =
+                RouterConfigHelper.getRouterConfigurations(networkConfigService, routerAppId);
+        if (routerConfigs.isEmpty()) {
             log.info("Router config not available");
             return;
         }
+        RoutersConfig.Router routerConfig = routerConfigs.stream().findFirst().get();
 
-        Set<String> interfaces = Sets.newHashSet(routerConfig.getInterfaces());
-
-        if (deviceId == null) {
-            controlPlaneConnectPoint = routerConfig.getControlPlaneConnectPoint();
-            log.info("Control Plane Connect Point: {}", controlPlaneConnectPoint);
-
-            deviceId = routerConfig.getControlPlaneConnectPoint().deviceId();
+        if (interfaceManager == null) {
+            deviceId = routerConfig.controlPlaneConnectPoint().deviceId();
             log.info("Router device ID is {}", deviceId);
 
             routeService.addListener(routeListener);
-            asyncDeviceFetcher.getDevice(deviceId).whenComplete((deviceId, e) ->
-                    interfaceManager = createRouter(deviceId, interfaces));
+
+            interfaceManager = createRouter(RouterInfo.from(routerConfig));
         } else {
-            interfaceManager.changeConfiguredInterfaces(interfaces);
+            interfaceManager.changeConfiguration(RouterInfo.from(routerConfig));
         }
     }
 
-    /*
+    /**
      * Removes filtering objectives and routes before deactivate.
      */
     private void cleanUp() {
@@ -249,10 +247,11 @@
         }
     }
 
-    private RouterInterfaceManager createRouter(DeviceId deviceId, Set<String> configuredInterfaces) {
-        return new RouterInterfaceManager(deviceId,
-                configuredInterfaces,
+    private Router createRouter(RouterInfo info) {
+        return new Router(
+                info,
                 interfaceService,
+                deviceService,
                 this::provisionInterface,
                 this::unprovisionInterface);
     }
@@ -410,11 +409,11 @@
         return group;
     }*/
 
-    private void provisionInterface(Interface intf) {
+    private void provisionInterface(InterfaceProvisionRequest intf) {
         updateInterfaceFilters(intf, true);
     }
 
-    private void unprovisionInterface(Interface intf) {
+    private void unprovisionInterface(InterfaceProvisionRequest intf) {
         updateInterfaceFilters(intf, false);
     }
 
@@ -424,7 +423,7 @@
      * @param intf interface to update objectives for
      * @param install true to install the objectives, false to remove them
      */
-    private void updateInterfaceFilters(Interface intf, boolean install) {
+    private void updateInterfaceFilters(InterfaceProvisionRequest intf, boolean install) {
         updateFilteringObjective(intf, install);
         updateMcastFilteringObjective(intf, install);
     }
@@ -432,10 +431,11 @@
     /**
      * Installs or removes unicast filtering objectives relating to an interface.
      *
-     * @param intf interface to update objectives for
+     * @param routerIntf interface to update objectives for
      * @param install true to install the objectives, false to remove them
      */
-    private void updateFilteringObjective(Interface intf, boolean install) {
+    private void updateFilteringObjective(InterfaceProvisionRequest routerIntf, boolean install) {
+        Interface intf = routerIntf.intf();
         VlanId assignedVlan = (egressVlan().equals(VlanId.NONE)) ?
                 VlanId.vlanId(ASSIGNED_VLAN) :
                 egressVlan();
@@ -454,20 +454,19 @@
         fob.permit().fromApp(fibAppId);
         sendFilteringObjective(install, fob, intf);
 
-        if (controlPlaneConnectPoint != null) {
-            // then add the same mac/vlan filters for control-plane connect point
-            fob.withKey(Criteria.matchInPort(controlPlaneConnectPoint.port()));
-            sendFilteringObjective(install, fob, intf);
-        }
+        // then add the same mac/vlan filters for control-plane connect point
+        fob.withKey(Criteria.matchInPort(routerIntf.controlPlaneConnectPoint().port()));
+        sendFilteringObjective(install, fob, intf);
     }
 
     /**
      * Installs or removes multicast filtering objectives relating to an interface.
      *
-     * @param intf interface to update objectives for
+     * @param routerIntf interface to update objectives for
      * @param install true to install the objectives, false to remove them
      */
-    private void updateMcastFilteringObjective(Interface intf, boolean install) {
+    private void updateMcastFilteringObjective(InterfaceProvisionRequest routerIntf, boolean install) {
+        Interface intf = routerIntf.intf();
         VlanId assignedVlan = (egressVlan().equals(VlanId.NONE)) ?
                 VlanId.vlanId(ASSIGNED_VLAN) :
                 egressVlan();
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 e17d779..fabc94b 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
@@ -60,6 +60,7 @@
 import org.onosproject.net.host.InterfaceIpAddress;
 import org.onosproject.routing.RoutingService;
 import org.onosproject.routing.config.RouterConfig;
+import org.onosproject.routing.config.RoutersConfig;
 import org.osgi.service.component.ComponentContext;
 
 import java.util.ArrayList;
@@ -189,6 +190,8 @@
         expect(networkConfigService.getConfig(
                 anyObject(ApplicationId.class), eq(RoutingService.ROUTER_CONFIG_CLASS))).
         andReturn(routerConfig);
+        expect(networkConfigService.getConfig(anyObject(ApplicationId.class), eq(RoutersConfig.class)))
+                .andReturn(null);
         replay(networkConfigService);
     }