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

Change-Id: I065ef5df908864f10f60c5491db3ff9e502c4101
diff --git a/src/main/java/org/onosproject/segmentrouting/HostHandler.java b/src/main/java/org/onosproject/segmentrouting/HostHandler.java
index 539af62..07f6d8d 100644
--- a/src/main/java/org/onosproject/segmentrouting/HostHandler.java
+++ b/src/main/java/org/onosproject/segmentrouting/HostHandler.java
@@ -17,6 +17,7 @@
 package org.onosproject.segmentrouting;
 
 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;
@@ -400,4 +401,125 @@
             srManager.defaultRoutingHandler.populateRoute(deviceId, ip.toIpPrefix(), mac, vlanId, port);
         }
     }
+
+    /**
+     * Populate or revoke a bridging rule on given deviceId that matches given vlanId,
+     * and hostMAC connected to given port, and output to given port only when
+     * vlan information is valid.
+     *
+     * @param deviceId device ID that host attaches to
+     * @param portNum port number that host attaches to
+     * @param hostMac mac address of the host connected to the switch port
+     * @param vlanId Vlan ID configured on the switch port
+     * @param popVlan true to pop Vlan tag at TrafficTreatment, false otherwise
+     * @param install true to populate the objective, false to revoke
+     */
+    private void updateBridgingRule(DeviceId deviceId, PortNumber portNum, MacAddress hostMac,
+                            VlanId vlanId, boolean popVlan, boolean install) {
+        // Create host selector
+        TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
+        sbuilder.matchEthDst(hostMac);
+
+        // Create host meta
+        TrafficSelector.Builder mbuilder = DefaultTrafficSelector.builder();
+
+        sbuilder.matchVlanId(vlanId);
+        mbuilder.matchVlanId(vlanId);
+
+        // Create host treatment
+        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+        tbuilder.immediate().setOutput(portNum);
+
+        if (popVlan) {
+            tbuilder.immediate().popVlan();
+        }
+
+        int portNextObjId = srManager.getPortNextObjectiveId(deviceId, portNum,
+                                                             tbuilder.build(), mbuilder.build(), install);
+        if (portNextObjId != -1) {
+            ForwardingObjective.Builder fob = DefaultForwardingObjective.builder()
+                    .withFlag(ForwardingObjective.Flag.SPECIFIC)
+                    .withSelector(sbuilder.build())
+                    .nextStep(portNextObjId)
+                    .withPriority(100)
+                    .fromApp(srManager.appId)
+                    .makePermanent();
+
+            ObjectiveContext context = new DefaultObjectiveContext(
+                    (objective) -> log.debug("Brigding rule for {}/{} {}", hostMac, vlanId,
+                                             install ? "populated" : "revoked"),
+                    (objective, error) -> log.warn("Failed to {} bridging rule for {}/{}: {}",
+                                                   install ? "populate" : "revoke", hostMac, vlanId, error));
+            flowObjectiveService.forward(deviceId, install ? fob.add(context) : fob.remove(context));
+        } else {
+            log.warn("Failed to retrieve next objective for {}/{}", hostMac, vlanId);
+        }
+    }
+
+    /**
+     * Update forwarding objective for unicast bridging and unicast routing.
+     * Also check the validity of updated interface configuration on VLAN.
+     *
+     * @param deviceId device ID that host attaches to
+     * @param portNum port number that host attaches to
+     * @param vlanId Vlan ID configured on the switch port
+     * @param popVlan true to pop Vlan tag at TrafficTreatment, false otherwise
+     * @param install true to populate the objective, false to revoke
+     */
+    void processIntfVlanUpdatedEvent(DeviceId deviceId, PortNumber portNum, VlanId vlanId,
+                                 boolean popVlan, boolean install) {
+        ConnectPoint connectPoint = new ConnectPoint(deviceId, portNum);
+        Set<Host> hosts = hostService.getConnectedHosts(connectPoint);
+
+        if (hosts == null || hosts.size() == 0) {
+            return;
+        }
+
+        hosts.forEach(host -> {
+            MacAddress mac = host.mac();
+            VlanId hostVlanId = host.vlan();
+
+            // Check whether the host vlan is valid for new interface configuration
+            if ((!popVlan && hostVlanId.equals(vlanId)) ||
+                    (popVlan && hostVlanId.equals(VlanId.NONE))) {
+                updateBridgingRule(deviceId, portNum, mac, vlanId, popVlan, install);
+                // Update Forwarding objective and corresponding simple Next objective
+                // for each host and IP address connected to given port
+                host.ipAddresses().forEach(ipAddress ->
+                    srManager.routingRulePopulator.updateFwdObj(deviceId, portNum, ipAddress.toIpPrefix(),
+                                                                mac, vlanId, popVlan, install)
+                );
+            }
+        });
+    }
+
+    /**
+     * Populate or revoke routing rule for each host, according to the updated
+     * subnet configuration on the interface.
+     * @param cp connect point of the updated interface
+     * @param ipPrefixSet IP Prefixes added or removed
+     * @param install true if IP Prefixes added, false otherwise
+     */
+    void processIntfIpUpdatedEvent(ConnectPoint cp, Set<IpPrefix> ipPrefixSet, boolean install) {
+        Set<Host> hosts = hostService.getConnectedHosts(cp);
+
+        if (hosts == null || hosts.size() == 0) {
+            log.warn("processIntfIpUpdatedEvent: No hosts connected to {}", cp);
+            return;
+        }
+
+        // Check whether the host IP address is in the interface's subnet
+        hosts.forEach(host ->
+            host.ipAddresses().forEach(hostIpAddress -> {
+                ipPrefixSet.forEach(ipPrefix -> {
+                    if (install && ipPrefix.contains(hostIpAddress)) {
+                            srManager.routingRulePopulator.populateRoute(cp.deviceId(), hostIpAddress.toIpPrefix(),
+                                                                         host.mac(), host.vlan(), cp.port());
+                    } else if (!install && ipPrefix.contains(hostIpAddress)) {
+                            srManager.routingRulePopulator.revokeRoute(cp.deviceId(), hostIpAddress.toIpPrefix(),
+                                                                       host.mac(), host.vlan(), cp.port());
+                    }
+                });
+            }));
+    }
 }
