ONOS-3461 Disable LinkDiscovery on specific device/port.

- Configuration moved from "apps" -> "devices", "ports"
  in network configuration tree

Change-Id: I030bab489939ce5326a6ebea14f246726ca024f0
diff --git a/core/api/src/main/java/org/onosproject/net/config/basics/BasicFeatureConfig.java b/core/api/src/main/java/org/onosproject/net/config/basics/BasicFeatureConfig.java
new file mode 100644
index 0000000..fcf24bc
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/config/basics/BasicFeatureConfig.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.net.config.basics;
+
+import org.onosproject.net.config.Config;
+
+/**
+ * Base abstraction for configuring feature on subject.
+ *
+ * @param <S> Subject type
+ */
+public abstract class BasicFeatureConfig<S> extends Config<S> {
+
+    private static final String ENABLED = "enabled";
+
+    private final boolean defaultValue;
+
+    protected BasicFeatureConfig(boolean defaultValue) {
+        this.defaultValue = defaultValue;
+    }
+
+    /**
+     * Indicates whether the feature for the subject is enabled.
+     *
+     * @return true if feature is enabled
+     */
+    public boolean enabled() {
+        return get(ENABLED, defaultValue);
+    }
+
+    /**
+     * Specifies whether the feature for the subject is to be enabled.
+     *
+     * @param enabled true to enable; false to disable; null to clear
+     * @return self
+     */
+    public BasicFeatureConfig<S> enabled(Boolean enabled) {
+        return (BasicFeatureConfig<S>) setOrClear(ENABLED, enabled);
+    }
+
+}
diff --git a/core/store/dist/src/main/java/org/onosproject/store/config/impl/DistributedNetworkConfigStore.java b/core/store/dist/src/main/java/org/onosproject/store/config/impl/DistributedNetworkConfigStore.java
index ca8eea3..27c7912 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/config/impl/DistributedNetworkConfigStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/config/impl/DistributedNetworkConfigStore.java
@@ -23,6 +23,7 @@
 import com.fasterxml.jackson.databind.node.IntNode;
 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
 import com.fasterxml.jackson.databind.node.LongNode;
+import com.fasterxml.jackson.databind.node.NullNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.fasterxml.jackson.databind.node.ShortNode;
 import com.fasterxml.jackson.databind.node.TextNode;
@@ -95,7 +96,8 @@
                 .register(ConfigKey.class, ObjectNode.class, ArrayNode.class,
                           JsonNodeFactory.class, LinkedHashMap.class,
                           TextNode.class, BooleanNode.class,
-                          LongNode.class, DoubleNode.class, ShortNode.class, IntNode.class);
+                          LongNode.class, DoubleNode.class, ShortNode.class, IntNode.class,
+                          NullNode.class);
 
         configs = storageService.<ConfigKey, JsonNode>consistentMapBuilder()
                 .withSerializer(Serializer.using(kryoBuilder.build()))
diff --git a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscoveryFromDevice.java b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscoveryFromDevice.java
new file mode 100644
index 0000000..32b12d4
--- /dev/null
+++ b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscoveryFromDevice.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.provider.lldp.impl;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.config.basics.BasicFeatureConfig;
+
+/**
+ * Configuration to see LinkDiscovery should be enabled on Device.
+ */
+public class LinkDiscoveryFromDevice extends BasicFeatureConfig<DeviceId> {
+
+    public LinkDiscoveryFromDevice() {
+        // default: enabled
+        super(true);
+    }
+
+}
diff --git a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscoveryFromPort.java b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscoveryFromPort.java
new file mode 100644
index 0000000..b9a502f
--- /dev/null
+++ b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscoveryFromPort.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.provider.lldp.impl;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.config.basics.BasicFeatureConfig;
+
+/**
+ * Configuration to see LinkDiscovery should be enabled on a Port.
+ */
+public class LinkDiscoveryFromPort extends BasicFeatureConfig<ConnectPoint> {
+
+    public LinkDiscoveryFromPort() {
+        // default: enabled
+        super(true);
+    }
+
+}
diff --git a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LldpLinkProvider.java b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LldpLinkProvider.java
index 1b16e51..94abeba 100644
--- a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LldpLinkProvider.java
+++ b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LldpLinkProvider.java
@@ -15,9 +15,28 @@
  */
 package org.onosproject.provider.lldp.impl;
 
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Maps;
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.onlab.packet.Ethernet.TYPE_BSN;
+import static org.onlab.packet.Ethernet.TYPE_LLDP;
+import static org.onlab.util.Tools.get;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.net.Link.Type.DIRECT;
+import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
+import static org.onosproject.net.config.basics.SubjectFactories.CONNECT_POINT_SUBJECT_FACTORY;
+import static org.onosproject.net.config.basics.SubjectFactories.DEVICE_SUBJECT_FACTORY;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.Dictionary;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
+
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -61,25 +80,9 @@
 import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 
