Support install IntIntents to device with postcard mode

Include the following changes:
 - INT application now can install reuls/configs to the device based on the
   Int intent telemetry mode and the functionality supported by device.
 - Add "Telemetry Mode" option to INT app GUI

Change-Id: I1a9938b076030497a3b2984abe66fed09c190c7d
diff --git a/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/ui/IntAppTableMessageHandler.java b/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/ui/IntAppTableMessageHandler.java
index 2c44696..b696b1d 100644
--- a/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/ui/IntAppTableMessageHandler.java
+++ b/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/ui/IntAppTableMessageHandler.java
@@ -56,8 +56,10 @@
     private static final String DST_PORT = "dstPort";
     private static final String PROTOCOL = "protocol";
     private static final String METADATA = "metadata";
+    private static final String TELEMETRY_MODE = "telemetryMode";
 
-    private static final String[] COLUMN_IDS = {ID, SRC_ADDR, DST_ADDR, SRC_PORT, DST_PORT, PROTOCOL, METADATA};
+    private static final String[] COLUMN_IDS = {
+            ID, SRC_ADDR, DST_ADDR, SRC_PORT, DST_PORT, PROTOCOL, METADATA, TELEMETRY_MODE};
 
     private final Logger log = LoggerFactory.getLogger(getClass());
 
@@ -130,6 +132,7 @@
                 metaStr += ", ";
             }
             row.cell(METADATA, metaStr);
+            row.cell(TELEMETRY_MODE, intent.telemetryMode());
         }
     }
 
diff --git a/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/ui/IntAppUiMessageHandler.java b/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/ui/IntAppUiMessageHandler.java
index af47755..56a7f5f 100644
--- a/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/ui/IntAppUiMessageHandler.java
+++ b/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/ui/IntAppUiMessageHandler.java
@@ -154,10 +154,23 @@
                 }
             }
 
+            jsonNodeVal = payload.get("telemetryMode");
+            if (jsonNodeVal != null) {
+                if (jsonNodeVal.asText()
+                        .equalsIgnoreCase(IntIntent.TelemetryMode.POSTCARD.toString())) {
+                    builder.withTelemetryMode(IntIntent.TelemetryMode.POSTCARD);
+                } else if (jsonNodeVal.asText()
+                        .equalsIgnoreCase(IntIntent.TelemetryMode.INBAND_TELEMETRY.toString())) {
+                    builder.withTelemetryMode(IntIntent.TelemetryMode.INBAND_TELEMETRY);
+                } else {
+                    log.warn("Unsupport telemetry mode {}", jsonNodeVal.asText());
+                    return;
+                }
+            }
+
             builder.withSelector(sBuilder.build())
                     .withHeaderType(HOP_BY_HOP)
-                    .withReportType(IntIntent.IntReportType.TRACKED_FLOW)
-                    .withTelemetryMode(IntIntent.TelemetryMode.INBAND_TELEMETRY);
+                    .withReportType(IntIntent.IntReportType.TRACKED_FLOW);
             intService.installIntIntent(builder.build());
         }
 
diff --git a/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.html b/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.html
index a2d3b88..300b50f 100644
--- a/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.html
+++ b/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.html
@@ -46,6 +46,13 @@
                             <option value="UDP">UDP</option>
                         </select>
                     </label>
+                    <label>
+                        Telemetry Mode
+                        <select name="telemetryMode" ng-model="telemetryMode">
+                            <option selected value="POSTCARD">Postcard (INT-XD)</option>
+                            <option value="INBAND_TELEMETRY">Embedded Data (INT-MD)</option>
+                        </select>
+                    </label>
                 </div>
                 <div>
                     <label>
@@ -122,6 +129,7 @@
                         <td colId="dstPort" sortable>Dst Port</td>
                         <td colId="protocol" sortable>Protocol</td>
                         <td colId="metadata" sortable>Metadata</td>
+                        <td colId="telemetryMode" sortable>Telemetry Mode</td>
                     </tr>
                 </table>
             </div>
@@ -138,6 +146,7 @@
                         <td>{{item.dstPort}}</td>
                         <td>{{item.protocol}}</td>
                         <td>{{item.metadata}}</td>
+                        <td>{{item.telemetryMode}}</td>
                     </tr>
                 </table>
             </div>