diff --git a/src/main/java/org/onosproject/segmentrouting/McastHandler.java b/src/main/java/org/onosproject/segmentrouting/McastHandler.java
index a51d2f4..a131e4a 100644
--- a/src/main/java/org/onosproject/segmentrouting/McastHandler.java
+++ b/src/main/java/org/onosproject/segmentrouting/McastHandler.java
@@ -806,4 +806,55 @@
         }
         return null;
     }
+
+    /**
+     * Removes filtering objective for given device and port.
+     *
+     * @param deviceId device ID
+     * @param port ingress port number
+     * @param assignedVlan assigned VLAN ID
+     * @param mcastIp multicast IP address
+     */
+    private void removeFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan, IpAddress mcastIp) {
+        // Do nothing if the port is configured as suppressed
+        ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
+        SegmentRoutingAppConfig appConfig = srManager.cfgService
+                .getConfig(srManager.appId, SegmentRoutingAppConfig.class);
+        if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
+            log.info("Ignore suppressed port {}", connectPoint);
+            return;
+        }
+
+        FilteringObjective.Builder filtObjBuilder =
+                filterObjBuilder(deviceId, port, assignedVlan, mcastIp);
+        ObjectiveContext context = new DefaultObjectiveContext(
+                (objective) -> log.debug("Successfully removed filter on {}/{}, vlan {}",
+                                         deviceId, port.toLong(), assignedVlan),
+                (objective, error) ->
+                        log.warn("Failed to remove filter on {}/{}, vlan {}: {}",
+                                 deviceId, port.toLong(), assignedVlan, error));
+        srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.remove(context));
+    }
+
+    /**
+     * Adds or removes filtering objective for given device and port.
+     *
+     * @param deviceId device ID
+     * @param portNum ingress port number
+     * @param vlanId assigned VLAN ID
+     * @param install true to add, false to remove
+     */
+    protected void updateFilterToDevice(DeviceId deviceId, PortNumber portNum,
+                                        VlanId vlanId, boolean install) {
+        srManager.multicastRouteService.getRoutes().forEach(mcastRoute -> {
+            ConnectPoint source = srManager.multicastRouteService.fetchSource(mcastRoute);
+            if (source.deviceId().equals(deviceId) && source.port().equals(portNum)) {
+                if (install) {
+                    addFilterToDevice(deviceId, portNum, vlanId, mcastRoute.group());
+                } else {
+                    removeFilterToDevice(deviceId, portNum, vlanId, mcastRoute.group());
+                }
+            }
+        });
+    }
 }
