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/ControlPlaneRedirectManager.java b/apps/routing/src/main/java/org/onosproject/routing/impl/ControlPlaneRedirectManager.java
index 9a16bfd..7c00613 100644
--- a/apps/routing/src/main/java/org/onosproject/routing/impl/ControlPlaneRedirectManager.java
+++ b/apps/routing/src/main/java/org/onosproject/routing/impl/ControlPlaneRedirectManager.java
@@ -19,6 +19,7 @@
 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;
@@ -29,19 +30,10 @@
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
-
-import static org.onlab.packet.Ethernet.TYPE_ARP;
-import static org.onlab.packet.Ethernet.TYPE_IPV4;
-import static org.onlab.packet.Ethernet.TYPE_IPV6;
-import static org.onlab.packet.ICMP6.NEIGHBOR_ADVERTISEMENT;
-import static org.onlab.packet.ICMP6.NEIGHBOR_SOLICITATION;
-import static org.onlab.packet.IPv6.PROTOCOL_ICMP6;
 import org.onosproject.app.ApplicationService;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 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.mastership.MastershipService;
 import org.onosproject.net.ConnectPoint;
@@ -51,8 +43,6 @@
 import org.onosproject.net.config.NetworkConfigEvent;
 import org.onosproject.net.config.NetworkConfigListener;
 import org.onosproject.net.config.NetworkConfigService;
-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;
@@ -71,15 +61,19 @@
 import org.onosproject.routing.config.RouterConfig;
 import org.slf4j.Logger;
 
-import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkState;
+import static org.onlab.packet.Ethernet.TYPE_ARP;
+import static org.onlab.packet.Ethernet.TYPE_IPV4;
+import static org.onlab.packet.Ethernet.TYPE_IPV6;
+import static org.onlab.packet.ICMP6.NEIGHBOR_ADVERTISEMENT;
+import static org.onlab.packet.ICMP6.NEIGHBOR_SOLICITATION;
+import static org.onlab.packet.IPv6.PROTOCOL_ICMP6;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -97,14 +91,6 @@
     static final int ACL_PRIORITY = 40001;
     private static final int OSPF_IP_PROTO = 0x59;
 
-    private static final String APP_NAME = "org.onosproject.vrouter";
-    private ApplicationId appId;
-
-    private ConnectPoint controlPlaneConnectPoint;
-    private boolean ospfEnabled = false;
-    private List<String> interfaces = Collections.emptyList();
-    private Map<Host, Set<Integer>> peerNextId = Maps.newConcurrentMap();
-
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected CoreService coreService;
 
@@ -129,40 +115,58 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ApplicationService applicationService;
 
-    private final InternalDeviceListener deviceListener = new InternalDeviceListener();
+    private static final String APP_NAME = "org.onosproject.vrouter";
+    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 final InternalNetworkConfigListener networkConfigListener =
             new InternalNetworkConfigListener();
     private final InternalHostListener hostListener = new InternalHostListener();
-    private final InternalInterfaceListener interfaceListener = new InternalInterfaceListener();
 
     @Activate
     public void activate() {
         this.appId = coreService.registerApplication(APP_NAME);
 
-        deviceService.addListener(deviceListener);
         networkConfigService.addListener(networkConfigListener);
         hostService.addListener(hostListener);
-        interfaceService.addListener(interfaceListener);
+
+        asyncDeviceFetcher = AsyncDeviceFetcher.create(deviceService);
 
         readConfig();
 
         // FIXME There can be an issue when this component is deactivated before vRouter
-        applicationService.registerDeactivateHook(this.appId, () -> provisionDevice(false));
+        applicationService.registerDeactivateHook(this.appId, () -> {
+            if (interfaceManager != null) {
+                interfaceManager.cleanup();
+            }
+        });
     }
 
     @Deactivate
     public void deactivate() {
-        deviceService.removeListener(deviceListener);
         networkConfigService.removeListener(networkConfigListener);
         hostService.removeListener(hostListener);
-        interfaceService.removeListener(interfaceListener);
+        asyncDeviceFetcher.shutdown();
+    }
+
+    private RouterInterfaceManager createRouter(DeviceId deviceId, Set<String> configuredInterfaces) {
+        return new RouterInterfaceManager(deviceId,
+                configuredInterfaces,
+                interfaceService,
+                intf -> provisionInterface(intf, true),
+                intf -> provisionInterface(intf, false)
+        );
     }
 
     /**
-     * Installs or removes interface configuration
-     * based on the flag used on activate or deactivate.
-     *
-     **/
+     * Sets up the router interfaces if router config is available.
+     */
     private void readConfig() {
         ApplicationId routingAppId =
                 coreService.registerApplication(RoutingService.ROUTER_APP_ID);
@@ -175,30 +179,19 @@
             return;
         }
 
