[CORD-1850] Ignore DHCP on a particular VLAN

Change-Id: I4867d1ffa960e602f29280f16c7177ddff1fe4da
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java
index 99b8afb..1ff500e 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java
@@ -17,12 +17,17 @@
 
 import java.nio.ByteBuffer;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Dictionary;
+import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Stream;
 
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multimap;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -49,10 +54,20 @@
 import org.onosproject.dhcprelay.api.DhcpHandler;
 import org.onosproject.dhcprelay.api.DhcpRelayService;
 import org.onosproject.dhcprelay.config.DefaultDhcpRelayConfig;
+import org.onosproject.dhcprelay.config.IgnoreDhcpConfig;
 import org.onosproject.dhcprelay.config.IndirectDhcpRelayConfig;
 import org.onosproject.dhcprelay.store.DhcpRecord;
 import org.onosproject.dhcprelay.store.DhcpRelayStore;
+import org.onosproject.net.DeviceId;
 import org.onosproject.net.config.Config;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.UdpPortCriterion;
+import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveContext;
+import org.onosproject.net.flowobjective.ObjectiveError;
 import org.onosproject.net.intf.Interface;
 import org.onosproject.net.intf.InterfaceService;
 import org.onosproject.net.ConnectPoint;
@@ -92,6 +107,36 @@
             "org.onosproject.provider.host.impl.HostLocationProvider";
     public static final String ROUTE_STORE_IMPL =
             "org.onosproject.routeservice.store.RouteStoreImpl";
+    private static final TrafficSelector DHCP_SERVER_SELECTOR = DefaultTrafficSelector.builder()
+            .matchEthType(Ethernet.TYPE_IPV4)
+            .matchIPProtocol(IPv4.PROTOCOL_UDP)
+            .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
+            .build();
+    private static final TrafficSelector DHCP_CLIENT_SELECTOR = DefaultTrafficSelector.builder()
+            .matchEthType(Ethernet.TYPE_IPV4)
+            .matchIPProtocol(IPv4.PROTOCOL_UDP)
+            .matchUdpDst(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
+            .build();
+    private static final TrafficSelector DHCP6_SERVER_SELECTOR = DefaultTrafficSelector.builder()
+            .matchEthType(Ethernet.TYPE_IPV6)
+            .matchIPProtocol(IPv4.PROTOCOL_UDP)
+            .matchUdpDst(TpPort.tpPort(UDP.DHCP_V6_SERVER_PORT))
+            .build();
+    private static final TrafficSelector DHCP6_CLIENT_SELECTOR = DefaultTrafficSelector.builder()
+            .matchEthType(Ethernet.TYPE_IPV6)
+            .matchIPProtocol(IPv4.PROTOCOL_UDP)
+            .matchUdpDst(TpPort.tpPort(UDP.DHCP_V6_CLIENT_PORT))
+            .build();
+    static final List<TrafficSelector> DHCP_SELECTORS = ImmutableList.of(
+            DHCP_SERVER_SELECTOR,
+            DHCP_CLIENT_SELECTOR,
+            DHCP6_SERVER_SELECTOR,
+            DHCP6_CLIENT_SELECTOR
+    );
+    private static final TrafficSelector ARP_SELECTOR = DefaultTrafficSelector.builder()
+            .matchEthType(Ethernet.TYPE_ARP)
+            .build();
+    private static final int IGNORE_CONTROL_PRIORITY = PacketPriority.CONTROL.priorityValue() + 1000;
     private final Logger log = LoggerFactory.getLogger(getClass());
     private final InternalConfigListener cfgListener = new InternalConfigListener();
 
@@ -113,6 +158,15 @@
                 public IndirectDhcpRelayConfig createConfig() {
                     return new IndirectDhcpRelayConfig();
                 }
+            },
+            new ConfigFactory<ApplicationId, IgnoreDhcpConfig>(APP_SUBJECT_FACTORY,
+                                                               IgnoreDhcpConfig.class,
+                                                               IgnoreDhcpConfig.KEY,
+                                                               true) {
+                @Override
+                public IgnoreDhcpConfig createConfig() {
+                    return new IgnoreDhcpConfig();
+                }
             }
     );
 