diff --git a/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java b/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
index 3fb38ba..85d6e1e 100644
--- a/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
+++ b/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
@@ -767,6 +767,23 @@
         return true;
     }
 
+    /**
+     * Updates filtering objectives for a single port. Should only be called by
+     * the master for a switch
+     * @param deviceId device identifier
+     * @param portNum  port identifier for port to be filtered
+     * @param pushVlan true to push vlan, false otherwise
+     * @param vlanId vlan identifier
+     * @param install true to install the filtering objective, false to remove
+     */
+    void updateSinglePortFilters(DeviceId deviceId, PortNumber portNum,
+                                 boolean pushVlan, VlanId vlanId, boolean install) {
+        if (!processSinglePortFiltersInternal(deviceId, portNum, pushVlan, vlanId, install)) {
+            log.warn("Failed to update FilteringObjective for {}/{} with vlan {}",
+                     deviceId, portNum, vlanId);
+        }
+    }
+
     private boolean processSinglePortFiltersInternal(DeviceId deviceId, PortNumber portnum,
                                                       boolean pushVlan, VlanId vlanId, boolean install) {
         FilteringObjective.Builder fob = buildFilteringObjective(deviceId, portnum, pushVlan, vlanId);
@@ -774,7 +791,7 @@
             // error encountered during build
             return false;
         }