-        controlPlaneConnectPoint = config.getControlPlaneConnectPoint();
-        ospfEnabled = config.getOspfEnabled();
-        interfaces = config.getInterfaces();
+        if (interfaceManager == null) {
+            controlPlaneConnectPoint = config.getControlPlaneConnectPoint();
+            ospfEnabled = config.getOspfEnabled();
 
-        provisionDevice(true);
-    }
+            DeviceId deviceId = config.getControlPlaneConnectPoint().deviceId();
 
-    /**
-     * Installs or removes interface configuration for each interface
-     * based on the flag used on activate or deactivate.
-     *
-     * @param install true to install flows, false to remove them
-     **/
-    private void provisionDevice(boolean install) {
-        if (controlPlaneConnectPoint != null &&
-                deviceService.isAvailable(controlPlaneConnectPoint.deviceId())) {
-            DeviceId deviceId = controlPlaneConnectPoint.deviceId();
+            asyncDeviceFetcher.getDevice(deviceId)
+                    .thenAccept(deviceId1 ->
+                            interfaceManager = createRouter(deviceId,
+                                    Sets.newHashSet(config.getInterfaces())));
 
-            interfaceService.getInterfaces().stream()
-                    .filter(intf -> intf.connectPoint().deviceId().equals(deviceId))
-                    .filter(intf -> interfaces.isEmpty() || interfaces.contains(intf.name()))
-                    .forEach(intf -> provisionInterface(intf, install));
-
-            log.info("Set up interfaces on {}", controlPlaneConnectPoint.deviceId());
+        } else {
+            interfaceManager.changeConfiguredInterfaces(Sets.newHashSet(config.getInterfaces()));
         }
     }
 