@@ -137,6 +191,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ComponentConfigService compCfgService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowObjectiveService flowObjectiveService;
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY,
                target = "(version=4)")
     protected DhcpHandler v4Handler;
@@ -149,6 +206,7 @@
              label = "Enable Address resolution protocol")
     protected boolean arpEnabled = true;
 
+    protected Multimap<DeviceId, VlanId> ignoredVlans = HashMultimap.create();
     private DhcpRelayPacketProcessor dhcpRelayPacketProcessor = new DhcpRelayPacketProcessor();
     private ApplicationId appId;
 
@@ -218,14 +276,18 @@
                 cfgService.getConfig(appId, DefaultDhcpRelayConfig.class);
         IndirectDhcpRelayConfig indirectConfig =
                 cfgService.getConfig(appId, IndirectDhcpRelayConfig.class);
+        IgnoreDhcpConfig ignoreDhcpConfig =
+                cfgService.getConfig(appId, IgnoreDhcpConfig.class);
 
         if (defaultConfig != null) {
             updateConfig(defaultConfig);
         }
-
         if (indirectConfig != null) {
             updateConfig(indirectConfig);
         }
+        if (ignoreDhcpConfig != null) {
+            updateConfig(ignoreDhcpConfig);
+        }
     }
 
     /**
@@ -233,12 +295,7 @@
      *
      * @param config the configuration ot update
      */