-        log.debug("{} filtering objectives for dev/port:{}/{}",
+        log.debug("{} filtering objectives for dev/port: {}/{}",
                  install ? "Installing" : "Removing", deviceId, portnum);
         ObjectiveContext context = new DefaultObjectiveContext(
                 (objective) -> log.debug("Filter for {}/{} {}", deviceId, portnum,
@@ -856,11 +873,44 @@
             allIps.add(pairRouterIpv6);
         }
         for (IpAddress ipaddr : allIps) {
-            TrafficSelector.Builder sbuilder = buildIpSelectorFromIpAddress(ipaddr);
-            Optional<DeviceId> optDeviceId = Optional.of(deviceId);
+            populateSingleIpPunts(deviceId, ipaddr);
+        }
+    }
 
-            srManager.packetService.requestPackets(sbuilder.build(),
-                    PacketPriority.CONTROL, srManager.appId, optDeviceId);
+    /**
+     * Creates a forwarding objective to punt all IP packets, destined to the
+     * specified IP address, which should be router's port IP address.
+     *
+     * @param deviceId the switch dpid for the router
+     * @param ipAddress the IP address of the router's port
+     */
+    void populateSingleIpPunts(DeviceId deviceId, IpAddress ipAddress) {
+        TrafficSelector.Builder sbuilder = buildIpSelectorFromIpAddress(ipAddress);
+        Optional<DeviceId> optDeviceId = Optional.of(deviceId);
+
+        srManager.packetService.requestPackets(sbuilder.build(),
+                                               PacketPriority.CONTROL, srManager.appId, optDeviceId);
+    }
+
+    /**
+     * Removes a forwarding objective to punt all IP packets, destined to the
+     * specified IP address, which should be router's port IP address.
+     *
+     * @param deviceId the switch dpid for the router
+     * @param ipAddress the IP address of the router's port
+     */
+    void revokeSingleIpPunts(DeviceId deviceId, IpAddress ipAddress) {
+        TrafficSelector.Builder sbuilder = buildIpSelectorFromIpAddress(ipAddress);
+        Optional<DeviceId> optDeviceId = Optional.of(deviceId);
+
+        try {
+            if (!ipAddress.equals(config.getRouterIpv4(deviceId)) &&
+                    !ipAddress.equals(config.getRouterIpv6(deviceId))) {
+                srManager.packetService.cancelPackets(sbuilder.build(),
+                                                      PacketPriority.CONTROL, srManager.appId, optDeviceId);
+            }
+        } catch (DeviceConfigNotFoundException e) {
+            log.warn(e.getMessage() + " Aborting revokeSingleIpPunts");
         }
     }
 
@@ -1033,38 +1083,122 @@
      */
     void populateSubnetBroadcastRule(DeviceId deviceId) {
         srManager.getVlanPortMap(deviceId).asMap().forEach((vlanId, ports) -> {
-            int nextId = srManager.getVlanNextObjectiveId(deviceId, vlanId);
-
-            if (nextId < 0) {
-                log.error("Cannot install vlan {} broadcast rule in dev:{} due"
-                        + "to vlanId:{} or nextId:{}", vlanId, deviceId, vlanId, nextId);
-                return;
-            }
-
-            // Driver should treat objective with MacAddress.NONE as the
-            // subnet broadcast rule
-            TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
-            sbuilder.matchVlanId(vlanId);
-            sbuilder.matchEthDst(MacAddress.NONE);
-
-            ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
-            fob.withFlag(Flag.SPECIFIC)
-                    .withSelector(sbuilder.build())
-                    .nextStep(nextId)
-                    .withPriority(SegmentRoutingService.FLOOD_PRIORITY)
-                    .fromApp(srManager.appId)
-                    .makePermanent();
-            ObjectiveContext context = new DefaultObjectiveContext(
-                    (objective) -> log.debug("Vlan broadcast rule for {} populated", vlanId),
-                    (objective, error) ->
-                            log.warn("Failed to populate vlan broadcast rule for {}: {}", vlanId, error));
-            srManager.flowObjectiveService.forward(deviceId, fob.add(context));
+            updateSubnetBroadcastRule(deviceId, vlanId, true);
         });
     }
 
+    /**
+     * Creates or removes a forwarding objective to broadcast packets to its subnet.
+     * @param deviceId switch ID to set the rule
+     * @param vlanId vlan ID to specify the subnet
+     * @param install true to install the rule, false to revoke the rule
+     */
+    void updateSubnetBroadcastRule(DeviceId deviceId, VlanId vlanId, boolean install) {
+        int nextId = srManager.getVlanNextObjectiveId(deviceId, vlanId);
+
+        if (nextId < 0) {
+            log.error("Cannot install vlan {} broadcast rule in dev:{} due"
+                              + " to vlanId:{} or nextId:{}", vlanId, deviceId, vlanId, nextId);
+            return;
+        }
+
+        // Driver should treat objective with MacAddress.NONE as the
+        // subnet broadcast rule
+        TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
+        sbuilder.matchVlanId(vlanId);
+        sbuilder.matchEthDst(MacAddress.NONE);
+
+        ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
+        fob.withFlag(Flag.SPECIFIC)
+                .withSelector(sbuilder.build())
+                .nextStep(nextId)
+                .withPriority(SegmentRoutingService.FLOOD_PRIORITY)
+                .fromApp(srManager.appId)
+                .makePermanent();
+        ObjectiveContext context = new DefaultObjectiveContext(
+                (objective) -> log.debug("Vlan broadcast rule for {} populated", vlanId),
+                (objective, error) ->
+                        log.warn("Failed to populate vlan broadcast rule for {}: {}", vlanId, error));
+
+        if (install) {
+            srManager.flowObjectiveService.forward(deviceId, fob.add(context));
+        } else {
+            srManager.flowObjectiveService.forward(deviceId, fob.remove(context));
+        }
+    }
+
     private int getPriorityFromPrefix(IpPrefix prefix) {
         return (prefix.isIp4()) ?
                 2000 * prefix.prefixLength() + SegmentRoutingService.MIN_IP_PRIORITY :
                 500 * prefix.prefixLength() + SegmentRoutingService.MIN_IP_PRIORITY;
     }
+
+    /**
+     * Update Forwarding objective for each host and IP address connected to given port.
+     * And create corresponding Simple Next objective if it does not exist.
+     * Applied only when populating Forwarding objective
+     * @param deviceId switch ID to set the rule
+     * @param portNumber port number
+     * @param prefix IP prefix of the route
+     * @param hostMac MAC address of the next hop
+     * @param vlanId Vlan ID of the port
+     * @param popVlan true to pop vlan tag in TrafficTreatment
+     * @param install true to populate the forwarding objective, false to revoke
+     */
+    void updateFwdObj(DeviceId deviceId, PortNumber portNumber, IpPrefix prefix, MacAddress hostMac,
+                      VlanId vlanId, boolean popVlan, boolean install) {
+        ForwardingObjective.Builder fob;
+        TrafficSelector.Builder sbuilder = buildIpSelectorFromIpPrefix(prefix);
+        MacAddress deviceMac;
+        try {
+            deviceMac = config.getDeviceMac(deviceId);
+        } catch (DeviceConfigNotFoundException e) {
+            log.warn(e.getMessage() + " Aborting updateFwdObj.");
+            return;
+        }
+
+        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+        tbuilder.deferred()
+                .setEthDst(hostMac)
+                .setEthSrc(deviceMac)
+                .setOutput(portNumber);
+
+        TrafficSelector.Builder mbuilder = DefaultTrafficSelector.builder();
+
+        if (!popVlan) {
+            tbuilder.setVlanId(vlanId);
+        } else {
+            mbuilder.matchVlanId(vlanId);
+        }
+
+        // if the objective is to revoke an existing rule, and for some reason
+        // the next-objective does not exist, then a new one should not be created
+        int portNextObjId = srManager.getPortNextObjectiveId(deviceId, portNumber,
+                                                             tbuilder.build(), mbuilder.build(), install);
+        if (portNextObjId == -1) {
+            // Warning log will come from getPortNextObjective method
+            return;
+        }
+
+        fob = DefaultForwardingObjective.builder().withSelector(sbuilder.build())
+                .nextStep(portNextObjId).fromApp(srManager.appId).makePermanent()
+                .withPriority(getPriorityFromPrefix(prefix)).withFlag(ForwardingObjective.Flag.SPECIFIC);
+
+        ObjectiveContext context = new DefaultObjectiveContext(
+                (objective) -> log.debug("IP rule for route {} {}", prefix, install ? "installed" : "revoked"),
+                (objective, error) ->
+                        log.warn("Failed to {} IP rule for route {}: {}",
+                                 install ? "install" : "revoke", prefix, error));
+        srManager.flowObjectiveService.forward(deviceId, install ? fob.add(context) : fob.remove(context));
+
+        if (!install) {
+            DefaultGroupHandler grpHandler = srManager.getGroupHandler(deviceId);
+            if (grpHandler == null) {
+                log.warn("updateFwdObj: groupHandler for device {} not found", deviceId);
+            } else {
+                // Remove L3UG for the given port and host
+                grpHandler.removeGroupFromPort(portNumber, tbuilder.build(), mbuilder.build());
+            }
+        }
+    }
 }
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);
+    }
 }