diff --git a/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.js b/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.js
index f8913a0..ea6dffa 100644
--- a/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.js
+++ b/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.js
@@ -83,7 +83,8 @@
             "l4SrcPort": $scope.l4SrcPort,
             "l4DstPort": $scope.l4DstPort,
             "protocol": $scope.protocol,
-            "metadata": inst
+            "metadata": inst,
+            "telemetryMode": $scope.telemetryMode
         };
         if (checkArgAndShowMsg()) {
             wss.sendEvent(intIntentAddReq, intentObjectNode);
@@ -228,6 +229,7 @@
                 $scope.l4SrcPort = "";
                 $scope.l4DstPort = "";
                 $scope.protocol = "";
+                $scope.telemetryMode = "POSTCARD";
 
                 // get data the first time...
                 // getData();
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 8506a40..25d6a50 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
@@ -65,7 +65,6 @@
 import org.osgi.service.component.annotations.ReferenceCardinality;
 import org.slf4j.Logger;
 
-import java.util.Collection;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -101,25 +100,25 @@
     private static final String APP_NAME = "org.onosproject.inbandtelemetry";
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
-    private CoreService coreService;
+    protected CoreService coreService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
-    private DeviceService deviceService;
+    protected DeviceService deviceService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
-    private StorageService storageService;
+    protected StorageService storageService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
-    private MastershipService mastershipService;
+    protected MastershipService mastershipService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
-    private HostService hostService;
+    protected HostService hostService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
-    private NetworkConfigService netcfgService;
+    protected NetworkConfigService netcfgService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
-    private NetworkConfigRegistry netcfgRegistry;
+    protected NetworkConfigRegistry netcfgRegistry;
 
     private final Striped<Lock> deviceLocks = Striped.lock(10);
 
@@ -386,7 +385,7 @@
         device.as(IntProgrammable.class).cleanup();
     }
 
-    private boolean configDevice(DeviceId deviceId) {
+    protected boolean configDevice(DeviceId deviceId) {
         // Returns true if config was successful, false if not and a clean up is
         // needed.
         final Device device = deviceService.getDevice(deviceId);
@@ -400,12 +399,11 @@
         }
 
         final boolean isEdge = !hostService.getConnectedHosts(deviceId).isEmpty();
-        final IntDeviceRole intDeviceRole = isEdge
-                ? IntDeviceRole.SOURCE_SINK
-                : IntDeviceRole.TRANSIT;
+        final IntDeviceRole intDeviceRole =
+                isEdge ? IntDeviceRole.SOURCE_SINK : IntDeviceRole.TRANSIT;
 
         log.info("Started programming of INT device {} with role {}...",
-                 deviceId, intDeviceRole);
+                deviceId, intDeviceRole);
 
         final IntProgrammable intProg = device.as(IntProgrammable.class);
 
@@ -419,12 +417,16 @@
             return false;
         }
 