-    private void updateConfig(Config config) {
-        if (config == null) {
-            // Ignore if config is not present
-            return;
-        }
-
+    protected void updateConfig(Config config) {
         if (config instanceof IndirectDhcpRelayConfig) {
             IndirectDhcpRelayConfig indirectConfig = (IndirectDhcpRelayConfig) config;
             v4Handler.setIndirectDhcpServerConfigs(indirectConfig.dhcpServerConfigs());
@@ -248,58 +305,141 @@
             v4Handler.setDefaultDhcpServerConfigs(defaultConfig.dhcpServerConfigs());
             v6Handler.setDefaultDhcpServerConfigs(defaultConfig.dhcpServerConfigs());
         }
+        if (config instanceof IgnoreDhcpConfig) {
+            addIgnoreVlanRules((IgnoreDhcpConfig) config);
+        }
+    }
+
+    protected void removeConfig(Config config) {
+        if (config instanceof IndirectDhcpRelayConfig) {
+            v4Handler.setIndirectDhcpServerConfigs(Collections.emptyList());
+            v6Handler.setIndirectDhcpServerConfigs(Collections.emptyList());
+        } else if (config instanceof DefaultDhcpRelayConfig) {
+            v4Handler.setDefaultDhcpServerConfigs(Collections.emptyList());
+            v6Handler.setDefaultDhcpServerConfigs(Collections.emptyList());
+        }
+        if (config instanceof IgnoreDhcpConfig) {
+            ignoredVlans.forEach(this::removeIgnoreVlanRule);
+            ignoredVlans.clear();
+        }
+    }
+
+    private void addIgnoreVlanRules(IgnoreDhcpConfig config) {
+        config.ignoredVlans().forEach((deviceId, vlanId) -> {
+            if (ignoredVlans.get(deviceId).contains(vlanId)) {
+                // don't need to process if it already ignored
+                return;
+            }
+            installIgnoreVlanRule(deviceId, vlanId);
+            ignoredVlans.put(deviceId, vlanId);
+        });
+
+        Multimap<DeviceId, VlanId> removedVlans = HashMultimap.create();
+        ignoredVlans.forEach((deviceId, vlanId) -> {
+            if (!config.ignoredVlans().get(deviceId).contains(vlanId)) {
+                // not contains in new config, remove it
+                removeIgnoreVlanRule(deviceId, vlanId);
+                removedVlans.put(deviceId, vlanId);
+
+            }
+        });
+        removedVlans.forEach(ignoredVlans::remove);
+    }
+
+    private void installIgnoreVlanRule(DeviceId deviceId, VlanId vlanId) {
+        TrafficTreatment dropTreatment = DefaultTrafficTreatment.emptyTreatment();
+        dropTreatment.clearedDeferred();
+        DHCP_SELECTORS.forEach(trafficSelector -> {
+            UdpPortCriterion udpDst = (UdpPortCriterion) trafficSelector.getCriterion(Criterion.Type.UDP_DST);
+            TrafficSelector selector = DefaultTrafficSelector.builder(trafficSelector)
+                    .matchVlanId(vlanId)
+                    .build();
+
+            ForwardingObjective fwd = DefaultForwardingObjective.builder()
+                    .withFlag(ForwardingObjective.Flag.VERSATILE)
+                    .withSelector(selector)
+                    .withPriority(IGNORE_CONTROL_PRIORITY)
+                    .withTreatment(dropTreatment)
+                    .fromApp(appId)
+                    .add(new ObjectiveContext() {
+                        @Override
+                        public void onSuccess(Objective objective) {
+                            log.info("Vlan id {} from device {} ignored (UDP port {})",
+                                     vlanId, deviceId, udpDst.udpPort().toInt());
+                        }
+
+                        @Override
+                        public void onError(Objective objective, ObjectiveError error) {
+                            log.warn("Can't ignore vlan id {} from device {} due to {}",
+                                     vlanId, deviceId, error);
+                        }
+                    });
+            flowObjectiveService.apply(deviceId, fwd);
+        });
+    }
+
+    private void removeIgnoreVlanRule(DeviceId deviceId, VlanId vlanId) {
+        TrafficTreatment dropTreatment = DefaultTrafficTreatment.emptyTreatment();
+        dropTreatment.clearedDeferred();
+        DHCP_SELECTORS.forEach(trafficSelector -> {
+            UdpPortCriterion udpDst = (UdpPortCriterion) trafficSelector.getCriterion(Criterion.Type.UDP_DST);
+            TrafficSelector selector = DefaultTrafficSelector.builder(trafficSelector)
+                    .matchVlanId(vlanId)
+                    .build();
+
+            ForwardingObjective fwd = DefaultForwardingObjective.builder()
+                    .withFlag(ForwardingObjective.Flag.VERSATILE)
+                    .withSelector(selector)
+                    .withPriority(IGNORE_CONTROL_PRIORITY)
+                    .withTreatment(dropTreatment)
+                    .fromApp(appId)
+                    .remove(new ObjectiveContext() {
+                        @Override
+                        public void onSuccess(Objective objective) {
+                            log.info("Vlan id {} from device {} ignore rule removed (UDP port {})",
+                                     vlanId, deviceId, udpDst.udpPort().toInt());
+                        }
+
+                        @Override
+                        public void onError(Objective objective, ObjectiveError error) {
+                            log.warn("Can't remove ignore rule of vlan id {} from device {} due to {}",
+                                     vlanId, deviceId, error);
+                        }
+                    });
+            flowObjectiveService.apply(deviceId, fwd);
+        });
     }
 
     /**
      * Request DHCP packet in via PacketService.
      */
     private void requestDhcpPackets() {
-        TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPProtocol(IPv4.PROTOCOL_UDP)
-                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
-        packetService.requestPackets(selectorServer.build(), PacketPriority.CONTROL, appId);
-
-        TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPProtocol(IPv4.PROTOCOL_UDP)
-                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
-        packetService.requestPackets(selectorClient.build(), PacketPriority.CONTROL, appId);
+        DHCP_SELECTORS.forEach(trafficSelector -> {
+            packetService.requestPackets(trafficSelector, PacketPriority.CONTROL, appId);
+        });
     }
 
     /**
      * Cancel requested DHCP packets in via packet service.
      */
     private void cancelDhcpPackets() {
-        TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPProtocol(IPv4.PROTOCOL_UDP)
-                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
-        packetService.cancelPackets(selectorServer.build(), PacketPriority.CONTROL, appId);
-
-        TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPProtocol(IPv4.PROTOCOL_UDP)
-                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
-        packetService.cancelPackets(selectorClient.build(), PacketPriority.CONTROL, appId);
+        DHCP_SELECTORS.forEach(trafficSelector -> {
+            packetService.cancelPackets(trafficSelector, PacketPriority.CONTROL, appId);
+        });
     }
 
     /**
      * Request ARP packet in via PacketService.
      */
     private void requestArpPackets() {
-        TrafficSelector.Builder selectorArpServer = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_ARP);
-        packetService.requestPackets(selectorArpServer.build(), PacketPriority.CONTROL, appId);
+        packetService.requestPackets(ARP_SELECTOR, PacketPriority.CONTROL, appId);
     }
 
     /**
      * Cancel requested ARP packets in via packet service.
      */
     private void cancelArpPackets() {
-        TrafficSelector.Builder selectorArpServer = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_ARP);
-        packetService.cancelPackets(selectorArpServer.build(), PacketPriority.CONTROL, appId);
+        packetService.cancelPackets(ARP_SELECTOR, PacketPriority.CONTROL, appId);
     }
 
     @Override