diff --git a/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java b/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
index 1a9a80e..848e2ca 100644
--- a/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
+++ b/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
@@ -1016,13 +1016,64 @@
             );
         NextObjective nextObj = nextObjBuilder.add(context);
         flowObjectiveService.next(deviceId, nextObj);
-        log.debug("createBcastGroupFromVlan: Submited next objective {} in device {}",
+        log.debug("createBcastGroupFromVlan: Submitted next objective {} in device {}",
                   nextId, deviceId);
 
         vlanNextObjStore.put(key, nextId);
     }
 
     /**
+     * Removes a single broadcast group from a given vlan id.
+     * The group should be empty.
+     * @param deviceId device Id to remove the group
+     * @param portNum port number related to the group
+     * @param vlanId vlan id of the broadcast group to remove
+     * @param popVlan true if the TrafficTreatment involves pop vlan tag action
+     */
+    public void removeBcastGroupFromVlan(DeviceId deviceId, PortNumber portNum,
+                                         VlanId vlanId, boolean popVlan) {
+        VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
+
+        if (!vlanNextObjStore.containsKey(key)) {
+            log.debug("Broadcast group for device {} and subnet {} does not exist",
+                      deviceId, vlanId);
+            return;
+        }
+
+        TrafficSelector metadata =
+                DefaultTrafficSelector.builder().matchVlanId(vlanId).build();
+
+        int nextId = vlanNextObjStore.get(key);
+
+        NextObjective.Builder nextObjBuilder = DefaultNextObjective
+                .builder().withId(nextId)
+                .withType(NextObjective.Type.BROADCAST).fromApp(appId)
+                .withMeta(metadata);
+
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+        if (popVlan) {
+            tBuilder.popVlan();
+        }
+        tBuilder.setOutput(portNum);
+        nextObjBuilder.addTreatment(tBuilder.build());
+
+        ObjectiveContext context = new DefaultObjectiveContext(
+                (objective) ->
+                        log.debug("removeBroadcastGroupFromVlan removed "
+                                          + "NextObj {} on {}", nextId, deviceId),
+                (objective, error) ->
+                        log.warn("removeBroadcastGroupFromVlan failed to remove "
+                                         + " NextObj {} on {}: {}", nextId, deviceId, error)
+        );
+        NextObjective nextObj = nextObjBuilder.remove(context);
+        flowObjectiveService.next(deviceId, nextObj);
+        log.debug("removeBcastGroupFromVlan: Submited next objective {} in device {}",
+                  nextId, deviceId);
+
+        vlanNextObjStore.remove(key, nextId);
+    }
+
+    /**
      * Determine if we should pop given vlan before sending packets to the given port.
      *
      * @param portNumber port number
@@ -1073,6 +1124,48 @@
     }
 
     /**
+     * Removes simple next objective for a single port.
+     *
+     * @param deviceId device id that has the port to deal with
+     * @param portNum the outgoing port on the device
+     * @param vlanId vlan id associated with the port
+     * @param popVlan true if POP_VLAN action is applied on the packets, false otherwise
+     */
+    public void removePortNextObjective(DeviceId deviceId, PortNumber portNum, VlanId vlanId, boolean popVlan) {
+        TrafficSelector.Builder mbuilder = DefaultTrafficSelector.builder();
+        mbuilder.matchVlanId(vlanId);
+
+        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+        tbuilder.immediate().setOutput(portNum);
+        if (popVlan) {
+            tbuilder.immediate().popVlan();
+        }
+
+        int portNextObjId = srManager.getPortNextObjectiveId(deviceId, portNum,
+                                                             tbuilder.build(), mbuilder.build(), false);
+
+        PortNextObjectiveStoreKey key = new PortNextObjectiveStoreKey(
+                deviceId, portNum, tbuilder.build(), mbuilder.build());
+        if (portNextObjId != -1 && portNextObjStore.containsKey(key)) {
+            NextObjective.Builder nextObjBuilder = DefaultNextObjective
+                    .builder().withId(portNextObjId)
+                    .withType(NextObjective.Type.SIMPLE).fromApp(appId);
+            ObjectiveContext context = new DefaultObjectiveContext(
+                    (objective) -> log.debug("removePortNextObjective removes NextObj {} on {}",
+                                             portNextObjId, deviceId),
+                    (objective, error) ->
+                            log.warn("removePortNextObjective failed to remove NextObj {} on {}: {}",
+                                     portNextObjId, deviceId, error));
+            NextObjective nextObjective = nextObjBuilder.remove(context);
+            log.info("**removePortNextObjective: Submitted "
+                             + "next objective {} in device {}",
+                     portNextObjId, deviceId);
+            flowObjectiveService.next(deviceId, nextObjective);
+
+            portNextObjStore.remove(key);
+        }
+    }
+    /**
      * Removes groups for the next objective ID given.
      *
      * @param objectiveId next objective ID to remove
@@ -1105,6 +1198,42 @@
 
         return false;
     }
+    /**
+     * Remove simple next objective for a single port. The treatments can include
+     * all outgoing actions that need to happen on the packet.
+     *
+     * @param portNum  the outgoing port on the device
+     * @param treatment the actions applied on the packets (should include outport)
+     * @param meta optional data to pass to the driver
+     */
+    public void removeGroupFromPort(PortNumber portNum, TrafficTreatment treatment,
+                                    TrafficSelector meta) {
+        PortNextObjectiveStoreKey key = new PortNextObjectiveStoreKey(
+                deviceId, portNum, treatment, meta);
+        Integer nextId = portNextObjStore.get(key);
+
+        NextObjective.Builder nextObjBuilder = DefaultNextObjective
+                .builder().withId(nextId)
+                .withType(NextObjective.Type.SIMPLE)
+                .addTreatment(treatment)
+                .fromApp(appId)
+                .withMeta(meta);
+
+        ObjectiveContext context = new DefaultObjectiveContext(
+                (objective) ->
+                        log.info("removeGroupFromPort installed "
+                                          + "NextObj {} on {}", nextId, deviceId),
+                (objective, error) ->
+                        log.warn("removeGroupFromPort failed to install"
+                                         + " NextObj {} on {}: {}", nextId, deviceId, error)
+        );
+        NextObjective nextObj = nextObjBuilder.remove(context);
+        flowObjectiveService.next(deviceId, nextObj);
+        log.info("removeGroupFromPort: Submitted next objective {} in device {} "
+                          + "for port {}", nextId, deviceId, portNum);
+
+        portNextObjStore.remove(key);
+    }
 
     /**
      * Removes all groups from all next objective stores.
@@ -1133,6 +1262,40 @@
         bc.run();
     }
 
+    public void updateGroupFromVlanConfiguration(PortNumber portNumber, Collection<VlanId> vlanIds,
+                                                 int nextId, boolean install) {
+        vlanIds.forEach(vlanId -> updateGroupFromVlanInternal(vlanId, portNumber, nextId, install));
+    }
+
+    private void updateGroupFromVlanInternal(VlanId vlanId, PortNumber portNum, int nextId, boolean install) {
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+        if (toPopVlan(portNum, vlanId)) {
+            tBuilder.popVlan();
+        }
+        tBuilder.setOutput(portNum);
+
+        TrafficSelector metadata =
+                DefaultTrafficSelector.builder().matchVlanId(vlanId).build();
+
+        NextObjective.Builder nextObjBuilder = DefaultNextObjective
+                .builder().withId(nextId)
+                .withType(NextObjective.Type.BROADCAST).fromApp(appId)
+                .addTreatment(tBuilder.build())
+                .withMeta(metadata);
+
+        ObjectiveContext context = new DefaultObjectiveContext(
+                (objective) -> log.debug("port {} successfully removedFrom NextObj {} on {}",
+                                         portNum, nextId, deviceId),
+                (objective, error) ->
+                        log.warn("port {} failed to removedFrom NextObj {} on {}: {}",
+                                 portNum, nextId, deviceId, error));
+
+        if (install) {
+            flowObjectiveService.next(deviceId, nextObjBuilder.addToExisting(context));
+        } else {
+            flowObjectiveService.next(deviceId, nextObjBuilder.removeFromExisting(context));
+        }
+    }
 
     /**
      * Performs bucket verification operation for all hash groups in this device.