@@ -218,7 +211,7 @@
     private void updateInterfaceForwarding(Interface intf, boolean install) {
         log.debug("Adding interface objectives for {}", intf);
 
-        DeviceId deviceId = controlPlaneConnectPoint.deviceId();
+        DeviceId deviceId = intf.connectPoint().deviceId();
         PortNumber controlPlanePort = controlPlaneConnectPoint.port();
         for (InterfaceIpAddress ip : intf.ipAddresses()) {
             // create nextObjectives for forwarding to this interface and the
@@ -429,8 +422,8 @@
                 .build();
 
         // create nextObjectives for forwarding to the controlPlaneConnectPoint
-        DeviceId deviceId = controlPlaneConnectPoint.deviceId();
-        PortNumber controlPlanePort = controlPlaneConnectPoint.port();
+        DeviceId deviceId = intf.connectPoint().deviceId();
+        PortNumber controlPlanePort = intf.connectPoint().port();
         int cpNextId;
         if (intf.vlan() == VlanId.NONE) {
             cpNextId = modifyNextObjective(deviceId, controlPlanePort,
@@ -441,7 +434,8 @@
                                            intf.vlan(), false, install);
         }
         log.debug("OSPF flows intf:{} nextid:{}", intf, cpNextId);
-        flowObjectiveService.forward(controlPlaneConnectPoint.deviceId(),
+        log.debug("install={}", install);
+        flowObjectiveService.forward(intf.connectPoint().deviceId(),
                 buildForwardingObjective(toSelector, null, cpNextId, install ? ospfEnabled : install, ACL_PRIORITY));
     }
 
@@ -519,36 +513,6 @@
     }
 
     /**
-     * Listener for device events.
-     */
-    private class InternalDeviceListener implements DeviceListener {
-
-        @Override
-        public void event(DeviceEvent event) {
-            if (controlPlaneConnectPoint != null &&
-                    event.subject().id().equals(controlPlaneConnectPoint.deviceId())) {
-                switch (event.type()) {
-                case DEVICE_ADDED:
-                case DEVICE_AVAILABILITY_CHANGED:
-                    if (deviceService.isAvailable(event.subject().id())) {
-                        log.info("Device connected {}", event.subject().id());
-                        provisionDevice(true);
-                    }
-                    break;
-                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 {
@@ -557,19 +521,16 @@
         public void event(NetworkConfigEvent event) {
             if (event.configClass().equals(RoutingService.ROUTER_CONFIG_CLASS)) {
                 switch (event.type()) {
-                case CONFIG_ADDED:
-                case CONFIG_UPDATED:
-                    readConfig();
-                    if (event.prevConfig().isPresent()) {
-                            updateConfig(event);
-                            }
-
-                    break;
-                case CONFIG_REGISTERED:
-                case CONFIG_UNREGISTERED:
-                case CONFIG_REMOVED:
-                    removeConfig();
-
+                    case CONFIG_ADDED:
+                    case CONFIG_UPDATED:
+                        readConfig();
+                        break;
+                    case CONFIG_REGISTERED:
+                        break;
+                    case CONFIG_UNREGISTERED:
+                        break;
+                    case CONFIG_REMOVED:
+                        removeConfig();
                         break;
                 default:
                     break;
@@ -583,13 +544,21 @@
      */
     private class InternalHostListener implements HostListener {
 
-        private void peerAdded(HostEvent event) {
-            Host peer = event.subject();
-            Optional<Interface> peerIntf =
-                    interfaceService.getInterfacesByPort(peer.location()).stream()
-                    .filter(intf -> interfaces.isEmpty() || interfaces.contains(intf.name()))
+        private Optional<Interface> getPeerInterface(Host peer) {
+            return interfaceService.getInterfacesByPort(peer.location()).stream()
+                    .filter(intf -> interfaceManager.configuredInterfaces().isEmpty()
+                            || interfaceManager.configuredInterfaces().contains(intf.name()))
                     .filter(intf -> peer.vlan().equals(intf.vlan()))
                     .findFirst();
+        }
+
+        private void peerAdded(HostEvent event) {
+            Host peer = event.subject();
+            if (interfaceManager == null) {
+                return;
+            }
+
+            Optional<Interface> peerIntf = getPeerInterface(peer);
             if (!peerIntf.isPresent()) {
                 log.debug("Adding peer {}/{} on {} but the interface is not configured",
                         peer.mac(), peer.vlan(), peer.location());
@@ -598,7 +567,7 @@
 
             // Generate L3 Unicast groups and store it in the map
             int toRouterL3Unicast = createPeerGroup(peer.mac(), peerIntf.get().mac(),
-                    peer.vlan(), peer.location().deviceId(), controlPlaneConnectPoint.port());
+                    peer.vlan(), peer.location().deviceId(), peerIntf.get().connectPoint().port());
             int toPeerL3Unicast = createPeerGroup(peerIntf.get().mac(), peer.mac(),
                     peer.vlan(), peer.location().deviceId(), peer.location().port());
             peerNextId.put(peer, ImmutableSortedSet.of(toRouterL3Unicast, toPeerL3Unicast));
@@ -618,11 +587,7 @@
 
         private void peerRemoved(HostEvent event) {
             Host peer = event.subject();
-            Optional<Interface> peerIntf =
-                    interfaceService.getInterfacesByPort(peer.location()).stream()
-                            .filter(intf -> interfaces.isEmpty() || interfaces.contains(intf.name()))
-                            .filter(intf -> peer.vlan().equals(intf.vlan()))
-                            .findFirst();
+            Optional<Interface> peerIntf = getPeerInterface(peer);
             if (!peerIntf.isPresent()) {
                 log.debug("Removing peer {}/{} on {} but the interface is not configured",
                         peer.mac(), peer.vlan(), peer.location());
@@ -722,119 +687,11 @@
                 IPV6_PRIORITY * prefix.prefixLength() + MIN_IP_PRIORITY;
     }
 
-    private void updateConfig(NetworkConfigEvent event) {
-        RouterConfig prevRouterConfig = (RouterConfig) event.prevConfig().get();
-        List<String> prevInterfaces = prevRouterConfig.getInterfaces();
-        Set<Interface> previntfs = filterInterfaces(prevInterfaces);
-        if (previntfs.isEmpty() && !interfaces.isEmpty()) {
-            interfaceService.getInterfaces().stream()
-                    .filter(intf -> !interfaces.contains(intf.name()))
-                    .forEach(intf -> processIntfFilter(false, intf));
-            return;
-        }
-        //remove the filtering objective for the interfaces which are not
-        //part of updated interfaces list.
-        previntfs.stream()
-                .filter(intf -> !interfaces.contains(intf.name()))
-                .forEach(intf -> processIntfFilter(false, intf));
-    }
-
-  /**
-   * process filtering objective for interface add/remove.
-   *
-   * @param install true to install flows, false to uninstall the flows
-   * @param intf Interface object captured on event
-   */
-    private void processIntfFilter(boolean install, Interface intf) {
-
-        if (!intf.connectPoint().deviceId().equals(controlPlaneConnectPoint.deviceId())) {
-            // Ignore interfaces if they are not on the router switch
-            return;
-        }
-        if (!interfaces.contains(intf.name()) && install) {
-            return;
-        }
-
-        provisionInterface(intf, install);
-    }
-
-    private Set<Interface> filterInterfaces(List<String> interfaces) {
-        return interfaceService.getInterfaces().stream()
-                .filter(intf -> intf.connectPoint().deviceId().equals(controlPlaneConnectPoint.deviceId()))
-                .filter(intf -> interfaces.contains(intf.name()))
-                .collect(Collectors.toSet());
-    }
-
     private void removeConfig() {
-        Set<Interface> intfs = getInterfaces();
-        if (!intfs.isEmpty()) {
-            intfs.forEach(intf -> processIntfFilter(false, intf));
-        }
-        networkConfigService.removeConfig();
-    }
-
-    private Set<Interface> getInterfaces() {
-
-        return interfaces.isEmpty() ? interfaceService.getInterfaces()
-                : filterInterfaces(interfaces);
-    }
-
-    /**
-     * Update the flows comparing previous event and current event.
-     *
-     * @param prevIntf the previous interface event
-     * @param intf the current occurred update event
-     **/
-    private void updateInterface(Interface prevIntf, Interface intf) {
-        if (!intf.connectPoint().deviceId().equals(controlPlaneConnectPoint.deviceId())
-                || !interfaces.contains(intf.name())) {
-            // Ignore interfaces if they are not on the router switch
-            return;
-        }
-        if (!prevIntf.vlan().equals(intf.vlan()) || !prevIntf.mac().equals(intf.mac())) {
-            provisionInterface(prevIntf, false);
-            provisionInterface(intf, true);
-        } else {
-            List<InterfaceIpAddress> removeIps =
-                    prevIntf.ipAddressesList().stream()
-                    .filter(pre -> !intf.ipAddressesList().contains(pre))
-                    .collect(Collectors.toList());
-            List<InterfaceIpAddress> addIps =
-                    intf.ipAddressesList().stream()
-                    .filter(cur -> !prevIntf.ipAddressesList().contains(cur))
-                    .collect(Collectors.toList());
-            // removing flows with match parameters present in previous subject
-            updateInterfaceForwarding(new Interface(prevIntf.name(), prevIntf.connectPoint(),
-                    removeIps, prevIntf.mac(), prevIntf.vlan()), false);
-            // adding flows with match parameters present in event subject
-            updateInterfaceForwarding(new Interface(intf.name(), intf.connectPoint(),
-                    addIps, intf.mac(), intf.vlan()), true);
+        if (interfaceManager != null) {
+            interfaceManager.cleanup();
         }
     }
 
-    private class InternalInterfaceListener implements InterfaceListener {
-        @Override
-        public void event(InterfaceEvent event) {
-            if (controlPlaneConnectPoint == null) {
-                log.warn("Control plane connect point is not configured. Abort InterfaceEvent.");
-                return;
-            }
-            Interface intf = event.subject();
-            Interface prevIntf = event.prevSubject();
-            switch (event.type()) {
-                case INTERFACE_ADDED:
-                    processIntfFilter(true, intf);
-                    break;
-                case INTERFACE_UPDATED:
-                    updateInterface(prevIntf, intf);
-                    break;
-                case INTERFACE_REMOVED:
-                    processIntfFilter(false, intf);
-                    break;
-                default:
-                    break;
-            }
-        }
-    }
 }