Abstract handling of router interfaces and pull out of CPRM and SSFI.

This separates interface bookkeeping from the actual provisioning work,
and simplifies the interface bookkeeping logic.

Change-Id: I639cde25ab5d3e02784399d356df813b3760eead
diff --git a/apps/routing/src/main/java/org/onosproject/routing/impl/SingleSwitchFibInstaller.java b/apps/routing/src/main/java/org/onosproject/routing/impl/SingleSwitchFibInstaller.java
index 24dd468..4d4141b 100644
--- a/apps/routing/src/main/java/org/onosproject/routing/impl/SingleSwitchFibInstaller.java
+++ b/apps/routing/src/main/java/org/onosproject/routing/impl/SingleSwitchFibInstaller.java
@@ -19,6 +19,7 @@
 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;
@@ -38,8 +39,6 @@
 import org.onosproject.core.CoreService;
 import org.onosproject.incubator.net.config.basics.McastConfig;
 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.incubator.net.routing.ResolvedRoute;
 import org.onosproject.incubator.net.routing.RouteEvent;
@@ -53,8 +52,6 @@
 import org.onosproject.net.config.NetworkConfigRegistry;
 import org.onosproject.net.config.NetworkConfigService;
 import org.onosproject.net.config.basics.SubjectFactories;
-import org.onosproject.net.device.DeviceEvent;
-import org.onosproject.net.device.DeviceListener;
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
@@ -77,10 +74,8 @@
 import org.slf4j.LoggerFactory;
 
 import java.util.Dictionary;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 /**
  * Programs routes to a single OpenFlow switch.
@@ -134,7 +129,8 @@
 
     private ConnectPoint controlPlaneConnectPoint;
 
-    private List<String> interfaces;
+    private RouterInterfaceManager interfaceManager;
+    private AsyncDeviceFetcher asyncDeviceFetcher;
 
     private ApplicationId coreAppId;
     private ApplicationId routerAppId;
@@ -149,8 +145,6 @@
     // Mapping from next hop IP to next hop object containing group info
     private final Map<IpAddress, Integer> nextHops = Maps.newHashMap();
 
-    private final InternalDeviceListener deviceListener = new InternalDeviceListener();
-    private final InternalInterfaceListener internalInterfaceList = new InternalInterfaceListener();
     private final InternalRouteListener routeListener = new InternalRouteListener();
     private final InternalNetworkConfigListener configListener = new InternalNetworkConfigListener();
 
@@ -175,8 +169,8 @@
         networkConfigRegistry.registerConfigFactory(mcastConfigFactory);
 
         networkConfigService.addListener(configListener);
-        deviceService.addListener(deviceListener);
-        interfaceService.addListener(internalInterfaceList);
+
+        asyncDeviceFetcher = AsyncDeviceFetcher.create(deviceService);
 
         updateConfig();
 
@@ -187,14 +181,22 @@
         log.info("Started");
     }
 
+    private RouterInterfaceManager createRouter(DeviceId deviceId, Set<String> configuredInterfaces) {
+        return new RouterInterfaceManager(deviceId,
+                configuredInterfaces,
+                interfaceService,
+                intf -> processIntfFilter(true, intf),
+                intf -> processIntfFilter(false, intf)
+        );
+    }
+
     @Deactivate
     protected void deactivate() {
          // FIXME: This will also remove flows when an instance goes down.
          //        This is a temporary solution and should be addressed in CORD-710.
         cleanUp();
 
-        deviceService.removeListener(deviceListener);
-        interfaceService.removeListener(internalInterfaceList);
+        asyncDeviceFetcher.shutdown();
         networkConfigService.removeListener(configListener);
 
         componentConfigService.unregisterProperties(getClass(), false);
@@ -225,10 +227,8 @@
             deleteRoute(new ResolvedRoute(routes.getKey(), null, null, null));
         }
 
-        //clean up the filtering objective for interfaces.
-        Set<Interface> intfs = getInterfaces();
-        if (!intfs.isEmpty()) {
-            processIntfFilters(false, intfs);
+        if (interfaceManager != null) {
+            interfaceManager.cleanup();
         }
     }
 
@@ -240,69 +240,22 @@
             log.info("Router config not available");
             return;
         }
-        controlPlaneConnectPoint = routerConfig.getControlPlaneConnectPoint();
-        log.info("Control Plane Connect Point: {}", controlPlaneConnectPoint);
 
-        deviceId = routerConfig.getControlPlaneConnectPoint().deviceId();
-        log.info("Router device ID is {}", deviceId);
+        Set<String> interfaces = Sets.newHashSet(routerConfig.getInterfaces());
 
-        interfaces = routerConfig.getInterfaces();
-        log.info("Using interfaces: {}", interfaces.isEmpty() ? "all" : interfaces);
+        if (deviceId == null) {
+            controlPlaneConnectPoint = routerConfig.getControlPlaneConnectPoint();
+            log.info("Control Plane Connect Point: {}", controlPlaneConnectPoint);
 
-        routeService.addListener(routeListener);
-        updateDevice();
-    }
+            deviceId = routerConfig.getControlPlaneConnectPoint().deviceId();
+            log.info("Router device ID is {}", deviceId);
 
-    //remove the filtering objective for interfaces which are no longer part of vRouter config.
-    private void removeFilteringObjectives(NetworkConfigEvent event) {
-        RouterConfig prevRouterConfig = (RouterConfig) event.prevConfig().get();
-        List<String> prevInterfaces = prevRouterConfig.getInterfaces();
-
-        Set<Interface> previntfs = filterInterfaces(prevInterfaces);
-        //if previous interface list is empty it means filtering objectives are
-        //installed for all the interfaces.
-        if (previntfs.isEmpty() && !interfaces.isEmpty()) {
-            Set<Interface> allIntfs = interfaceService.getInterfaces();
-            for (Interface allIntf : allIntfs) {
-                if (!interfaces.contains(allIntf.name())) {
-                    processIntfFilter(false, allIntf);
-                }
-            }
-            return;
-        }
-
-        //remove the filtering objective for the interfaces which are not
-        //part of updated interfaces list.
-        for (Interface prevIntf : previntfs) {
-            if (!interfaces.contains(prevIntf.name())) {
-                processIntfFilter(false, prevIntf);
-            }
-        }
-    }
-
-    private void updateDevice() {
-        if (deviceId != null && deviceService.isAvailable(deviceId)) {
-            Set<Interface> intfs = getInterfaces();
-            processIntfFilters(true, intfs);
-        }
-    }
-
-    private Set<Interface> getInterfaces() {
-        Set<Interface> intfs;
-        if (interfaces == null || interfaces.isEmpty()) {
-            intfs = interfaceService.getInterfaces();
+            routeService.addListener(routeListener);
+            asyncDeviceFetcher.getDevice(deviceId).whenComplete((deviceId, e) ->
+                    interfaceManager = createRouter(deviceId, interfaces));
         } else {
-            // TODO need to fix by making interface names globally unique
-            intfs = filterInterfaces(interfaces);
+            interfaceManager.changeConfiguredInterfaces(interfaces);
         }
-        return intfs;
-    }
-
-    private Set<Interface> filterInterfaces(List<String> interfaces) {
-        return interfaceService.getInterfaces().stream()
-                .filter(intf -> intf.connectPoint().deviceId().equals(deviceId))
-                .filter(intf -> interfaces.contains(intf.name()))
-                .collect(Collectors.toSet());
     }
 
     private void updateRoute(ResolvedRoute route) {
@@ -458,30 +411,8 @@
         return group;
     }*/
 
