[CORD-634]Add dynamic config update support for interfaces

Change-Id: I065ef5df908864f10f60c5491db3ff9e502c4101
diff --git a/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index 9d4cf04..cd34844 100644
--- a/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -15,6 +15,8 @@
  */
 package org.onosproject.segmentrouting;
 
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -40,6 +42,7 @@
 import org.onlab.packet.ICMP6;
 import org.onlab.packet.IPv4;
 import org.onlab.packet.IPv6;
+import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.VlanId;
 import org.onlab.util.KryoNamespace;
@@ -56,6 +59,7 @@
 import org.onosproject.net.Link;
 import org.onosproject.net.Port;
 import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.ConfigException;
 import org.onosproject.net.config.ConfigFactory;
 import org.onosproject.net.config.NetworkConfigEvent;
 import org.onosproject.net.config.NetworkConfigListener;
@@ -72,6 +76,7 @@
 import org.onosproject.net.host.HostEvent;
 import org.onosproject.net.host.HostListener;
 import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.InterfaceIpAddress;
 import org.onosproject.net.intf.Interface;
 import org.onosproject.net.intf.InterfaceService;
 import org.onosproject.net.link.LinkEvent;
@@ -751,7 +756,6 @@
     }
 
     /**
-     * Returns the next objective ID for the given subnet prefix. It is expected
      * Returns the next objective ID for the given vlan id. It is expected
      * that the next-objective has been pre-created from configuration.
      *
@@ -1485,6 +1489,11 @@
                     case CONFIG_UPDATED:
                         log.info("Interface Config updated for {}", event.subject());
                         createOrUpdateDeviceConfiguration();
+
+                        // Following code will be uncommented when [CORD-634] is fully implemented.
+                        // [CORD-634] Add dynamic config support for interfaces
+                        updateInterface((InterfaceConfig) event.config().get(),
+                                        (InterfaceConfig) event.prevConfig().get());
                         // TODO support dynamic configuration
                         break;
                     default:
@@ -1618,4 +1627,197 @@
         }
     }
 
+    private void updateInterface(InterfaceConfig conf, InterfaceConfig prevConf) {
+        try {
+            Set<Interface> intfs = conf.getInterfaces();
+            Set<Interface> prevIntfs = prevConf.getInterfaces();
+
+            // Now we only handle one interface config at each port.
+            if (intfs.size() != 1 || prevIntfs.size() != 1) {
+                log.warn("Interface update aborted - one at a time is allowed, " +
+                                 "but {} / {}(prev) received.", intfs.size(), prevIntfs.size());
+                return;
+            }
+
+            Interface intf = intfs.stream().findFirst().get();
+            Interface prevIntf = prevIntfs.stream().findFirst().get();
+
+            DeviceId deviceId = intf.connectPoint().deviceId();
+            PortNumber portNum = intf.connectPoint().port();
+
+            if (!mastershipService.isLocalMaster(deviceId)) {
+                log.debug("CONFIG_UPDATED event for interfaces should be " +
+                                  "handled by master node for device {}", deviceId);
+                return;
+            }
+
+            removeSubnetConfig(prevIntf.connectPoint(),
+                               Sets.difference(new HashSet<>(prevIntf.ipAddressesList()),
+                                               new HashSet<>(intf.ipAddressesList())));
+
+            if (prevIntf.vlanNative() != VlanId.NONE && !intf.vlanNative().equals(prevIntf.vlanNative())) {
+                // RemoveVlanNative
+                updateVlanConfigInternal(deviceId, portNum, prevIntf.vlanNative(), true, false);
+            }
+
+            if (!prevIntf.vlanTagged().isEmpty() && !intf.vlanTagged().equals(prevIntf.vlanTagged())) {
+                // RemoveVlanTagged
+                prevIntf.vlanTagged().stream().filter(i -> !intf.vlanTagged().contains(i)).forEach(
+                        vlanId -> updateVlanConfigInternal(deviceId, portNum, vlanId, false, false)
+                );
+            }
+
+            if (prevIntf.vlanUntagged() != VlanId.NONE && !intf.vlanUntagged().equals(prevIntf.vlanUntagged())) {
+                // RemoveVlanUntagged
+                updateVlanConfigInternal(deviceId, portNum, prevIntf.vlanUntagged(), true, false);
+            }
+
+            if (intf.vlanNative() != VlanId.NONE && !prevIntf.vlanNative().equals(intf.vlanNative())) {
+                // AddVlanNative
+                updateVlanConfigInternal(deviceId, portNum, intf.vlanNative(), true, true);
+            }
+
+            if (!intf.vlanTagged().isEmpty() && !intf.vlanTagged().equals(prevIntf.vlanTagged())) {
+                // AddVlanTagged
+                intf.vlanTagged().stream().filter(i -> !prevIntf.vlanTagged().contains(i)).forEach(
+                        vlanId -> updateVlanConfigInternal(deviceId, portNum, vlanId, false, true)
+                );
+            }
+
+            if (intf.vlanUntagged() != VlanId.NONE && !prevIntf.vlanUntagged().equals(intf.vlanUntagged())) {
+                // AddVlanUntagged
+                updateVlanConfigInternal(deviceId, portNum, intf.vlanUntagged(), true, true);
+            }
+            addSubnetConfig(prevIntf.connectPoint(),
+                            Sets.difference(new HashSet<>(intf.ipAddressesList()),
+                                            new HashSet<>(prevIntf.ipAddressesList())));
+        } catch (ConfigException e) {
+            log.error("Error in configuration");
+        }
+    }
+
+    private void updateVlanConfigInternal(DeviceId deviceId, PortNumber portNum,
+                                          VlanId vlanId, boolean pushVlan, boolean install) {
+        DefaultGroupHandler grpHandler = getGroupHandler(deviceId);
+        if (grpHandler == null) {
+            log.warn("Failed to retrieve group handler for device {}", deviceId);
+            return;
+        }
+
+        // Update filtering objective for a single port
+        routingRulePopulator.updateSinglePortFilters(deviceId, portNum, pushVlan, vlanId, install);
+
+        // Update filtering objective for multicast ingress port
+        mcastHandler.updateFilterToDevice(deviceId, portNum, vlanId, install);
+
+        int nextId = getVlanNextObjectiveId(deviceId, vlanId);
+
+        if (nextId != -1 && !install) {
+            // Update next objective for a single port as an output port
+            // Remove a single port from L2FG
+            grpHandler.updateGroupFromVlanConfiguration(portNum, Collections.singleton(vlanId), nextId, install);
+            // Remove L2 Bridging rule and L3 Unicast rule to the host
+            hostHandler.processIntfVlanUpdatedEvent(deviceId, portNum, vlanId, pushVlan, install);
+            // Remove broadcast forwarding rule and corresponding L2FG for VLAN
+            // only if there is no port configured on that VLAN ID
+            if (!getVlanPortMap(deviceId).containsKey(vlanId)) {
+                // Remove broadcast forwarding rule for the VLAN
+                routingRulePopulator.updateSubnetBroadcastRule(deviceId, vlanId, install);
+                // Remove L2FG for VLAN
+                grpHandler.removeBcastGroupFromVlan(deviceId, portNum, vlanId, pushVlan);
+            } else {
+                // Remove L2IG of the port
+                grpHandler.removePortNextObjective(deviceId, portNum, vlanId, pushVlan);
+            }
+        } else if (install) {
+            if (nextId != -1) {
+                // Add a single port to L2FG
+                grpHandler.updateGroupFromVlanConfiguration(portNum, Collections.singleton(vlanId), nextId, install);
+            } else {
+                // Create L2FG for VLAN
+                grpHandler.createBcastGroupFromVlan(vlanId, Collections.singleton(portNum));
+                routingRulePopulator.updateSubnetBroadcastRule(deviceId, vlanId, install);
+            }
+            hostHandler.processIntfVlanUpdatedEvent(deviceId, portNum, vlanId, pushVlan, install);
+        } else {
+            log.warn("Failed to retrieve next objective for vlan {} in device {}:{}", vlanId, deviceId, portNum);
+        }
+    }
+
+    private void removeSubnetConfig(ConnectPoint cp, Set<InterfaceIpAddress> ipAddressSet) {
+        Set<IpPrefix> ipPrefixSet = ipAddressSet.stream().
+                map(InterfaceIpAddress::subnetAddress).collect(Collectors.toSet());
+
+        Set<InterfaceIpAddress> deviceIntfIpAddrs = interfaceService.getInterfaces().stream()
+                .filter(intf -> intf.connectPoint().deviceId().equals(cp.deviceId()))
+                .filter(intf -> !intf.connectPoint().equals(cp))
+                .flatMap(intf -> intf.ipAddressesList().stream())
+                .collect(Collectors.toSet());
+        // 1. Partial subnet population
+        // Remove routing rules for removed subnet from previous configuration,
+        // which does not also exist in other interfaces in the same device
+        Set<IpPrefix> deviceIpPrefixSet = deviceIntfIpAddrs.stream()
+                .map(InterfaceIpAddress::subnetAddress)
+                .collect(Collectors.toSet());
+
+        defaultRoutingHandler.revokeSubnet(
+                ipPrefixSet.stream()
+                        .filter(ipPrefix -> !deviceIpPrefixSet.contains(ipPrefix))
+                        .collect(Collectors.toSet()));
+
+        // 2. Interface IP punts
+        // Remove IP punts for old Intf address
+        Set<IpAddress> deviceIpAddrs = deviceIntfIpAddrs.stream()
+                .map(InterfaceIpAddress::ipAddress)
+                .collect(Collectors.toSet());
+        ipAddressSet.stream()
+                .map(InterfaceIpAddress::ipAddress)
+                .filter(interfaceIpAddress -> !deviceIpAddrs.contains(interfaceIpAddress))
+                .forEach(interfaceIpAddress ->
+                                 routingRulePopulator.revokeSingleIpPunts(
+                                         cp.deviceId(), interfaceIpAddress));
+
+        // 3. Host unicast routing rule
+        // Remove unicast routing rule
+        hostHandler.processIntfIpUpdatedEvent(cp, ipPrefixSet, false);
+    }
+
+    private void addSubnetConfig(ConnectPoint cp, Set<InterfaceIpAddress> ipAddressSet) {
+        Set<IpPrefix> ipPrefixSet = ipAddressSet.stream().
+                map(InterfaceIpAddress::subnetAddress).collect(Collectors.toSet());
+
+        Set<InterfaceIpAddress> deviceIntfIpAddrs = interfaceService.getInterfaces().stream()
+                .filter(intf -> intf.connectPoint().deviceId().equals(cp.deviceId()))
+                .filter(intf -> !intf.connectPoint().equals(cp))
+                .flatMap(intf -> intf.ipAddressesList().stream())
+                .collect(Collectors.toSet());
+        // 1. Partial subnet population
+        // Add routing rules for newly added subnet, which does not also exist in
+        // other interfaces in the same device
+        Set<IpPrefix> deviceIpPrefixSet = deviceIntfIpAddrs.stream()
+                .map(InterfaceIpAddress::subnetAddress)
+                .collect(Collectors.toSet());
+
+        defaultRoutingHandler.populateSubnet(
+                Collections.singleton(cp),
+                ipPrefixSet.stream()
+                        .filter(ipPrefix -> !deviceIpPrefixSet.contains(ipPrefix))
+                        .collect(Collectors.toSet()));
+
+        // 2. Interface IP punts
+        // Add IP punts for new Intf address
+        Set<IpAddress> deviceIpAddrs = deviceIntfIpAddrs.stream()
+                .map(InterfaceIpAddress::ipAddress)
+                .collect(Collectors.toSet());
+        ipAddressSet.stream()
+                .map(InterfaceIpAddress::ipAddress)
+                .filter(interfaceIpAddress -> !deviceIpAddrs.contains(interfaceIpAddress))
+                .forEach(interfaceIpAddress ->
+                                 routingRulePopulator.populateSingleIpPunts(
+                                         cp.deviceId(), interfaceIpAddress));
+
+        // 3. Host unicast routing rule
+        // Add unicast routing rule
+        hostHandler.processIntfIpUpdatedEvent(cp, ipPrefixSet, true);
+    }
 }