-        if (intDeviceRole != IntDeviceRole.SOURCE_SINK) {
-            // Stop here, no more configuration needed for transit devices.
+        boolean supportSource = intProg.supportsFunctionality(IntProgrammable.IntFunctionality.SOURCE);
+        boolean supportSink = intProg.supportsFunctionality(IntProgrammable.IntFunctionality.SINK);
+        boolean supportPostcard = intProg.supportsFunctionality(IntProgrammable.IntFunctionality.POSTCARD);
+
+        if (intDeviceRole != IntDeviceRole.SOURCE_SINK && !supportPostcard) {
+            // Stop here, no more configuration needed for transit devices unless it support postcard.
             return true;
         }
 
-        if (intProg.supportsFunctionality(IntProgrammable.IntFunctionality.SINK)) {
+        if (supportSink || supportPostcard) {
             if (!intProg.setupIntConfig(intConfig.get())) {
                 log.warn("Unable to apply INT report config on {}", deviceId);
                 return false;
@@ -440,14 +442,14 @@
                 .collect(Collectors.toSet());
 
         for (PortNumber port : hostPorts) {
-            if (intProg.supportsFunctionality(IntProgrammable.IntFunctionality.SOURCE)) {
+            if (supportSource) {
                 log.info("Setting port {}/{} as INT source port...", deviceId, port);
                 if (!intProg.setSourcePort(port)) {
                     log.warn("Unable to set INT source port {} on {}", port, deviceId);
                     return false;
                 }
             }
-            if (intProg.supportsFunctionality(IntProgrammable.IntFunctionality.SINK)) {
+            if (supportSink) {
                 log.info("Setting port {}/{} as INT sink port...", deviceId, port);
                 if (!intProg.setSinkPort(port)) {
                     log.warn("Unable to set INT sink port {} on {}", port, deviceId);
@@ -456,28 +458,32 @@
             }
         }
 
-        if (!intProg.supportsFunctionality(IntProgrammable.IntFunctionality.SOURCE)) {
-            // Stop here, no more configuration needed for sink devices.
+        if (!supportSource && !supportPostcard) {
+            // Stop here, no more configuration needed for sink devices unless
+            // it supports postcard mode.
             return true;
         }
 
         // Apply intents.
         // This is a trivial implementation where we simply get the
-        // corresponding INT objective from an intent and we apply to all source
-        // device.
-        final Collection<IntObjective> objectives = intentMap.values().stream()
-                .map(v -> getIntObjective(v.value()))
-                .collect(Collectors.toList());
+        // corresponding INT objective from an intent and we apply to all
+        // device which support reporting.
         int appliedCount = 0;
-        for (IntObjective objective : objectives) {
-            if (intProg.addIntObjective(objective)) {
-                appliedCount = appliedCount + 1;
+        for (Versioned<IntIntent> versionedIntent : intentMap.values()) {
+            IntIntent intent = versionedIntent.value();
+            IntObjective intObjective = getIntObjective(intent);
+            if (intent.telemetryMode() == IntIntent.TelemetryMode.INBAND_TELEMETRY && supportSource) {
+                intProg.addIntObjective(intObjective);
+                appliedCount++;
+            } else if (intent.telemetryMode() == IntIntent.TelemetryMode.POSTCARD && supportPostcard) {
+                intProg.addIntObjective(intObjective);
+                appliedCount++;
+            } else {
+                log.warn("Device {} does not support intent {}.", deviceId, intent);
             }
         }
-
         log.info("Completed programming of {}, applied {} INT objectives of {} total",
-                 deviceId, appliedCount, objectives.size());
-
+                deviceId, appliedCount, intentMap.size());
         return true;
     }
 
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 6f7ed96..566ed2b 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
@@ -16,47 +16,104 @@
 package org.onosproject.inbandtelemetry.impl;
 
 
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.JsonNodeFactory;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 import org.easymock.Capture;
 import org.easymock.EasyMock;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
 import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
 import org.onosproject.TestApplicationId;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
+import org.onosproject.inbandtelemetry.api.IntIntent;
 import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.behaviour.inbandtelemetry.IntObjective;
+import org.onosproject.net.behaviour.inbandtelemetry.IntProgrammable;
 import org.onosproject.net.behaviour.inbandtelemetry.IntReportConfig;
 import org.onosproject.net.behaviour.inbandtelemetry.IntDeviceConfig;
-import org.onosproject.net.config.ConfigApplyDelegate;
 import org.onosproject.net.config.NetworkConfigEvent;
 import org.onosproject.net.config.NetworkConfigListener;
 import org.onosproject.net.config.NetworkConfigRegistry;
 import org.onosproject.net.config.NetworkConfigService;
 import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.host.HostService;
 import org.onosproject.store.service.StorageService;
 import org.onosproject.store.service.TestStorageService;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
 import static org.easymock.EasyMock.anyObject;
 import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.eq;
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.expectLastCall;
 import static org.easymock.EasyMock.newCapture;
 import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.easymock.EasyMock.verify;
 import static org.junit.Assert.assertEquals;
-import static org.onlab.junit.TestUtils.setField;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.net.behaviour.inbandtelemetry.IntProgrammable.IntFunctionality.POSTCARD;
+import static org.onosproject.net.behaviour.inbandtelemetry.IntProgrammable.IntFunctionality.SINK;
+import static org.onosproject.net.behaviour.inbandtelemetry.IntProgrammable.IntFunctionality.SOURCE;
+import static org.onosproject.net.behaviour.inbandtelemetry.IntProgrammable.IntFunctionality.TRANSIT;
 
 public class SimpleIntManagerTest {
     private static final String APP_NAME = "org.onosproject.inbandtelemetry";
     private static final ApplicationId APP_ID = new TestApplicationId(APP_NAME);
     private static final IpAddress COLLECTOR_IP = IpAddress.valueOf("10.0.0.1");
-    private static final TpPort COLLECTOR_PORT = TpPort.tpPort(5500);
-    private static final int MIN_FLOW_HOP_LATENCY_CHANGE_NS = 16;
+    private static final TpPort COLLECTOR_PORT = TpPort.tpPort(32766);
+    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 TrafficSelector FLOW_SELECTOR1 = DefaultTrafficSelector.builder()
+            .matchIPDst(IpPrefix.valueOf("192.168.10.0/24"))
+            .matchVlanId(VlanId.vlanId((short) 10))
+            .build();
+    private static final TrafficSelector FLOW_SELECTOR2 = DefaultTrafficSelector.builder()
+            .matchIPDst(IpPrefix.valueOf("192.168.20.0/24"))
+            .matchVlanId(VlanId.vlanId((short) 20))
+            .build();
+    private static final Device DEFAULT_DEVICE =
+            new DefaultDevice(null, DEVICE_ID, Device.Type.SWITCH, "", "", "", "", null);
+    private static final List<Port> DEVICE_PORTS = ImmutableList.of(
+            new DefaultPort(DEFAULT_DEVICE, PortNumber.portNumber(1), true),
+            new DefaultPort(DEFAULT_DEVICE, PortNumber.portNumber(2), true)
+    );
+    private static final Host HOST1 =
+            new DefaultHost(null, HostId.hostId("00:00:00:00:00:01/None"), null,
+                    VlanId.NONE, ImmutableSet.of(), ImmutableSet.of(), true);
+    private static final Host HOST2 =
+            new DefaultHost(null, HostId.hostId("00:00:00:00:00:02/None"), null,
+                    VlanId.NONE, ImmutableSet.of(), ImmutableSet.of(), true);
+    private static final Map<ConnectPoint, Host> HOSTS = ImmutableMap.of(
+            ConnectPoint.fromString("device:leaf1/1"), HOST1,
+            ConnectPoint.fromString("device:leaf1/2"), HOST2
+    );
 
     private SimpleIntManager manager;
     private StorageService storageService;
@@ -70,7 +127,7 @@
 
 
     @Before
-    public void setup() {
+    public void setup() throws IOException {
         storageService = new TestStorageService();
         mastershipService = createNiceMock(MastershipService.class);
         coreService = createNiceMock(CoreService.class);
@@ -81,21 +138,26 @@
         networkConfigService = createNiceMock(NetworkConfigService.class);
 
         manager = new SimpleIntManager();
-        setField(manager, "coreService", coreService);
-        setField(manager, "deviceService", deviceService);
-        setField(manager, "storageService", storageService);
-        setField(manager, "mastershipService", mastershipService);
-        setField(manager, "hostService", hostService);
-        setField(manager, "netcfgService", networkConfigService);
-        setField(manager, "netcfgRegistry", networkConfigRegistry);
+        manager.coreService = coreService;
+        manager.deviceService = deviceService;
+        manager.storageService = storageService;
+        manager.mastershipService = mastershipService;
+        manager.hostService = hostService;
+        manager.netcfgService = networkConfigService;
+        manager.netcfgRegistry = networkConfigRegistry;
 
-        expect(coreService.registerApplication(APP_NAME)).andReturn(APP_ID).once();
+        expect(coreService.registerApplication(APP_NAME))
+                .andReturn(APP_ID).anyTimes();
         networkConfigRegistry.registerConfigFactory(anyObject());
         expectLastCall().once();
 
         Capture<NetworkConfigListener> capture = newCapture();
         networkConfigService.addListener(EasyMock.capture(capture));
         expectLastCall().once();
+        IntReportConfig config = getIntReportConfig("/report-config.json");
+        expect(networkConfigService.getConfig(APP_ID, IntReportConfig.class))
+                .andReturn(config)
+                .anyTimes();
         replay(mastershipService, deviceService, coreService,
                 hostService, networkConfigRegistry, networkConfigService);
         manager.activate();
@@ -108,15 +170,8 @@
     }
 
     @Test
-    public void testPushIntAppConfig() {
-        IntReportConfig config = new IntReportConfig();
-        ObjectMapper mapper = new ObjectMapper();
-        ConfigApplyDelegate delegate = configApply -> { };
-        config.init(APP_ID, "report", JsonNodeFactory.instance.objectNode(), mapper, delegate);
-        config.setCollectorIp(COLLECTOR_IP)
-                .setCollectorPort(COLLECTOR_PORT)
-                .setMinFlowHopLatencyChangeNs(MIN_FLOW_HOP_LATENCY_CHANGE_NS);
-
+    public void testPushIntAppConfig() throws IOException {
+        IntReportConfig config = getIntReportConfig("/report-config.json");
         NetworkConfigEvent event =
                 new NetworkConfigEvent(NetworkConfigEvent.Type.CONFIG_ADDED, APP_ID,
                         config, null, IntReportConfig.class);
@@ -124,9 +179,199 @@
 
         // We expected that the manager will store the device config which
         // converted from the app config.
-        IntDeviceConfig deviceConfig = manager.getConfig();
-        assertEquals(COLLECTOR_IP, deviceConfig.collectorIp());
-        assertEquals(COLLECTOR_PORT, deviceConfig.collectorPort());
-        assertEquals(MIN_FLOW_HOP_LATENCY_CHANGE_NS, deviceConfig.minFlowHopLatencyChangeNs());
+        IntDeviceConfig expectedConfig = createIntDeviceConfig();
+        IntDeviceConfig actualConfig = manager.getConfig();
+        assertEquals(expectedConfig, actualConfig);
+    }
+
+    @Test
+    public void testConfigNonIntDevice() {
+        reset(deviceService);
+        Device device = getMockDevice(false, DEVICE_ID);
+        expect(deviceService.getDevice(DEVICE_ID))
+                .andReturn(device)
+                .anyTimes();
+        expect(deviceService.getDevices())
+                .andReturn(ImmutableSet.of(device))
+                .anyTimes();
+        replay(deviceService, device);
+        assertTrue(manager.configDevice(DEVICE_ID));
+        verify();
+    }
+
+    @Test
+    public void testConfigSourceDevice() {
+        reset(deviceService, hostService);
+        Device device = getMockDevice(true, DEVICE_ID);
+        IntProgrammable intProg = getMockIntProgrammable(true, false, false, false);
+        setUpDeviceTest(device, intProg, true, false);
+        IntObjective intObj = IntObjective.builder()
+                .withSelector(FLOW_SELECTOR2)
+                .build();
+        expect(intProg.addIntObjective(eq(intObj)))
+                .andReturn(true)
+                .once();
+        expect(intProg.setSourcePort(PortNumber.portNumber(1))).andReturn(true).once();
+        expect(intProg.setSourcePort(PortNumber.portNumber(2))).andReturn(true).once();
+        replay(deviceService, hostService, device, intProg);
+        installTestIntents();
+        assertTrue(manager.configDevice(DEVICE_ID));
+        verify(intProg);
+    }
+
+    @Test
+    public void testConfigTransitDevice() {
+        reset(deviceService, hostService);
+        Device device = getMockDevice(true, DEVICE_ID);
+        IntProgrammable intProg = getMockIntProgrammable(false, true, false, false);
+        setUpDeviceTest(device, intProg, false, false);
+        replay(deviceService, hostService, device, intProg);
+        installTestIntents();
+        assertTrue(manager.configDevice(DEVICE_ID));
+        verify(intProg);
+    }
+
+    @Test
+    public void testConfigSinkDevice() {
+        reset(deviceService, hostService);
+        Device device = getMockDevice(true, DEVICE_ID);
+        IntProgrammable intProg = getMockIntProgrammable(false, false, true, false);
+        setUpDeviceTest(device, intProg, true, true);
+        expect(intProg.setSinkPort(PortNumber.portNumber(1))).andReturn(true).once();
+        expect(intProg.setSinkPort(PortNumber.portNumber(2))).andReturn(true).once();
+        replay(deviceService, hostService, device, intProg);
+        installTestIntents();
+        assertTrue(manager.configDevice(DEVICE_ID));
+        verify(intProg);
+    }
+
+    @Test
+    public void testConfigPostcardOnlyDevice() {
+        reset(deviceService, hostService);
+        Device device = getMockDevice(true, DEVICE_ID);
+        IntProgrammable intProg = getMockIntProgrammable(false, false, false, true);
+        setUpDeviceTest(device, intProg, true, true);
+        IntObjective intObj = IntObjective.builder()
+                .withSelector(FLOW_SELECTOR1)
+                .build();
+        expect(intProg.addIntObjective(eq(intObj)))
+                .andReturn(true)
+                .once();
+        replay(deviceService, hostService, device, intProg);
+        installTestIntents();
+        assertTrue(manager.configDevice(DEVICE_ID));
+        verify(intProg);
+    }
+
+    /*
+     * Utilities
+     */
+    private void installTestIntents() {
+        // Pre-install an INT intent to the manager.
+        IntIntent.Builder intentBuilder = IntIntent.builder()
+                .withHeaderType(IntIntent.IntHeaderType.HOP_BY_HOP)
+                .withReportType(IntIntent.IntReportType.TRACKED_FLOW);
+
+        IntIntent postcardIntent = intentBuilder
+                .withTelemetryMode(IntIntent.TelemetryMode.POSTCARD)
+                .withSelector(FLOW_SELECTOR1)
+                .build();
+        IntIntent nonPoscardIntent = intentBuilder
+                .withTelemetryMode(IntIntent.TelemetryMode.INBAND_TELEMETRY)
+                .withSelector(FLOW_SELECTOR2)
+                .build();
+        manager.installIntIntent(nonPoscardIntent);
+        manager.installIntIntent(postcardIntent);
+    }
+
+    private void setUpDeviceTest(Device device, IntProgrammable intProg,
+                                 boolean hostConnected, boolean setupIntConfig) {
+        expect(device.as(IntProgrammable.class))
+                .andReturn(intProg)
+                .anyTimes();
+        expect(deviceService.getDevice(eq(DEVICE_ID)))
+                .andReturn(device)
+                .anyTimes();
+        expect(deviceService.getDevices())
+                .andReturn(ImmutableList.of(device))
+                .anyTimes();
+        if (setupIntConfig) {
+            IntDeviceConfig expectedConfig = createIntDeviceConfig();
+            expect(intProg.setupIntConfig(eq(expectedConfig)))
+                    .andReturn(true)
+                    .atLeastOnce();
+        }
+        expect(deviceService.getPorts(DEVICE_ID))
+                .andReturn(DEVICE_PORTS)
+                .anyTimes();
+
+        if (hostConnected) {
+            HOSTS.forEach((cp, host) -> {
+                expect(hostService.getConnectedHosts(eq(cp)))
+                        .andReturn(ImmutableSet.of(host))
+                        .anyTimes();
+            });
+            expect(hostService.getConnectedHosts(eq(DEVICE_ID)))
+                    .andReturn(Sets.newHashSet(HOSTS.values()));
+        } else {
+            expect(hostService.getConnectedHosts(eq(DEVICE_ID)))
+                    .andReturn(ImmutableSet.of())
+                    .anyTimes();
+        }
+    }
+
+    private IntReportConfig getIntReportConfig(String fileName) throws IOException {
+        IntReportConfig config = new IntReportConfig();
+        InputStream jsonStream = getClass().getResourceAsStream(fileName);
+        ObjectMapper mapper = new ObjectMapper();
+        JsonNode jsonNode = mapper.readTree(jsonStream);
+        config.init(APP_ID, INT_REPORT_CONFIG_KEY, jsonNode, mapper, c -> {
+        });
+        return config;
+    }
+
+    private Device getMockDevice(boolean supportInt, DeviceId deviceId) {
+        Device device = createNiceMock(Device.class);
+        expect(device.is(IntProgrammable.class))
+                .andReturn(supportInt)
+                .anyTimes();
+        expect(device.id())
+                .andReturn(deviceId)
+                .anyTimes();
+        return device;
+    }
+
+    private IntProgrammable getMockIntProgrammable(boolean supportSource, boolean supportTransit, boolean supportSink,
+                                                   boolean supportPostcard) {
+        IntProgrammable intProg = createNiceMock(IntProgrammable.class);
+        if (supportSource) {
+            expect(intProg.supportsFunctionality(SOURCE))
+                    .andReturn(true).anyTimes();
+        }
+        if (supportTransit) {
+            expect(intProg.supportsFunctionality(TRANSIT))
+                    .andReturn(true).anyTimes();
+        }
+        if (supportSink) {
+            expect(intProg.supportsFunctionality(SINK))
+                    .andReturn(true).anyTimes();
+        }
+        if (supportPostcard) {
+            expect(intProg.supportsFunctionality(POSTCARD))
+                    .andReturn(true).anyTimes();
+        }
+        expect(intProg.init())
+                .andReturn(true)
+                .anyTimes();
+        return intProg;
+    }
+
+    private IntDeviceConfig createIntDeviceConfig() {
+        return IntDeviceConfig.builder()
+                .withMinFlowHopLatencyChangeNs(MIN_FLOW_HOP_LATENCY_CHANGE_NS)
+                .withCollectorPort(COLLECTOR_PORT)
+                .withCollectorIp(COLLECTOR_IP)
+                .enabled(true)
+                .build();
     }
 }
diff --git a/apps/inbandtelemetry/impl/src/test/resources/report-config.json b/apps/inbandtelemetry/impl/src/test/resources/report-config.json
new file mode 100644
index 0000000..c0c1ca2
--- /dev/null
+++ b/apps/inbandtelemetry/impl/src/test/resources/report-config.json
@@ -0,0 +1,5 @@
+{
+  "collectorIp": "10.0.0.1",
+  "collectorPort": 32766,
+  "minFlowHopLatencyChangeNs": 32
+}
\ No newline at end of file
diff --git a/apps/inbandtelemetry/int-gui2-lib/projects/int-gui2-lib/src/lib/intapp/intapp.component.html b/apps/inbandtelemetry/int-gui2-lib/projects/int-gui2-lib/src/lib/intapp/intapp.component.html
index 0196aef..2d6b59a 100644
--- a/apps/inbandtelemetry/int-gui2-lib/projects/int-gui2-lib/src/lib/intapp/intapp.component.html
+++ b/apps/inbandtelemetry/int-gui2-lib/projects/int-gui2-lib/src/lib/intapp/intapp.component.html
@@ -60,6 +60,13 @@
                                 <option value="UDP">UDP</option>
                             </select>
                         </label>
+                        <label>
+                            Telemetry Mode
+                            <select name="telemetryMode" formControlName="telemetryMode">
+                                <option selected value="POSTCARD">Postcard (INT-XD)</option>
+                                <option value="INBAND_TELEMETRY">Embedded Data (INT-MD)</option>
+                            </select>
+                        </label>
                         <div *ngIf="formSend.controls.ip4DstPrefix.errors" class="invalid-feedback">
                             <div class="error-text" *ngIf="formSend.controls.ip4DstPrefix.errors.required"> Destination
                                 IP is required
@@ -140,6 +147,7 @@
                         <td colId="dstPort" sortable>Dst Port</td>
                         <td colId="protocol" sortable>Protocol</td>
                         <td colId="metadata" sortable>Metadata</td>
+                        <td colId="telemetryMode" sortable>Telemetry Mode</td>
                     </tr>
                 </table>
             </div>
@@ -166,6 +174,7 @@
                         <td>{{ row.dstPort }}</td>
                         <td>{{ row.protocol }}</td>
                         <td>{{ row.metadata }}</td>
+                        <td>{{ row.telemetryMode }}</td>
                     </tr>
                 </table>
             </div>
diff --git a/apps/inbandtelemetry/int-gui2-lib/projects/int-gui2-lib/src/lib/intapp/intapp.component.ts b/apps/inbandtelemetry/int-gui2-lib/projects/int-gui2-lib/src/lib/intapp/intapp.component.ts
index 185ef6e..0cff1e3 100644
--- a/apps/inbandtelemetry/int-gui2-lib/projects/int-gui2-lib/src/lib/intapp/intapp.component.ts
+++ b/apps/inbandtelemetry/int-gui2-lib/projects/int-gui2-lib/src/lib/intapp/intapp.component.ts
@@ -82,6 +82,7 @@
             l4SrcPort: new FormControl(null, [ Validators.pattern(regSendPort)]),
             l4DstPort: new FormControl(null, [ Validators.pattern(regSendPort)]),
             protocol: new FormControl(),
+            telemetryMode: new FormControl("POSTCARD")
         });
         this.log.debug('IntAppComponent initialized');
     }
@@ -120,7 +121,8 @@
             "l4SrcPort": this.formSend.value.l4SrcPort,
             "l4DstPort": this.formSend.value.l4DstPort,
             "protocol": this.formSend.value.protocol,
-            "metadata": this.formSend.value.name
+            "metadata": this.formSend.value.name,
+            "telemetryMode": this.formSend.value.telemetryMode
         };
         this.wss.sendEvent(intIntentAddReq, intentObjectNode);
     }
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/inbandtelemetry/IntDeviceConfig.java b/core/api/src/main/java/org/onosproject/net/behaviour/inbandtelemetry/IntDeviceConfig.java
index a21a2ae..92e5fa2 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/inbandtelemetry/IntDeviceConfig.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/inbandtelemetry/IntDeviceConfig.java
@@ -16,6 +16,7 @@
 package org.onosproject.net.behaviour.inbandtelemetry;
 
 import com.google.common.annotations.Beta;
+import com.google.common.base.Objects;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.TpPort;
@@ -36,14 +37,14 @@
          * Embeds telemetry metadata according to the INT specification.
          *
          * @see <a href="https://github.com/p4lang/p4-applications/blob/master/docs/INT.pdf">
-         *     INT sepcification</a>
+         * INT sepcification</a>
          */
         INT,
         /**
          * Embeds telemetry metadata according to the OAM specification.
          *
          * @see <a href="https://tools.ietf.org/html/draft-ietf-ippm-ioam-data">
-         *     Data fields for In-situ OAM</a>
+         * Data fields for In-situ OAM</a>
          */
         IOAM
     }
@@ -166,6 +167,31 @@
         return new Builder();
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        IntDeviceConfig that = (IntDeviceConfig) o;
+        return enabled == that.enabled &&
+                hopLatencySensitivity == that.hopLatencySensitivity &&
+                Objects.equal(collectorIp, that.collectorIp) &&
+                Objects.equal(collectorPort, that.collectorPort) &&
+                Objects.equal(collectorNextHopMac, that.collectorNextHopMac) &&
+                Objects.equal(sinkIp, that.sinkIp) &&
+                Objects.equal(sinkMac, that.sinkMac) &&
+                spec == that.spec;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(collectorIp, collectorPort, collectorNextHopMac,
+                sinkIp, sinkMac, spec, enabled, hopLatencySensitivity);
+    }
+
     /**
      * An IntConfig object builder.
      */
@@ -280,7 +306,7 @@
             checkArgument(minFlowHopLatencyChangeNs >= 0, "Hop latency sensitivity must be positive or zero");
 
             return new IntDeviceConfig(collectorIp, collectorPort, collectorNextHopMac,
-                                 sinkIp, sinkMac, spec, enabled, minFlowHopLatencyChangeNs);
+                    sinkIp, sinkMac, spec, enabled, minFlowHopLatencyChangeNs);
         }
     }
 }
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 8f036fe..4d94e62 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
@@ -15,6 +15,7 @@
  */
 package org.onosproject.net.behaviour.inbandtelemetry;
 
+import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableSet;
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.TrafficSelector;
@@ -75,6 +76,24 @@
         return new Builder();
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        IntObjective that = (IntObjective) o;
+        return Objects.equal(selector, that.selector) &&
+                Objects.equal(metadataTypes, that.metadataTypes);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(selector, metadataTypes);
+    }
+
     /**
      * An IntObjective builder.
      */
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/inbandtelemetry/IntProgrammable.java b/core/api/src/main/java/org/onosproject/net/behaviour/inbandtelemetry/IntProgrammable.java
index 5560e8c..8cbcae0 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/inbandtelemetry/IntProgrammable.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/inbandtelemetry/IntProgrammable.java
@@ -41,7 +41,11 @@
         /**
          * Transit functionality.
          */
-        TRANSIT
+        TRANSIT,
+        /**
+         * Postcard functionality.
+         */
+        POSTCARD
     }
 
     /**