Add INT watchlist config

- add "watchSubnets" to the netcfg of INT app

Change-Id: I271d9c27692c8969999ece32f8aa08b4392216e0
(cherry picked from commit 906febe058507758b67f7074bb1139e3c6170b52)
diff --git a/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/api/IntIntent.java b/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/api/IntIntent.java
index 8ce9d0f..69d538c 100644
--- a/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/api/IntIntent.java
+++ b/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/api/IntIntent.java
@@ -16,6 +16,7 @@
 package org.onosproject.inbandtelemetry.api;
 
 import com.google.common.annotations.Beta;
+import com.google.common.base.Objects;
 import org.onosproject.net.behaviour.inbandtelemetry.IntMetadataType;
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.TrafficSelector;
@@ -175,6 +176,27 @@
         return new Builder();
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        IntIntent intIntent = (IntIntent) o;
+        return Objects.equal(selector, intIntent.selector) &&
+                Objects.equal(metadataTypes, intIntent.metadataTypes) &&
+                headerType == intIntent.headerType &&
+                Objects.equal(reportTypes, intIntent.reportTypes) &&
+                telemetryMode == intIntent.telemetryMode;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(selector, metadataTypes, headerType, reportTypes, telemetryMode);
+    }
+
     /**
      * An IntIntent builder.
      */
@@ -246,7 +268,6 @@
          * @return an IntIntent
          */
         public IntIntent build() {
-            checkArgument(!selector.criteria().isEmpty(), "Empty selector cannot match any flow.");
             checkNotNull(headerType, "Header type cannot be null.");
             checkArgument(!reportTypes.isEmpty(), "Report types cannot be empty.");
             checkNotNull(telemetryMode, "Telemetry mode cannot be null.");
diff --git a/apps/inbandtelemetry/impl/src/main/java/org/onosproject/inbandtelemetry/impl/SimpleIntManager.java b/apps/inbandtelemetry/impl/src/main/java/org/onosproject/inbandtelemetry/impl/SimpleIntManager.java
index 25d6a50..ffb6b4f 100644
--- a/apps/inbandtelemetry/impl/src/main/java/org/onosproject/inbandtelemetry/impl/SimpleIntManager.java
+++ b/apps/inbandtelemetry/impl/src/main/java/org/onosproject/inbandtelemetry/impl/SimpleIntManager.java
@@ -44,6 +44,8 @@
 import org.onosproject.net.device.DeviceEvent;
 import org.onosproject.net.device.DeviceListener;
 import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.host.HostEvent;
 import org.onosproject.net.host.HostListener;
 import org.onosproject.net.host.HostService;
@@ -591,6 +593,32 @@
                                         .enabled(true)
                                         .build();
                                 setConfig(intDeviceConfig);
+
+                                // For each watched subnet, we install two INT rules.
+                                // One match on the source, another match on the destination.
+                                intentMap.clear();
+                                config.watchSubnets().forEach(subnet -> {
+                                    IntIntent.Builder intIntentBuilder = IntIntent.builder()
+                                            .withReportType(IntIntent.IntReportType.TRACKED_FLOW)
+                                            .withReportType(IntIntent.IntReportType.DROPPED_PACKET)
+                                            .withReportType(IntIntent.IntReportType.CONGESTED_QUEUE)
+                                            .withTelemetryMode(IntIntent.TelemetryMode.POSTCARD);
+                                    if (subnet.prefixLength() == 0) {
+                                        // Special case, match any packet
+                                        installIntIntent(intIntentBuilder
+                                                .withSelector(DefaultTrafficSelector.emptySelector())
+                                                .build());
+                                    } else {
+                                        TrafficSelector selector = DefaultTrafficSelector.builder()
+                                                .matchIPSrc(subnet)
+                                                .build();
+                                        installIntIntent(intIntentBuilder.withSelector(selector).build());
+                                        selector = DefaultTrafficSelector.builder()
+                                                .matchIPDst(subnet)
+                                                .build();
+                                        installIntIntent(intIntentBuilder.withSelector(selector).build());
+                                    }
+                                });
                             });
                     break;
                 // TODO: Support removing INT config.
diff --git a/apps/inbandtelemetry/impl/src/test/java/org/onosproject/inbandtelemetry/impl/SimpleIntManagerTest.java b/apps/inbandtelemetry/impl/src/test/java/org/onosproject/inbandtelemetry/impl/SimpleIntManagerTest.java
index 566ed2b..8519ed5 100644
--- a/apps/inbandtelemetry/impl/src/test/java/org/onosproject/inbandtelemetry/impl/SimpleIntManagerTest.java
+++ b/apps/inbandtelemetry/impl/src/test/java/org/onosproject/inbandtelemetry/impl/SimpleIntManagerTest.java
@@ -27,6 +27,7 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.onlab.junit.TestUtils;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.TpPort;
@@ -35,6 +36,7 @@
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.inbandtelemetry.api.IntIntent;
+import org.onosproject.inbandtelemetry.api.IntIntentId;
 import org.onosproject.mastership.MastershipService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DefaultDevice;