-import java.util.Dictionary;
-import java.util.EnumSet;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Properties;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ScheduledExecutorService;
-
-import static com.google.common.base.Strings.isNullOrEmpty;
-import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.onlab.packet.Ethernet.TYPE_BSN;
-import static org.onlab.packet.Ethernet.TYPE_LLDP;
-import static org.onlab.util.Tools.get;
-import static org.onlab.util.Tools.groupedThreads;
-import static org.onosproject.net.Link.Type.DIRECT;
-import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
-import static org.slf4j.LoggerFactory.getLogger;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
 
 /**
  * Provider which uses LLDP and BDDP packets to detect network infrastructure links.
@@ -170,15 +173,15 @@
     private ApplicationId appId;
 
     static final SuppressionRules DEFAULT_RULES
-        = new SuppressionRules(ImmutableSet.of(),
-                               EnumSet.of(Device.Type.ROADM),
+        = new SuppressionRules(EnumSet.of(Device.Type.ROADM),
                                ImmutableMap.of(NO_LLDP, SuppressionRules.ANY_VALUE));
 
     private SuppressionRules rules = LldpLinkProvider.DEFAULT_RULES;
 
     public static final String CONFIG_KEY = "suppression";
+    public static final String FEATURE_NAME = "linkDiscovery";
 
-    private final Set<ConfigFactory> factories = ImmutableSet.of(
+    private final Set<ConfigFactory<?, ?>> factories = ImmutableSet.of(
             new ConfigFactory<ApplicationId, SuppressionConfig>(APP_SUBJECT_FACTORY,
                     SuppressionConfig.class,
                     CONFIG_KEY) {
@@ -186,6 +189,20 @@
                 public SuppressionConfig createConfig() {
                     return new SuppressionConfig();
                 }
+            },
+            new ConfigFactory<DeviceId, LinkDiscoveryFromDevice>(DEVICE_SUBJECT_FACTORY,
+                    LinkDiscoveryFromDevice.class, FEATURE_NAME) {
+                @Override
+                public LinkDiscoveryFromDevice createConfig() {
+                    return new LinkDiscoveryFromDevice();
+                }
+            },
+            new ConfigFactory<ConnectPoint, LinkDiscoveryFromPort>(CONNECT_POINT_SUBJECT_FACTORY,
+                    LinkDiscoveryFromPort.class, FEATURE_NAME) {
+                @Override
+                public LinkDiscoveryFromPort createConfig() {
+                    return new LinkDiscoveryFromPort();
+                }
             }
     );
 
@@ -211,8 +228,7 @@
         if (cfg == null) {
             // If no configuration is found, register default.
             cfg = cfgRegistry.addConfig(appId, SuppressionConfig.class);
-            cfg.deviceIds(DEFAULT_RULES.getSuppressedDevice())
-               .deviceTypes(DEFAULT_RULES.getSuppressedDeviceType())
+            cfg.deviceTypes(DEFAULT_RULES.getSuppressedDeviceType())
                .annotation(DEFAULT_RULES.getSuppressedAnnotation())
                .apply();
         }
@@ -333,6 +349,30 @@
                                .ifPresent(ld -> updatePorts(ld, d.id())));
     }
 
+    private boolean isBlacklisted(DeviceId did) {
+        LinkDiscoveryFromDevice cfg = cfgRegistry.getConfig(did, LinkDiscoveryFromDevice.class);
+        if (cfg == null) {
+            return false;
+        }
+        return !cfg.enabled();
+    }
+
+    private boolean isBlacklisted(ConnectPoint cp) {
+        // if parent device is blacklisted, so is the port
+        if (isBlacklisted(cp.deviceId())) {
+            return true;
+        }
+        LinkDiscoveryFromPort cfg = cfgRegistry.getConfig(cp, LinkDiscoveryFromPort.class);
+        if (cfg == null) {
+            return false;
+        }
+        return !cfg.enabled();
+    }
+
+    private boolean isBlacklisted(Port port) {
+        return isBlacklisted(new ConnectPoint(port.element().id(), port.number()));
+    }
+
     /**
      * Updates discovery helper for specified device.
      *
@@ -343,7 +383,10 @@
      * @return discovery helper if discovery is enabled for the device
      */
     private Optional<LinkDiscovery> updateDevice(Device device) {
-        if (rules.isSuppressed(device)) {
+        if (device == null) {
+            return Optional.empty();
+        }
+        if (rules.isSuppressed(device) || isBlacklisted(device.id())) {
             log.trace("LinkDiscovery from {} disabled by configuration", device.id());
             removeDevice(device.id());
             return Optional.empty();
@@ -382,12 +425,15 @@
      * or calls {@link #removePort(Port)} otherwise.
      */
     private void updatePort(LinkDiscovery discoverer, Port port) {
+        if (port == null) {
+            return;
+        }
         if (port.number().isLogical()) {
             // silently ignore logical ports
             return;
         }
 
-        if (rules.isSuppressed(port)) {
+        if (rules.isSuppressed(port) || isBlacklisted(port)) {
             log.trace("LinkDiscovery from {} disabled by configuration", port);
             removePort(port);
             return;
@@ -659,6 +705,11 @@
         }
     }
 
+    static final EnumSet<NetworkConfigEvent.Type> CONFIG_CHANGED
+                    = EnumSet.of(NetworkConfigEvent.Type.CONFIG_ADDED,
+                                 NetworkConfigEvent.Type.CONFIG_UPDATED,
+                                 NetworkConfigEvent.Type.CONFIG_REMOVED);
+
     private class InternalConfigListener implements NetworkConfigListener {
 
         private synchronized void reconfigureSuppressionRules(SuppressionConfig cfg) {
@@ -667,8 +718,7 @@
                 return;
             }
 
-            SuppressionRules newRules = new SuppressionRules(cfg.deviceIds(),
-                                                             cfg.deviceTypes(),
+            SuppressionRules newRules = new SuppressionRules(cfg.deviceTypes(),
                                                              cfg.annotation());
 
             updateRules(newRules);
@@ -676,7 +726,29 @@
 
         @Override
         public void event(NetworkConfigEvent event) {
-            if (event.configClass().equals(SuppressionConfig.class) &&
+            if (event.configClass() == LinkDiscoveryFromDevice.class &&
+                CONFIG_CHANGED.contains(event.type())) {
+
+                if (event.subject() instanceof DeviceId) {
+                    final DeviceId did = (DeviceId) event.subject();
+                    Device device = deviceService.getDevice(did);
+                    updateDevice(device).ifPresent(ld -> updatePorts(ld, did));
+                }
+
+            } else if (event.configClass() == LinkDiscoveryFromPort.class &&
+                       CONFIG_CHANGED.contains(event.type())) {
+
+                if (event.subject() instanceof ConnectPoint) {
+                    ConnectPoint cp = (ConnectPoint) event.subject();
+                    if (cp.elementId() instanceof DeviceId) {
+                        final DeviceId did = (DeviceId) cp.elementId();
+                        Device device = deviceService.getDevice(did);
+                        Port port = deviceService.getPort(did, cp.port());
+                        updateDevice(device).ifPresent(ld -> updatePort(ld, port));
+                    }
+                }
+
+            } else if (event.configClass().equals(SuppressionConfig.class) &&
                 (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
                  event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED)) {
                 SuppressionConfig cfg = cfgRegistry.getConfig(appId, SuppressionConfig.class);
diff --git a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionConfig.java b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionConfig.java
index 20bafb3..5b10f6d 100644
--- a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionConfig.java
+++ b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionConfig.java
@@ -26,7 +26,6 @@
 
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.Device;
-import org.onosproject.net.DeviceId;
 import org.onosproject.net.config.Config;
 import org.slf4j.Logger;
 
@@ -39,44 +38,22 @@
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
- * LLDP suppression config class.
+ * LinkDiscovery suppression config class.
  */
 public class SuppressionConfig extends Config<ApplicationId> {
-    private static final String DEVICE_IDS = "deviceIds";
+
     private static final String DEVICE_TYPES = "deviceTypes";
     private static final String ANNOTATION = "annotation";
 
     private static final ObjectMapper MAPPER = new ObjectMapper();
-    private static final List<DeviceId>  DEFAULT_DEVICE_IDS
-                    = ImmutableList.copyOf(DEFAULT_RULES.getSuppressedDevice());
+
     private static final List<Device.Type>  DEFAULT_DEVICE_TYPES
                 = ImmutableList.copyOf(DEFAULT_RULES.getSuppressedDeviceType());
 
     private final Logger log = getLogger(getClass());
 
     /**
-     * Returns device IDs on which LLDP is suppressed.
-     *
-     * @return Set of DeviceId objects
-     */
-    @Deprecated
-    public Set<DeviceId> deviceIds() {
-        return ImmutableSet.copyOf(getList(DEVICE_IDS, DeviceId::deviceId, DEFAULT_DEVICE_IDS));
-    }
-
-    /**
-     * Sets device IDs on which LLDP is suppressed.
-     *
-     * @param deviceIds new set of device IDs; null to clear
-     * @return self
-     */
-    @Deprecated
-    public SuppressionConfig deviceIds(Set<DeviceId> deviceIds) {
-        return (SuppressionConfig) setOrClear(DEVICE_IDS, deviceIds);
-    }
-
-    /**
-     * Returns types of devices on which LLDP is suppressed.
+     * Returns types of devices on which LinkDiscovery is suppressed.
      *
      * @return set of device types
      */
@@ -85,7 +62,7 @@
     }
 
     /**
-     * Sets types of devices on which LLDP is suppressed.
+     * Sets types of devices on which LinkDiscovery is suppressed.
      *
      * @param deviceTypes new set of device types; null to clear
      * @return self
@@ -95,7 +72,7 @@
     }
 
     /**
-     * Returns annotation of Ports on which LLDP is suppressed.
+     * Returns annotation of Ports on which LinkDiscovery is suppressed.
      *
      * @return key-value pairs of annotation
      */
@@ -142,7 +119,7 @@
     }
 
     /**
-     * Sets annotation of Ports on which LLDP is suppressed.
+     * Sets annotation of Ports on which LinkDiscovery is suppressed.
      *
      * @param annotation new key-value pair of annotation; null to clear
      * @return self
diff --git a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionRules.java b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionRules.java
index 9cda07c..14bc220 100644
--- a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionRules.java
+++ b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionRules.java
@@ -23,7 +23,6 @@
 
 import org.onosproject.net.Annotations;
 import org.onosproject.net.Device;
-import org.onosproject.net.DeviceId;
 import org.onosproject.net.Element;
 import org.onosproject.net.Port;
 
@@ -35,23 +34,17 @@
 
     public static final String ANY_VALUE = "(any)";
 
-    private final Set<DeviceId> suppressedDevice;
     private final Set<Device.Type> suppressedDeviceType;
     private final Map<String, String> suppressedAnnotation;
 
-    public SuppressionRules(Set<DeviceId> suppressedDevice,
-                     Set<Device.Type> suppressedType,
-                     Map<String, String> suppressedAnnotation) {
+    public SuppressionRules(Set<Device.Type> suppressedType,
+                            Map<String, String> suppressedAnnotation) {
 
-        this.suppressedDevice = ImmutableSet.copyOf(suppressedDevice);
         this.suppressedDeviceType = ImmutableSet.copyOf(suppressedType);
         this.suppressedAnnotation = ImmutableMap.copyOf(suppressedAnnotation);
     }
 
     public boolean isSuppressed(Device device) {
-        if (suppressedDevice.contains(device.id())) {
-            return true;
-        }
         if (suppressedDeviceType.contains(device.type())) {
             return true;
         }
@@ -94,10 +87,6 @@
         return false;
     }
 
-    Set<DeviceId> getSuppressedDevice() {
-        return suppressedDevice;
-    }
-
     Set<Device.Type> getSuppressedDeviceType() {
         return suppressedDeviceType;
     }
@@ -108,8 +97,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(suppressedDevice,
-                            suppressedDeviceType,
+        return Objects.hash(suppressedDeviceType,
                             suppressedAnnotation);
     }
 
@@ -117,9 +105,7 @@
     public boolean equals(Object object) {
         if (object != null && getClass() == object.getClass()) {
             SuppressionRules that = (SuppressionRules) object;
-            return Objects.equals(this.suppressedDevice,
-                                  that.suppressedDevice)
-                    && Objects.equals(this.suppressedDeviceType,
+            return Objects.equals(this.suppressedDeviceType,
                                       that.suppressedDeviceType)
                     && Objects.equals(this.suppressedAnnotation,
                                       that.suppressedAnnotation);
@@ -130,7 +116,6 @@
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(this)
-                .add("suppressedDevice", suppressedDevice)
                 .add("suppressedDeviceType", suppressedDeviceType)
                 .add("suppressedAnnotation", suppressedAnnotation)
                 .toString();
diff --git a/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LldpLinkProviderTest.java b/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LldpLinkProviderTest.java
index ecf60c1..758c34e 100644
--- a/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LldpLinkProviderTest.java
+++ b/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LldpLinkProviderTest.java
@@ -15,7 +15,6 @@
  */
 package org.onosproject.provider.lldp.impl;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -47,8 +46,8 @@
 import org.onosproject.net.Port;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.config.Config;
-import org.onosproject.net.config.ConfigApplyDelegate;
 import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigEvent.Type;
 import org.onosproject.net.config.NetworkConfigListener;
 import org.onosproject.net.config.NetworkConfigRegistryAdapter;
 import org.onosproject.net.device.DeviceEvent;
@@ -121,8 +120,14 @@
 
     private TestSuppressionConfig cfg;
 
+    private Set<DeviceId> deviceBlacklist;
+
+    private Set<ConnectPoint> portBlacklist;
+
     @Before
     public void setUp() {
+        deviceBlacklist = new HashSet<>();
+        portBlacklist = new HashSet<>();
         cfg = new TestSuppressionConfig();
         coreService = createMock(CoreService.class);
         expect(coreService.registerApplication(appId.name()))
@@ -174,7 +179,7 @@
      * Checks that links on a reconfigured switch are properly removed.
      */
     @Test
-    public void switchSuppressed() {
+    public void switchSuppressedByAnnotation() {
 
         // add device to stub DeviceService
         deviceService.putDevice(device(DID3));
@@ -196,6 +201,26 @@
     }
 
     @Test
+    public void switchSuppressByBlacklist() {
+        // add device in stub DeviceService
+        deviceService.putDevice(device(DID3));
+        deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID3));
+
+        // add deviveId to device blacklist
+        deviceBlacklist.add(DID3);
+        configListener.event(new NetworkConfigEvent(Type.CONFIG_ADDED,
+                                                    DID3,
+                                                    LinkDiscoveryFromDevice.class));
+
+        // discovery helper for device is expected to be gone or stopped
+        LinkDiscovery linkDiscovery = provider.discoverers.get(DID3);
+        if (linkDiscovery != null) {
+            assertTrue("Discovery expected to be stopped", linkDiscovery.isStopped());
+        }
+
+    }
+
+    @Test
     public void portUp() {
         deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1));
         deviceListener.event(portEvent(DeviceEvent.Type.PORT_ADDED, DID1, port(DID1, 3, true)));
@@ -269,7 +294,7 @@
      * Checks that discovery on reconfigured switch are properly restarted.
      */
     @Test
-    public void portSuppressedByDeviceIdConfig() {
+    public void portSuppressedByParentDeviceIdBlacklist() {
 
         /// When Device is configured without suppression:OFF,
         /// Port should be included for discovery
@@ -288,9 +313,11 @@
         assertTrue("Discoverer should contain the port there", provider.discoverers.get(DID3).containsPort(portno3));
 
         // add suppression rule for "deviceId: "of:0000000000000003""
-        provider.updateRules(new SuppressionRules(ImmutableSet.of(DID3),
-                                                  ImmutableSet.of(),
-                                                  ImmutableMap.of()));
+        deviceBlacklist.add(DID3);
+        configListener.event(new NetworkConfigEvent(Type.CONFIG_ADDED,
+                                                    DID3,
+                                                    LinkDiscoveryFromDevice.class));
+
 
         /// When Device is reconfigured with suppression:ON, Port also is same
 
@@ -300,7 +327,7 @@
         deviceService.putPorts(DID3, port(DID3, portno3, true));
         deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_UPDATED, DID3));
 
-        // discovery on device is expected to be stopped
+        // discovery helper for device is expected to be gone or stopped
         LinkDiscovery linkDiscovery = provider.discoverers.get(DID3);
         if (linkDiscovery != null) {
             assertTrue("Discovery expected to be stopped", linkDiscovery.isStopped());
@@ -371,6 +398,34 @@
     }
 
     @Test
+    public void portSuppressedByPortBlacklist() {
+
+        // add device in stub DeviceService without suppression configured
+        deviceService.putDevice(device(DID3));
+        deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID3));
+
+        final long portno3 = 3L;
+        final Port port3 = port(DID3, portno3, true);
+
+        final ConnectPoint cpDid3no3 = new ConnectPoint(DID3, PortNumber.portNumber(portno3));
+        portBlacklist.add(cpDid3no3);
+
+        // suppressed port added to non-suppressed device
+        deviceService.putPorts(DID3, port3);
+        deviceListener.event(portEvent(DeviceEvent.Type.PORT_ADDED, DID3, port3));
+
+        configListener.event(new NetworkConfigEvent(Type.CONFIG_ADDED,
+                                                    cpDid3no3,
+                                                    LinkDiscoveryFromPort.class));
+
+        // discovery helper should be there turned on
+        assertFalse("Discoverer is expected to start", provider.discoverers.get(DID3).isStopped());
+        // but port is not a discovery target
+        assertFalse("Discoverer should not contain the port there",
+                    provider.discoverers.get(DID3).containsPort(portno3));
+    }
+
+    @Test
     public void portUnknown() {
         deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1));
         // Note: DID3 hasn't been added to TestDeviceService, but only port is added
@@ -481,42 +536,6 @@
         return false;
     }
 
-
-    @Test
-    public void addDeviceIdRule() {
-        DeviceId deviceId1 = DeviceId.deviceId("of:0000000000000001");
-        DeviceId deviceId2 = DeviceId.deviceId("of:0000000000000002");
-        Set<DeviceId> deviceIds = new HashSet<>();
-
-        deviceIds.add(deviceId1);
-        cfg.deviceIds(deviceIds);
-
-        configEvent(NetworkConfigEvent.Type.CONFIG_ADDED);
-
-        assertTrue(provider.rules().getSuppressedDevice().contains(deviceId1));
-        assertFalse(provider.rules().getSuppressedDevice().contains(deviceId2));
-    }
-
-    @Test
-    public void updateDeviceIdRule() {
-        DeviceId deviceId1 = DeviceId.deviceId("of:0000000000000001");
-        DeviceId deviceId2 = DeviceId.deviceId("of:0000000000000002");
-        Set<DeviceId> deviceIds = new HashSet<>();
-
-        deviceIds.add(deviceId1);
-        cfg.deviceIds(deviceIds);
-
-        configEvent(NetworkConfigEvent.Type.CONFIG_ADDED);
-
-        deviceIds.add(deviceId2);
-        cfg.deviceIds(deviceIds);
-
-        configEvent(NetworkConfigEvent.Type.CONFIG_UPDATED);
-
-        assertTrue(provider.rules().getSuppressedDevice().contains(deviceId1));
-        assertTrue(provider.rules().getSuppressedDevice().contains(deviceId2));
-    }
-
     @Test
     public void addDeviceTypeRule() {
         Device.Type deviceType1 = Device.Type.ROADM;
@@ -866,11 +885,28 @@
 
     private final class TestNetworkConfigRegistry
         extends NetworkConfigRegistryAdapter {
+        @SuppressWarnings("unchecked")
         @Override
-        public <S, C extends Config<S>> C getConfig(S subject, Class<C> configClass) {
-            ConfigApplyDelegate delegate = config -> { };
-            ObjectMapper mapper = new ObjectMapper();
-            return (C) cfg;
+        public <S, C extends Config<S>> C getConfig(S subj, Class<C> configClass) {
+            if (configClass == SuppressionConfig.class) {
+                return (C) cfg;
+            } else if (configClass == LinkDiscoveryFromDevice.class) {
+                return (C) new LinkDiscoveryFromDevice() {
+                    @Override
+                    public boolean enabled() {
+                        return !deviceBlacklist.contains(subj);
+                    }
+                };
+            } else if (configClass == LinkDiscoveryFromPort.class) {
+                return (C) new LinkDiscoveryFromPort() {
+                    @Override
+                    public boolean enabled() {
+                        return !portBlacklist.contains(subj);
+                    }
+                };
+            } else {
+                return null;
+            }
         }
 
         @Override
@@ -880,22 +916,10 @@
     }
 
     private final class TestSuppressionConfig extends SuppressionConfig {
-        private Set<DeviceId> deviceIds = new HashSet<>(DEFAULT_RULES.getSuppressedDevice());
         private Set<Device.Type> deviceTypes = new HashSet<>(DEFAULT_RULES.getSuppressedDeviceType());
         private Map<String, String> annotation = new HashMap<>(DEFAULT_RULES.getSuppressedAnnotation());
 
         @Override
-        public Set<DeviceId> deviceIds() {
-            return ImmutableSet.copyOf(deviceIds);
-        }
-
-        @Override
-        public SuppressionConfig deviceIds(Set<DeviceId> deviceIds) {
-            this.deviceIds = ImmutableSet.copyOf(deviceIds);
-            return this;
-        }
-
-        @Override
         public Set<Device.Type> deviceTypes() {
             return ImmutableSet.copyOf(deviceTypes);
         }
diff --git a/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionConfigTest.java b/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionConfigTest.java
index 781ed17..85061ca 100644
--- a/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionConfigTest.java
+++ b/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionConfigTest.java
@@ -39,21 +39,6 @@
     }
 
     @Test
-    public void testDeviceIds() {
-        Set<DeviceId> inputIds = new HashSet<DeviceId>() { {
-            add(DEVICE_ID_1);
-            add(DEVICE_ID_2);
-        } };
-
-        assertNotNull(cfg.deviceIds(inputIds));
-
-        Set<DeviceId> outputIds = cfg.deviceIds();
-        assertTrue(outputIds.contains(DEVICE_ID_1));
-        assertTrue(outputIds.contains(DEVICE_ID_2));
-        assertEquals(outputIds.size(), 2);
-    }
-
-    @Test
     public void testDeviceTypes() {
         Set<Device.Type> inputTypes = new HashSet<Device.Type>() { {
             add(DEVICE_TYPE_1);
diff --git a/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionRulesTest.java b/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionRulesTest.java
index 03d431a..c18c248 100644
--- a/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionRulesTest.java
+++ b/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionRulesTest.java
@@ -51,22 +51,12 @@
 
     @Before
     public void setUp() throws Exception {
-        rules = new SuppressionRules(ImmutableSet.of(SUPPRESSED_DID),
-                               ImmutableSet.of(Device.Type.ROADM),
+        rules = new SuppressionRules(ImmutableSet.of(Device.Type.ROADM),
                                ImmutableMap.of("no-lldp", SuppressionRules.ANY_VALUE,
                                                "sendLLDP", "false"));
     }
 
     @Test
-    public void testSuppressedDeviceId() {
-        Device device = new DefaultDevice(PID,
-                                          SUPPRESSED_DID,
-                                          Device.Type.SWITCH,
-                                          MFR, HW, SW1, SN, CID);
-        assertTrue(rules.isSuppressed(device));
-    }
-
-    @Test
     public void testSuppressedDeviceType() {
         Device device = new DefaultDevice(PID,
                                           NON_SUPPRESSED_DID,
@@ -111,17 +101,6 @@
     }
 
     @Test
-    public void testSuppressedPortOnSuppressedDevice() {
-        Device device = new DefaultDevice(PID,
-                                          SUPPRESSED_DID,
-                                          Device.Type.SWITCH,
-                                          MFR, HW, SW1, SN, CID);
-        Port port = new DefaultPort(device, P1, true);
-
-        assertTrue(rules.isSuppressed(port));
-    }
-
-    @Test
     public void testSuppressedPortAnnotation() {
         Annotations annotation = DefaultAnnotations.builder()
                 .set("no-lldp", "random")
diff --git a/tools/package/config/samples/network-cfg-linkdiscovery.json b/tools/package/config/samples/network-cfg-linkdiscovery.json
new file mode 100644
index 0000000..fcc805f
--- /dev/null
+++ b/tools/package/config/samples/network-cfg-linkdiscovery.json
@@ -0,0 +1,35 @@
+{
+    "ports": {
+        "of:0000000000000002/3": {
+            "linkDiscovery": {
+                "enabled": false
+            }
+        },
+        "of:0000000000000002/2": {
+            "linkDiscovery": {
+                "enabled": false
+            }
+        }
+    },
+    "devices": {
+        "of:0000000000000001": {
+            "linkDiscovery": {
+                "enabled": false
+            }
+        }
+    },
+    "apps": {
+        "org.onosproject.provider.lldp": {
+            "suppression": {
+                "deviceTypes": [
+                    "ROADM"
+                ],
+                "annotation": {
+                    "no-lldp": null,
+                    "sendLLDP": "false"
+                }
+            }
+        }
+    }
+}
+
diff --git a/tools/package/config/samples/network-cfg.json b/tools/package/config/samples/network-cfg.json
index a11237d..9e9257e 100644
--- a/tools/package/config/samples/network-cfg.json
+++ b/tools/package/config/samples/network-cfg.json
@@ -62,12 +62,6 @@
 		]
 	    }
 	}
-        "org.onosproject.provider.lldp": {
-            "suppression": {
-                "deviceIds": [ "of:2222000000000000" ],
-                "deviceTypes": [ "ROADM" ],
-                "annotation": { "no-lldp": null, "sendLLDP" : "false" }
-            }
-        }
     }
 }
+