@@ -449,20 +589,25 @@
     private class InternalConfigListener implements NetworkConfigListener {
         @Override
         public void event(NetworkConfigEvent event) {
-            if (event.type() != NetworkConfigEvent.Type.CONFIG_ADDED &&
-                    event.type() != NetworkConfigEvent.Type.CONFIG_UPDATED) {
-                // Ignore unhandled event type
-                return;
+            switch (event.type()) {
+                case CONFIG_UPDATED:
+                case CONFIG_ADDED:
+                    event.config().ifPresent(config -> {
+                        updateConfig(config);
+                        log.info("{} updated", config.getClass().getSimpleName());
+                    });
+                    break;
+                case CONFIG_REMOVED:
+                    event.prevConfig().ifPresent(config -> {
+                        removeConfig(config);
+                        log.info("{} removed", config.getClass().getSimpleName());
+                    });
+                    break;
+                default:
+                    log.warn("Unsupported event type {}", event.type());
+                    break;
             }
-            if (!event.configClass().equals(DefaultDhcpRelayConfig.class) &&
-                    !event.configClass().equals(IndirectDhcpRelayConfig.class)) {
-                // Ignore unhandled config type
-                return;
-            }
-            event.config().ifPresent(config -> {
-                updateConfig(config);
-                log.info("Reconfigured");
-            });
+
         }
     }
 }
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/config/IgnoreDhcpConfig.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/config/IgnoreDhcpConfig.java
new file mode 100644
index 0000000..b4b8adb
--- /dev/null
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/config/IgnoreDhcpConfig.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.dhcprelay.config;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.config.Config;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class IgnoreDhcpConfig extends Config<ApplicationId> {
+    public static final String KEY = "ignoreDhcp";
+    private static final String DEVICE_ID = "deviceId";
+    private static final String VLAN_ID = "vlan";
+
+    @Override
+    public boolean isValid() {
+        AtomicBoolean valid = new AtomicBoolean(true);
+        if (array == null) {
+            return false;
+        }
+        array.forEach(node -> {
+            valid.compareAndSet(true, node.has(DEVICE_ID) && node.has(VLAN_ID));
+        });
+        return valid.get();
+    }
+
+    public Multimap<DeviceId, VlanId> ignoredVlans() {
+        Multimap<DeviceId, VlanId> ignored = ArrayListMultimap.create();
+
+        array.forEach(node -> {
+            DeviceId deviceId = DeviceId.deviceId(node.get(DEVICE_ID).asText());
+            VlanId vlanId = VlanId.vlanId((short) node.get(VLAN_ID).asInt());
+            ignored.put(deviceId, vlanId);
+        });
+
+        return ignored;
+    }
+}
diff --git a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
index 45cfa23..eb93286 100644
--- a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
+++ b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
@@ -15,12 +15,17 @@
  */
 package org.onosproject.dhcprelay;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