@@ -58,6 +60,7 @@
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.host.HostService;
+import org.onosproject.store.service.ConsistentMap;
 import org.onosproject.store.service.StorageService;
 import org.onosproject.store.service.TestStorageService;
 
@@ -65,6 +68,7 @@
 import java.io.InputStream;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import static org.easymock.EasyMock.anyObject;
 import static org.easymock.EasyMock.createNiceMock;
@@ -90,12 +94,14 @@
     private static final int MIN_FLOW_HOP_LATENCY_CHANGE_NS = 32;
     private static final String INT_REPORT_CONFIG_KEY = "report";
     private static final DeviceId DEVICE_ID = DeviceId.deviceId("device:leaf1");
+    private static final String WATCHED_SUBNET_1 = "192.168.10.0/24";
+    private static final String WATCHED_SUBNET_2 = "192.168.20.0/24";
     private static final TrafficSelector FLOW_SELECTOR1 = DefaultTrafficSelector.builder()
-            .matchIPDst(IpPrefix.valueOf("192.168.10.0/24"))
+            .matchIPDst(IpPrefix.valueOf(WATCHED_SUBNET_1))
             .matchVlanId(VlanId.vlanId((short) 10))
             .build();
     private static final TrafficSelector FLOW_SELECTOR2 = DefaultTrafficSelector.builder()
-            .matchIPDst(IpPrefix.valueOf("192.168.20.0/24"))
+            .matchIPDst(IpPrefix.valueOf(WATCHED_SUBNET_2))
             .matchVlanId(VlanId.vlanId((short) 20))
             .build();
     private static final Device DEFAULT_DEVICE =
@@ -182,6 +188,49 @@
         IntDeviceConfig expectedConfig = createIntDeviceConfig();
         IntDeviceConfig actualConfig = manager.getConfig();
         assertEquals(expectedConfig, actualConfig);
+
+        // Install watch subnets via netcfg
+        // In the report-config.json, there are 3 subnets we want to watch
+        // For subnet 0.0.0.0/0, the IntManager will create only one IntIntent with an empty selector.
+        Set<IntIntent> expectedIntIntents = Sets.newHashSet();
+        ConsistentMap<IntIntentId, IntIntent> intentMap = TestUtils.getField(manager, "intentMap");
+        IntIntent.Builder baseIntentBuilder = IntIntent.builder()
+                .withReportType(IntIntent.IntReportType.TRACKED_FLOW)
+                .withReportType(IntIntent.IntReportType.DROPPED_PACKET)
+                .withReportType(IntIntent.IntReportType.CONGESTED_QUEUE)
+                .withTelemetryMode(IntIntent.TelemetryMode.POSTCARD);
+
+        // Watch IP Src == subnet 1
+        TrafficSelector expectedSelector = DefaultTrafficSelector.builder()
+                .matchIPSrc(IpPrefix.valueOf(WATCHED_SUBNET_1))
+                .build();
+        expectedIntIntents.add(baseIntentBuilder.withSelector(expectedSelector).build());
+        // Watch IP Dst == subnet 1
+        expectedSelector = DefaultTrafficSelector.builder()
+                .matchIPDst(IpPrefix.valueOf(WATCHED_SUBNET_1))
+                .build();
+        expectedIntIntents.add(baseIntentBuilder.withSelector(expectedSelector).build());
+        // Watch IP Src == subnet 2
+        expectedSelector = DefaultTrafficSelector.builder()
+                .matchIPSrc(IpPrefix.valueOf(WATCHED_SUBNET_2))
+                .build();
+        expectedIntIntents.add(baseIntentBuilder.withSelector(expectedSelector).build());
+        // Watch IP Dst == subnet 2
+        expectedSelector = DefaultTrafficSelector.builder()
+                .matchIPDst(IpPrefix.valueOf(WATCHED_SUBNET_2))
+                .build();
+        expectedIntIntents.add(baseIntentBuilder.withSelector(expectedSelector).build());
+        // Any packets
+        expectedSelector = DefaultTrafficSelector.emptySelector();
+        expectedIntIntents.add(baseIntentBuilder.withSelector(expectedSelector).build());
+
+        // The INT intent installation order can be random, so we need to collect
+        // all expected INT intents and check if actual intent exists.
+        assertEquals(5, intentMap.size());
+        intentMap.entrySet().forEach(entry -> {
+            IntIntent actualIntIntent = entry.getValue().value();
+            assertTrue(expectedIntIntents.contains(actualIntIntent));
+        });
     }
 
     @Test