-    private void processIntfFilters(boolean install, Set<Interface> intfs) {
-        log.info("Processing {} router interfaces", intfs.size());
-        for (Interface intf : intfs) {
-            if (!intf.connectPoint().deviceId().equals(deviceId)) {
-                // Ignore interfaces if they are not on the router switch
-                continue;
-            }
-
-            createFilteringObjective(install, intf);
-            createMcastFilteringObjective(install, intf);
-        }
-    }
-
     //process filtering objective for interface add/remove.
     private void processIntfFilter(boolean install, Interface intf) {
-
-        if (!intf.connectPoint().deviceId().equals(deviceId)) {
-            // Ignore interfaces if they are not on the router switch
-            return;
-        }
-        if (!interfaces.contains(intf.name()) && install) {
-            return;
-        }
-
         createFilteringObjective(install, intf);
         createMcastFilteringObjective(install, intf);
     }
@@ -578,36 +509,6 @@
     }
 
     /**
-     * Listener for device events used to trigger driver setup when a device is
-     * (re)detected.
-     */
-    private class InternalDeviceListener implements DeviceListener {
-        @Override
-        public void event(DeviceEvent event) {
-            switch (event.type()) {
-            case DEVICE_ADDED:
-            case DEVICE_AVAILABILITY_CHANGED:
-                if (deviceService.isAvailable(event.subject().id())) {
-                    log.info("Device connected {}", event.subject().id());
-                    if (event.subject().id().equals(deviceId)) {
-                        updateDevice();
-                    }
-                }
-                break;
-            // TODO other cases
-            case DEVICE_UPDATED:
-            case DEVICE_REMOVED:
-            case DEVICE_SUSPENDED:
-            case PORT_ADDED:
-            case PORT_UPDATED:
-            case PORT_REMOVED:
-            default:
-                break;
-            }
-        }
-    }
-
-    /**
      * Listener for network config events.
      */
     private class InternalNetworkConfigListener implements NetworkConfigListener {
@@ -618,9 +519,6 @@
                 case CONFIG_ADDED:
                 case CONFIG_UPDATED:
                     updateConfig();
-                    if (event.prevConfig().isPresent()) {
-                        removeFilteringObjectives(event);
-                    }
                     break;
                 case CONFIG_REGISTERED:
                     break;
@@ -635,27 +533,4 @@
             }
         }
     }
-
-    private class InternalInterfaceListener implements InterfaceListener {
-        @Override
-        public void event(InterfaceEvent event) {
-            Interface intf = event.subject();
-            switch (event.type()) {
-            case INTERFACE_ADDED:
-                if (intf != null) {
-                    processIntfFilter(true, intf);
-                }
-                break;
-            case INTERFACE_UPDATED:
-                break;
-            case INTERFACE_REMOVED:
-                if (intf != null) {
-                    processIntfFilter(false, intf);
-                }
-                break;
-            default:
-                break;
-            }
-        }
-    }
 }