+import com.google.common.io.Resources;
 import org.apache.commons.io.Charsets;
+import org.easymock.Capture;
+import org.easymock.CaptureType;
 import org.easymock.EasyMock;
 import org.junit.After;
 import org.junit.Before;
@@ -45,12 +50,22 @@
 import org.onosproject.dhcprelay.api.DhcpHandler;
 import org.onosproject.dhcprelay.config.DefaultDhcpRelayConfig;
 import org.onosproject.dhcprelay.config.DhcpServerConfig;
+import org.onosproject.dhcprelay.config.IgnoreDhcpConfig;
 import org.onosproject.dhcprelay.config.IndirectDhcpRelayConfig;
 import org.onosproject.dhcprelay.store.DhcpRecord;
 import org.onosproject.dhcprelay.store.DhcpRelayStore;
 import org.onosproject.dhcprelay.store.DhcpRelayStoreEvent;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.Objective;
 import org.onosproject.net.intf.Interface;
 import org.onosproject.net.intf.InterfaceServiceAdapter;
+import org.onosproject.net.packet.PacketPriority;
 import org.onosproject.routeservice.Route;
 import org.onosproject.routeservice.RouteStoreAdapter;
 import org.onosproject.net.ConnectPoint;
@@ -86,8 +101,12 @@
 
 import static org.easymock.EasyMock.*;
 import static org.junit.Assert.*;
+import static org.onosproject.dhcprelay.DhcpRelayManager.DHCP_RELAY_APP;
 
 public class DhcpRelayManagerTest {
+    private static final String CONFIG_FILE_PATH = "dhcp-relay.json";
+    private static final DeviceId DEV_1_ID = DeviceId.deviceId("of:0000000000000001");
+    private static final DeviceId DEV_2_ID = DeviceId.deviceId("of:0000000000000002");
     // Ip address for interfaces
     private static final InterfaceIpAddress INTERFACE_IP = InterfaceIpAddress.valueOf("10.0.3.254/32");
     private static final List<InterfaceIpAddress> INTERFACE_IPS = ImmutableList.of(INTERFACE_IP);
@@ -165,6 +184,8 @@
             SERVER_INTERFACE
     );
     private static final String NON_ONOS_CID = "Non-ONOS circuit ID";
+    private static final VlanId IGNORED_VLAN = VlanId.vlanId("100");
+    private static final int IGNORE_CONTROL_PRIORITY = PacketPriority.CONTROL.priorityValue() + 1000;
 
     private DhcpRelayManager manager;
     private MockPacketService packetService;
@@ -204,6 +225,7 @@
         manager.dhcpRelayStore = mockDhcpRelayStore;
 
         manager.interfaceService = new MockInterfaceService();
+        manager.flowObjectiveService = EasyMock.niceMock(FlowObjectiveService.class);
 
         Dhcp4HandlerImpl v4Handler = new Dhcp4HandlerImpl();
         v4Handler.dhcpRelayStore = mockDhcpRelayStore;
@@ -333,6 +355,95 @@
         assertArrayEquals(arp.getSenderHardwareAddress(), CLIENT_INTERFACE.mac().toBytes());
     }
 