diff --git a/apps/inbandtelemetry/impl/src/test/resources/report-config.json b/apps/inbandtelemetry/impl/src/test/resources/report-config.json
index c0c1ca2..366aeab 100644
--- a/apps/inbandtelemetry/impl/src/test/resources/report-config.json
+++ b/apps/inbandtelemetry/impl/src/test/resources/report-config.json
@@ -1,5 +1,10 @@
 {
   "collectorIp": "10.0.0.1",
   "collectorPort": 32766,
-  "minFlowHopLatencyChangeNs": 32
+  "minFlowHopLatencyChangeNs": 32,
+  "watchSubnets": [
+    "0.0.0.0/0",
+    "192.168.10.0/24",
+    "192.168.20.0/24"
+  ]
 }
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/inbandtelemetry/IntObjective.java b/core/api/src/main/java/org/onosproject/net/behaviour/inbandtelemetry/IntObjective.java
index 4d94e62..41d927f 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/inbandtelemetry/IntObjective.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/inbandtelemetry/IntObjective.java
@@ -23,8 +23,6 @@
 import java.util.HashSet;
 import java.util.Set;
 
-import static com.google.common.base.Preconditions.checkArgument;
-
 /**
  * Represents a device-level objective to collect INT metadata for packets
  * identified by a traffic selector.
@@ -129,8 +127,6 @@
          * @return an IntObjective
          */
         public IntObjective build() {
-            checkArgument(!selector.criteria().isEmpty(), "Empty selector cannot match any flow.");
-
             return new IntObjective(selector, metadataTypes);
         }
     }
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/inbandtelemetry/IntReportConfig.java b/core/api/src/main/java/org/onosproject/net/behaviour/inbandtelemetry/IntReportConfig.java
index 9575607..158a992 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/inbandtelemetry/IntReportConfig.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/inbandtelemetry/IntReportConfig.java
@@ -15,14 +15,20 @@
  */
 package org.onosproject.net.behaviour.inbandtelemetry;
 
+import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.google.common.annotations.Beta;
+import com.google.common.collect.Sets;
 import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.TpPort;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.config.Config;
 import org.onosproject.ui.JsonUtils;
 
+import java.util.Collections;
+import java.util.Set;
+
 /**
  * Application level configuration of the INT process.
  * Config example:
@@ -32,7 +38,11 @@
  *       "report": {
  *         "collectorIp": "192.168.0.1",
  *         "collectorPort": 5500,
- *         "minFlowHopLatencyChangeNs": 300
+ *         "minFlowHopLatencyChangeNs": 300,
+ *         "watchSubnets": [
+ *           "192.168.0.0/24",
+ *           "10.140.0.0/16"
+ *         ]
  *       }
  *     }
  *   }
@@ -46,6 +56,7 @@
     private static final String COLLECTOR_NEXT_HOP_MAC = "collectorNextHopMac";
     private static final String SINK_IP = "sinkIp";
     private static final String SINK_MAC = "sinkMac";
+    private static final String WATCH_SUBNETS = "watchSubnets";
 
     /**
      * IP address of the collector.
@@ -146,6 +157,24 @@
     }
 
     /**
+     * Gets subnets to be watched.
+     *
+     * @return subnets to be watched
+     */
+    public Set<IpPrefix> watchSubnets() {
+        if (object.hasNonNull(WATCH_SUBNETS) && object.path(WATCH_SUBNETS).isArray()) {
+            Set<IpPrefix> subnets = Sets.newHashSet();
+            ArrayNode subnetArray = (ArrayNode) object.path(WATCH_SUBNETS);
+            subnetArray.forEach(subnetNode -> {
+                subnets.add(IpPrefix.valueOf(subnetNode.asText()));
+            });
+            return subnets;
+        } else {
+            return Collections.EMPTY_SET;
+        }
+    }
+
+    /**
      * Sets the collector IP to the config.
      *
      * @param collectorIp the collector IP
@@ -205,4 +234,14 @@
     public IntReportConfig setSinkMac(MacAddress sinkMac) {
         return (IntReportConfig) setOrClear(SINK_MAC, sinkMac.toString());
     }
+
+    /**
+     * Sets subnets to be watched.
+     *
+     * @param subnets subnets to be watched.
+     * @return the config
+     */
+    public IntReportConfig setWatchSubnets(Set<IpPrefix> subnets) {
+        return (IntReportConfig) setOrClear(WATCH_SUBNETS, subnets);
+    }
 }