+    /**
+     * Ignores specific vlans from specific devices if config.
+     *
+     * @throws Exception the exception from this test
+     */
+    @Test
+    public void testIgnoreVlan() throws Exception {
+        ObjectMapper om = new ObjectMapper();
+        JsonNode json = om.readTree(Resources.getResource(CONFIG_FILE_PATH));
+        IgnoreDhcpConfig config = new IgnoreDhcpConfig();
+        json = json.path("apps").path(DHCP_RELAY_APP).path(IgnoreDhcpConfig.KEY);
+        config.init(APP_ID, IgnoreDhcpConfig.KEY, json, om, null);
+
+        Capture<Objective> capturedFromDev1 = newCapture(CaptureType.ALL);
+        manager.flowObjectiveService.apply(eq(DEV_1_ID), capture(capturedFromDev1));
+        expectLastCall().times(2);
+        Capture<Objective> capturedFromDev2 = newCapture(CaptureType.ALL);
+        manager.flowObjectiveService.apply(eq(DEV_2_ID), capture(capturedFromDev2));
+        expectLastCall().times(2);
+        replay(manager.flowObjectiveService);
+        manager.updateConfig(config);
+        verify(manager.flowObjectiveService);
+
+        List<Objective> objectivesFromDev1 = capturedFromDev1.getValues();
+        List<Objective> objectivesFromDev2 = capturedFromDev2.getValues();
+
+        assertTrue(objectivesFromDev1.containsAll(objectivesFromDev2));
+        assertTrue(objectivesFromDev2.containsAll(objectivesFromDev1));
+        TrafficTreatment dropTreatment = DefaultTrafficTreatment.emptyTreatment();
+        dropTreatment.clearedDeferred();
+
+        for (int index = 0; index < objectivesFromDev1.size(); index++) {
+            TrafficSelector selector =
+                    DefaultTrafficSelector.builder(DhcpRelayManager.DHCP_SELECTORS.get(index))
+                    .matchVlanId(IGNORED_VLAN)
+                    .build();
+            ForwardingObjective fwd = (ForwardingObjective) objectivesFromDev1.get(index);
+            assertEquals(selector, fwd.selector());
+            assertEquals(dropTreatment, fwd.treatment());
+            assertEquals(IGNORE_CONTROL_PRIORITY, fwd.priority());
+            assertEquals(ForwardingObjective.Flag.VERSATILE, fwd.flag());
+            assertEquals(Objective.Operation.ADD, fwd.op());
+        }
+
+        assertEquals(2, manager.ignoredVlans.size());
+    }
+
+    /**
+     * "IgnoreVlan" policy should be removed when the config removed.
+     */
+    @Test
+    public void testRemoveIgnoreVlan() {
+        manager.ignoredVlans.put(DEV_1_ID, IGNORED_VLAN);
+        manager.ignoredVlans.put(DEV_2_ID, IGNORED_VLAN);
+        IgnoreDhcpConfig config = new IgnoreDhcpConfig();
+
+        Capture<Objective> capturedFromDev1 = newCapture(CaptureType.ALL);
+        manager.flowObjectiveService.apply(eq(DEV_1_ID), capture(capturedFromDev1));
+        expectLastCall().times(2);
+        Capture<Objective> capturedFromDev2 = newCapture(CaptureType.ALL);
+        manager.flowObjectiveService.apply(eq(DEV_2_ID), capture(capturedFromDev2));
+        expectLastCall().times(2);
+        replay(manager.flowObjectiveService);
+        manager.removeConfig(config);
+        verify(manager.flowObjectiveService);
+
+        List<Objective> objectivesFromDev1 = capturedFromDev1.getValues();
+        List<Objective> objectivesFromDev2 = capturedFromDev2.getValues();
+
+        assertTrue(objectivesFromDev1.containsAll(objectivesFromDev2));
+        assertTrue(objectivesFromDev2.containsAll(objectivesFromDev1));
+        TrafficTreatment dropTreatment = DefaultTrafficTreatment.emptyTreatment();
+        dropTreatment.clearedDeferred();
+
+        for (int index = 0; index < objectivesFromDev1.size(); index++) {
+            TrafficSelector selector =
+                    DefaultTrafficSelector.builder(DhcpRelayManager.DHCP_SELECTORS.get(index))
+                    .matchVlanId(IGNORED_VLAN)
+                    .build();
+            ForwardingObjective fwd = (ForwardingObjective) objectivesFromDev1.get(index);
+            assertEquals(selector, fwd.selector());
+            assertEquals(dropTreatment, fwd.treatment());
+            assertEquals(IGNORE_CONTROL_PRIORITY, fwd.priority());
+            assertEquals(ForwardingObjective.Flag.VERSATILE, fwd.flag());
+            assertEquals(Objective.Operation.REMOVE, fwd.op());
+        }
+        assertEquals(0, manager.ignoredVlans.size());
+    }
+
     private static class MockDefaultDhcpRelayConfig extends DefaultDhcpRelayConfig {
         @Override
         public boolean isValid() {
diff --git a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/config/DhcpRelayConfigTest.java b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/config/DhcpRelayConfigTest.java
index 4160e06..c743247 100644
--- a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/config/DhcpRelayConfigTest.java
+++ b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/config/DhcpRelayConfigTest.java
@@ -27,7 +27,6 @@
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.ConnectPoint;
 
-
 import java.io.IOException;
 
 import static org.junit.Assert.*;
@@ -45,7 +44,6 @@
     private static final Ip4Address DEFAULT_GATEWAY_IP = Ip4Address.valueOf("192.168.10.254");
     private static final Ip6Address DEFAULT_SERVER_IP_V6 = Ip6Address.valueOf("2000::200:1");
     private static final Ip6Address DEFAULT_GATEWAY_IP_V6 = Ip6Address.valueOf("1000::100:1");
-
     private static final ConnectPoint INDIRECT_CONNECT_POINT = ConnectPoint.deviceConnectPoint("of:0000000000000002/3");
     private static final Ip4Address INDIRECT_SERVER_IP = Ip4Address.valueOf("172.168.10.3");
 
diff --git a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/config/IgnoreDhcpConfigTest.java b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/config/IgnoreDhcpConfigTest.java
new file mode 100644
index 0000000..3712c06
--- /dev/null
+++ b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/config/IgnoreDhcpConfigTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.dhcprelay.config;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.io.Resources;
+import org.junit.Test;
+import org.onlab.packet.VlanId;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.dhcprelay.DhcpRelayManager.DHCP_RELAY_APP;
+
+public class IgnoreDhcpConfigTest {
+    private static final String CONFIG_FILE_PATH = "dhcp-relay.json";
+    private static final ApplicationId APP_ID = new TestApplicationId("DhcpRelayTest");
+    private static final DeviceId DEV_1_ID = DeviceId.deviceId("of:0000000000000001");
+    private static final DeviceId DEV_2_ID = DeviceId.deviceId("of:0000000000000002");
+    private static final VlanId IGNORED_VLAN = VlanId.vlanId("100");
+    @Test
+    public void testIgnoredDhcpConfig() throws IOException {
+        ObjectMapper om = new ObjectMapper();
+        JsonNode json = om.readTree(Resources.getResource(CONFIG_FILE_PATH));
+        IgnoreDhcpConfig config = new IgnoreDhcpConfig();
+        json = json.path("apps").path(DHCP_RELAY_APP).path(IgnoreDhcpConfig.KEY);
+        config.init(APP_ID, IgnoreDhcpConfig.KEY, json, om, null);
+
+        assertEquals(2, config.ignoredVlans().size());
+        Collection<VlanId> vlanForDev1 = config.ignoredVlans().get(DEV_1_ID);
+        Collection<VlanId> vlanForDev2 = config.ignoredVlans().get(DEV_2_ID);
+
+        assertEquals(1, vlanForDev1.size());
+        assertEquals(1, vlanForDev2.size());
+
+        assertTrue(vlanForDev1.contains(IGNORED_VLAN));
+        assertTrue(vlanForDev2.contains(IGNORED_VLAN));
+    }
+}
diff --git a/apps/dhcprelay/src/test/resources/dhcp-relay.json b/apps/dhcprelay/src/test/resources/dhcp-relay.json
index dc724ee..e77d8a7 100644
--- a/apps/dhcprelay/src/test/resources/dhcp-relay.json
+++ b/apps/dhcprelay/src/test/resources/dhcp-relay.json
@@ -15,6 +15,10 @@
           "serverIps": ["172.168.10.3"],
           "relayAgentIps": ["10.0.1.1"]
         }
+      ],
+      "ignoreDhcp": [
+        {"deviceId": "of:0000000000000001", "vlan": 100},
+        {"deviceId": "of:0000000000000002", "vlan": 100}
       ]
     }
   }