Initial implementation of IntService and sample application

This implementation has been used for P4Workshop demo (ONOS INT Support).

Change-Id: I2ff94f8a79f6d5a328b94f7ed178e575b81c3fe9
diff --git a/apps/inbandtelemetry/app/BUCK b/apps/inbandtelemetry/app/BUCK
new file mode 100644
index 0000000..a5d7b9e
--- /dev/null
+++ b/apps/inbandtelemetry/app/BUCK
@@ -0,0 +1,32 @@
+BUNDLES = [
+    '//apps/inbandtelemetry/api:onos-apps-inbandtelemetry-api',
+    '//apps/inbandtelemetry/impl:onos-apps-inbandtelemetry-impl',
+    '//apps/inbandtelemetry/app:onos-apps-inbandtelemetry-app',
+]
+
+COMPILE_DEPS = [
+    '//lib:CORE_DEPS',
+    '//lib:KRYO',
+    '//lib:JACKSON',
+    '//core/store/serializers:onos-core-serializers',
+    '//apps/inbandtelemetry/api:onos-apps-inbandtelemetry-api',
+]
+
+TEST_DEPS = [
+    '//lib:TEST_REST',
+]
+
+osgi_jar_with_tests(
+    deps = COMPILE_DEPS,
+    test_deps = TEST_DEPS,
+)
+
+onos_app (
+    app_name = 'org.onosproject.inbandtelemetry.app',
+    title = 'P4 In-band Network Telemetry Sample Application',
+    category = 'Monitoring',
+    url = 'http://onosproject.org',
+    description = 'Provides managements of INT-capable devices. Specifies flows to enable INT and' +
+    'types of metadata to collect. Sets up INT-related information.',
+    included_bundles = BUNDLES,
+)
\ No newline at end of file
diff --git a/apps/inbandtelemetry/app/pom.xml b/apps/inbandtelemetry/app/pom.xml
new file mode 100644
index 0000000..e397330
--- /dev/null
+++ b/apps/inbandtelemetry/app/pom.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2016-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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>onos-apps-inbandtelemetry</artifactId>
+        <groupId>org.onosproject</groupId>
+        <version>1.14.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>onos-apps-inbandtelemetry-app</artifactId>
+    <packaging>bundle</packaging>
+
+    <url>http://onosproject.org</url>
+
+    <description>P4 INT Management Application</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-apps-inbandtelemetry-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-core-serializers</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/IntControl.java b/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/IntControl.java
new file mode 100644
index 0000000..efd3590
--- /dev/null
+++ b/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/IntControl.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2015-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.inbandtelemetry.app;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.core.CoreService;
+import org.onosproject.inbandtelemetry.api.IntService;
+import org.onosproject.net.host.HostService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(immediate = true)
+public class IntControl {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+//    private ApplicationId appId;
+//    private static final int collectorPort = 1234;
+//    private static final IpAddress collectorIp = IpAddress.valueOf("10.0.0.3");
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntService intService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Activate
+    protected void activate() {
+        coreService.registerApplication("org.onosproject.inbandtelemetry.app");
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        log.info("Stopped");
+    }
+}
diff --git a/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/package-info.java b/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/package-info.java
new file mode 100644
index 0000000..859bdf7
--- /dev/null
+++ b/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015-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.
+ */
+
+/**
+ * IntService sample application.
+ */
+package org.onosproject.inbandtelemetry.app;
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
new file mode 100644
index 0000000..fafd4b4
--- /dev/null
+++ b/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/ui/IntAppTableMessageHandler.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2015-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.inbandtelemetry.app.ui;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.inbandtelemetry.api.IntIntent;
+import org.onosproject.inbandtelemetry.api.IntIntentId;
+import org.onosproject.inbandtelemetry.api.IntService;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.IPCriterion;
+import org.onosproject.net.flow.criteria.TcpPortCriterion;
+import org.onosproject.net.flow.criteria.UdpPortCriterion;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.table.TableModel;
+import org.onosproject.ui.table.TableRequestHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Message handler for installed INT intents table in the application UI.
+ */
+public class IntAppTableMessageHandler extends UiMessageHandler {
+    private static final String INT_APP_INT_INTENT = "intAppIntIntent";
+    private static final String INT_APP_INT_INTENT_DATA_REQUEST = INT_APP_INT_INTENT + "DataRequest";
+    private static final String INT_APP_INT_INTENT_DATA_RESPONSE = INT_APP_INT_INTENT + "DataResponse";
+
+    private static final String INT_APP_DEL_INT_INTENT_REQ = "intAppDelIntIntentRequest";
+
+    private static final String NO_ROWS_MESSAGE = "No IntIntent found";
+
+    private static final String ID = "id";
+    private static final String SRC_ADDR = "srcAddr";
+    private static final String DST_ADDR = "dstAddr";
+    private static final String SRC_PORT = "srcPort";
+    private static final String DST_PORT = "dstPort";
+    private static final String PROTOCOL = "protocol";
+    private static final String METADATA = "metadata";
+
+    private static final String[] COLUMN_IDS = {ID, SRC_ADDR, DST_ADDR, SRC_PORT, DST_PORT, PROTOCOL, METADATA};
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    protected IntService intService;
+
+    @Override
+    protected Collection<RequestHandler> createRequestHandlers() {
+        return ImmutableSet.of(
+                new IntAppIntIntentRequestHandler(),
+                new IntAppDelIntIntentRequestHandler()
+        );
+    }
+
+    // handler for table requests
+    private final class IntAppIntIntentRequestHandler extends TableRequestHandler {
+
+        private IntAppIntIntentRequestHandler() {
+            super(INT_APP_INT_INTENT_DATA_REQUEST, INT_APP_INT_INTENT_DATA_RESPONSE, INT_APP_INT_INTENT);
+        }
+
+        @Override
+        protected String[] getColumnIds() {
+            return COLUMN_IDS;
+        }
+
+        @Override
+        protected String noRowsMessage(ObjectNode payload) {
+            return NO_ROWS_MESSAGE;
+        }
+
+        private Map<IntIntentId, IntIntent> getAllIntIntents() {
+            intService = get(IntService.class);
+            return intService.getIntIntents();
+        }
+
+        @Override
+        protected void populateTable(TableModel tm, ObjectNode payload) {
+            Map<IntIntentId, IntIntent> intentMap = getAllIntIntents();
+            intentMap.entrySet().forEach(entry ->
+                                                 populateRow(tm.addRow(), entry.getKey(), entry.getValue()));
+        }
+
+        private void populateRow(TableModel.Row row, IntIntentId intentId, IntIntent intent) {
+            IPCriterion ip4Src = (IPCriterion) intent.selector().getCriterion(Criterion.Type.IPV4_SRC);
+            IPCriterion ip4Dst = (IPCriterion) intent.selector().getCriterion(Criterion.Type.IPV4_DST);
+            TcpPortCriterion tcpSrcPort = (TcpPortCriterion) intent.selector().getCriterion(Criterion.Type.TCP_SRC);
+            TcpPortCriterion tcpDstPort = (TcpPortCriterion) intent.selector().getCriterion(Criterion.Type.TCP_DST);
+            UdpPortCriterion udpSrcPort = (UdpPortCriterion) intent.selector().getCriterion(Criterion.Type.UDP_SRC);
+            UdpPortCriterion udpDstPort = (UdpPortCriterion) intent.selector().getCriterion(Criterion.Type.UDP_DST);
+            Set<IntIntent.IntMetadataType> metadataTypes = intent.metadataTypes();
+            row.cell(ID, intentId.toString())
+                    .cell(SRC_ADDR, ip4Src == null ? "N/A" : ip4Src.ip().toString())
+                    .cell(DST_ADDR, ip4Dst == null ? "N/A" : ip4Dst.ip().toString());
+            if (tcpSrcPort != null || tcpDstPort != null) {
+                row.cell(PROTOCOL, "TCP")
+                        .cell(SRC_PORT, tcpSrcPort == null ? "N/A" : tcpSrcPort.tcpPort().toString())
+                        .cell(DST_PORT, tcpDstPort == null ? "N/A" : tcpDstPort.tcpPort().toString());
+            } else if (udpSrcPort != null || udpDstPort != null) {
+                row.cell(PROTOCOL, "UDP")
+                        .cell(SRC_PORT, udpSrcPort == null ? "N/A" : udpSrcPort.udpPort().toString())
+                        .cell(DST_PORT, udpDstPort == null ? "N/A" : udpDstPort.udpPort().toString());
+            } else {
+                row.cell(PROTOCOL, "N/A")
+                        .cell(SRC_PORT, "N/A")
+                        .cell(DST_PORT, "N/A");
+            }
+            String metaStr = "";
+            for (IntIntent.IntMetadataType metadataType : metadataTypes) {
+                metaStr += metadataType.toString();
+                metaStr += ", ";
+            }
+            row.cell(METADATA, metaStr);
+        }
+    }
+
+    private final class IntAppDelIntIntentRequestHandler extends RequestHandler {
+
+        private IntAppDelIntIntentRequestHandler() {
+            super(INT_APP_DEL_INT_INTENT_REQ);
+        }
+
+        @Override
+        public void process(ObjectNode payload) {
+            intService = get(IntService.class);
+            if (payload.get(ID) != null) {
+                intService.removeIntIntent(IntIntentId.valueOf(payload.get(ID).asLong()));
+            }
+        }
+    }
+}
diff --git a/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/ui/IntAppUiComponent.java b/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/ui/IntAppUiComponent.java
new file mode 100644
index 0000000..94c823e
--- /dev/null
+++ b/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/ui/IntAppUiComponent.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2015-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.inbandtelemetry.app.ui;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.ui.UiExtension;
+import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiMessageHandlerFactory;
+import org.onosproject.ui.UiView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+/**
+ * Skeletal ONOS UI Custom-View application component.
+ */
+@Component(immediate = true)
+public class IntAppUiComponent {
+
+    private static final String VIEW_ID = "intApp";
+    private static final String VIEW_TEXT = "In-band Telemetry Control";
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected UiExtensionService uiExtensionService;
+
+    // List of application views
+    private final List<UiView> uiViews = ImmutableList.of(
+            new UiView(UiView.Category.OTHER, VIEW_ID, VIEW_TEXT)
+    );
+
+    // Factory for UI message handlers
+    private final UiMessageHandlerFactory messageHandlerFactory =
+            () -> ImmutableList.of(
+                    new IntAppUiMessageHandler(),
+                    new IntAppTableMessageHandler()
+            );
+
+    // Application UI extension
+    protected UiExtension extension =
+            new UiExtension.Builder(getClass().getClassLoader(), uiViews)
+                    .resourcePath(VIEW_ID)
+                    .messageHandlerFactory(messageHandlerFactory)
+                    .build();
+
+    @Activate
+    protected void activate() {
+        uiExtensionService.register(extension);
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        uiExtensionService.unregister(extension);
+        log.info("Stopped");
+    }
+}
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
new file mode 100644
index 0000000..5a81c60
--- /dev/null
+++ b/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/ui/IntAppUiMessageHandler.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2015-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.inbandtelemetry.app.ui;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.collect.ImmutableSet;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.TpPort;
+import org.onosproject.inbandtelemetry.api.IntIntent;
+import org.onosproject.inbandtelemetry.api.IntIntentId;
+import org.onosproject.inbandtelemetry.api.IntService;
+import org.onosproject.inbandtelemetry.api.IntConfig;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiMessageHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+
+public class IntAppUiMessageHandler extends UiMessageHandler {
+
+    private static final String INT_INTENT_ADD_REQUEST = "intIntentAddRequest";
+    private static final String INT_INTENT_DEL_REQUEST = "intIntentDelRequest";
+    private static final String INT_CONFIG_ADD_REQUEST = "intConfigAddRequest";
+//    private static final String INT_CONFIG_DEL_REQUEST = "intConfigDelRequest";
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private IntService intService;
+
+    @Override
+    protected Collection<RequestHandler> createRequestHandlers() {
+        return ImmutableSet.of(
+                new IntIntentAddRequestHandler(),
+                new IntIntentDelRequestHandler(),
+                new IntConfigAddRequestHandler()
+//                new intConfigDelRequestHandler()
+        );
+    }
+
+    private final class IntConfigAddRequestHandler extends RequestHandler {
+        private IntConfigAddRequestHandler() {
+            super(INT_CONFIG_ADD_REQUEST);
+        }
+
+        @Override
+        public void process(ObjectNode payload) {
+            log.info("intConfigAddRequest: {}", payload);
+
+            intService = get(IntService.class);
+            IntConfig.Builder builder = IntConfig.builder();
+
+            if (payload.get("collectorIp") != null) {
+                builder.withCollectorIp(IpAddress.valueOf(payload.get("collectorIp").asText()));
+            } else {
+                builder.withCollectorIp(IpAddress.valueOf("127.0.0.1"));
+            }
+
+            if (payload.get("collectorPort") != null) {
+                builder.withCollectorPort(TpPort.tpPort(
+                        payload.get("collectorPort").asInt()));
+            } else {
+                builder.withCollectorPort(TpPort.tpPort(1234));
+            }
+
+            builder.enabled(true)
+                    .withSinkIp(IpAddress.valueOf("10.192.19.180"))
+                    .withSinkMac(MacAddress.NONE)
+                    .withCollectorNextHopMac(MacAddress.BROADCAST);
+
+            intService.setConfig(builder.build());
+        }
+    }
+
+    private final class IntIntentDelRequestHandler extends RequestHandler {
+        private IntIntentDelRequestHandler() {
+            super(INT_INTENT_DEL_REQUEST);
+        }
+
+        @Override
+        public void process(ObjectNode payload) {
+            log.info("intIntentDelRequest: {}", payload);
+
+            intService = get(IntService.class);
+
+            if (payload.get("intentId") != null) {
+                intService.removeIntIntent(IntIntentId.valueOf(payload.get("intentId").asLong()));
+            }
+        }
+    }
+
+    private final class IntIntentAddRequestHandler extends RequestHandler {
+        private IntIntentAddRequestHandler() {
+            super(INT_INTENT_ADD_REQUEST);
+        }
+
+        @Override
+        public void process(ObjectNode payload) {
+            log.info("intIntentAddRequest: {}", payload);
+
+            intService = get(IntService.class);
+
+            TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+            IntIntent.Builder builder = IntIntent.builder();
+
+            if (payload.get("ip4SrcPrefix") != null) {
+                sBuilder.matchIPSrc(parseIp4Prefix(payload.get("ip4SrcPrefix").asText()));
+            }
+
+            if (payload.get("ip4DstPrefix") != null) {
+                sBuilder.matchIPDst(parseIp4Prefix(payload.get("ip4DstPrefix").asText()));
+            }
+
+            if (payload.get("l4SrcPort") != null) {
+                if (payload.get("protocol") != null && payload.get("protocol").asText().equalsIgnoreCase("TCP")) {
+                    sBuilder.matchTcpSrc(TpPort.tpPort(payload.get("l4SrcPort").asInt()));
+                } else {
+                    sBuilder.matchUdpSrc(TpPort.tpPort(payload.get("l4SrcPort").asInt()));
+                }
+            }
+
+            if (payload.get("l4DstPort") != null) {
+                if (payload.get("protocol") != null && payload.get("protocol").asText().equalsIgnoreCase("TCP")) {
+                    sBuilder.matchTcpDst(TpPort.tpPort(payload.get("l4DstPort").asInt()));
+                } else {
+                    sBuilder.matchUdpDst(TpPort.tpPort(payload.get("l4DstPort").asInt()));
+                }
+            }
+
+            if (payload.get("metadata") != null) {
+                JsonNode meta = payload.get("metadata");
+                if (meta.isArray()) {
+                    for (final JsonNode json : meta) {
+                        switch (json.asText()) {
+                            case "SWITCH_ID":
+                                builder.withMetadataType(IntIntent.IntMetadataType.SWITCH_ID);
+                                break;
+                            case "PORT_ID":
+                                builder.withMetadataType(IntIntent.IntMetadataType.L1_PORT_ID);
+                                break;
+                            case "HOP_LATENCY":
+                                builder.withMetadataType(IntIntent.IntMetadataType.HOP_LATENCY);
+                                break;
+                            case "QUEUE_OCCUPANCY":
+                                builder.withMetadataType(IntIntent.IntMetadataType.QUEUE_OCCUPANCY);
+                                break;
+                            case "INGRESS_TIMESTAMP":
+                                builder.withMetadataType(IntIntent.IntMetadataType.INGRESS_TIMESTAMP);
+                                break;
+                            case "EGRESS_TIMESTAMP":
+                                builder.withMetadataType(IntIntent.IntMetadataType.EGRESS_TIMESTAMP);
+                                break;
+//                            case "QUEUE_CONGESTION":
+//                                builder.withMetadataType(IntIntent.IntMetadataType.QUEUE_CONGESTION);
+//                                break;
+                            case "EGRESS_TX_UTIL":
+                                builder.withMetadataType(IntIntent.IntMetadataType.EGRESS_TX_UTIL);
+                                break;
+                            default:
+                                break;
+                        }
+                    }
+                }
+            }
+
+            builder.withSelector(sBuilder.build())
+                    .withHeaderType(IntIntent.IntHeaderType.HOP_BY_HOP)
+                    .withReportType(IntIntent.IntReportType.TRACKED_FLOW)
+                    .withTelemetryMode(IntIntent.TelemetryMode.INBAND_TELEMETRY);
+            intService.installIntIntent(builder.build());
+        }
+
+        private Ip4Prefix parseIp4Prefix(String prefixString) {
+            if (prefixString == null) {
+                return null;
+            }
+            String[] splitString = prefixString.split("/");
+            Ip4Address ip4Address = Ip4Address.valueOf(splitString[0]);
+            int mask = splitString.length > 1 ? Integer.parseInt(splitString[1]) : 32;
+            return Ip4Prefix.valueOf(ip4Address, mask);
+        }
+    }
+}
diff --git a/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/ui/package-info.java b/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/ui/package-info.java
new file mode 100644
index 0000000..d3d4849
--- /dev/null
+++ b/apps/inbandtelemetry/app/src/main/java/org/onosproject/inbandtelemetry/app/ui/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015-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.
+ */
+
+/**
+ * Web UI component for IntService sample application.
+ */
+package org.onosproject.inbandtelemetry.app.ui;
diff --git a/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.css b/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.css
new file mode 100644
index 0000000..1cfd447
--- /dev/null
+++ b/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.css
@@ -0,0 +1,108 @@
+/* css for sample app custom view */
+
+#ov-int-app-main {
+    padding: 20px;
+}
+.light #ov-int-app-main {
+    color: navy;
+}
+.dark #ov-int-app-main {
+    color: #88f;
+}
+
+#ov-int-app-main .button-panel {
+    margin: 10px;
+    width: 200px;
+}
+
+.light #ov-int-app-main .button-panel {
+    background-color: #ccf;
+}
+
+.dark #ov-int-app-main .button-panel {
+    background-color: #444;
+}
+
+#ov-int-app-main .int-app-button {
+    cursor: pointer;
+    padding: 4px;
+    text-align: center;
+}
+
+
+.light #ov-int-app-main .int-app-button {
+    color: white;
+    background-color: #99d;
+}
+.dark #ov-int-app-main .int-app-button {
+    color: black;
+    background-color: #aaa;
+}
+
+#ov-int-app-main .config-button-panel {
+    margin: 10px;
+    width: 200px;
+}
+
+.light #ov-int-app-main .config-button-panel {
+    background-color: #ccf;
+}
+
+.dark #ov-int-app-main .config-button-panel {
+    background-color: #444;
+}
+
+#ov-int-app-main .int-app-config-button {
+    cursor: pointer;
+    padding: 4px;
+    text-align: center;
+}
+
+
+.light #ov-int-app-main .int-app-config-button {
+    color: white;
+    background-color: #99d;
+}
+.dark #ov-int-app-main .int-app-config-button {
+    color: black;
+    background-color: #aaa;
+}
+/*---------------------------------------------------------------------------*/
+#ov-int-app-main h2 {
+    display: inline-block;
+}
+/* #ov-int-app-main .table-body{
+    display: inline-block;
+    overflow-y: scroll;
+    max-height:100px;
+} */
+
+/* Panel Styling */
+#ov-int-app-main-item-details-panel.floatpanel {
+    position: absolute;
+    top: 115px;
+}
+
+.light #ov-int-app-main-item-details-panel.floatpanel {
+    background-color: rgb(229, 234, 237);
+}
+.dark #ov-int-app-main-item-details-panel.floatpanel {
+    background-color: #3A4042;
+}
+
+#ov-int-app-main-item-details-panel h3 {
+    margin: 0;
+    font-size: large;
+}
+
+#ov-int-app-main-item-details-panel h4 {
+    margin: 0;
+}
+
+#ov-int-app-main-item-details-panel td {
+    padding: 5px;
+}
+#ov-int-app-main-item-details-panel td.label {
+    font-style: italic;
+    opacity: 0.8;
+}
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
new file mode 100644
index 0000000..42717e0
--- /dev/null
+++ b/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.html
@@ -0,0 +1,110 @@
+<!-- partial HTML -->
+<div id="ov-int-app-main">
+    <div class="config-panel">
+        <div>
+            Collector IP
+            <input type="text" required pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}$" ng-model="collectorIp">
+
+            Collector Port
+            <input type="text" required pattern="^[0-9]{0,5}$" ng-model="collectorPort">
+        </div>
+    </div>
+    <div class="config-button-panel">
+        <div class = "int-app-config-button" ng-click="sendIntConfigString()">
+            Deploy
+        </div>
+    </div>
+    <hr>
+    <div class="input-panel">
+        <div>
+            Src Address
+            <input type="text" required pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?$" ng-model="ip4SrcPrefix">
+
+            Dst Address
+            <input type="text" required pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?$" ng-model="ip4DstPrefix">
+
+            Src Port
+            <input type="text" required pattern="^[0-9]{0,5}$" ng-model="l4SrcPort">
+
+            Dst Port
+            <input type="text" required pattern="^[0-9]{0,5}$" ng-model="l4DstPort">
+
+            Protocol
+            <select name="protocol" ng-model="protocol">
+                <option selected disabled hidden style="display: none" value=''></option>
+                <option value="TCP">TCP</option>
+                <option value="UDP">UDP</option>
+            </select>
+        </div>
+        <div>
+            <input type="checkbox" ng-model= "metaSwId">Switch Id
+            <input type="checkbox" ng-model= "metaPortId">Port Id
+            <input type="checkbox" ng-model= "metaHopLatency">Hop Latency
+            <input type="checkbox" ng-model= "metaQOccupancy">Queue Occupancy
+            <input type="checkbox" ng-model= "metaIngressTstamp">Ingress Timestamp
+            <input type="checkbox" ng-model= "metaEgressTstamp">Egress Timestamp
+            <!--<input type="checkbox" ng-model= "metaQCongestion">Queue Congestion Status-->
+            <input type="checkbox" ng-model= "metaEgressTx">Egress Port Tx Utilization
+        </div>
+    </div>
+
+    <div class="button-panel">
+        <div class = "int-app-button" ng-click="sendIntIntentString()">
+            Deploy
+        </div>
+    </div>
+    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+    <div class='int-app-main-intents'>
+        <div class="tabular-header">
+            <h2>Installed INT Intents ({{tableData.length}} total)</h2>
+            <div class="ctrl-btns">
+                <div class="refresh" ng-class="{active: autoRefresh}"
+                     icon icon-id="refresh" icon-size="36"
+                     tooltip tt-msg="autoRefreshTip"
+                     ng-click="toggleRefresh()">
+                </div>
+
+                <!-- tooltip tt-msg="uninstallTip" -->
+                <div icon icon-size="42" icon-id="garbage"
+                     ng-click="delIntIntent()"
+                     ng-class="{active: ctrlBtnState.selection}">
+                </div>
+            </div>
+        </div>
+
+        <div class="summary-list" onos-table-resize>
+
+            <div class="table-header" onos-sortable-header>
+                <table>
+                    <tr>
+                        <td colId="id" sortable>ID </td>
+                        <td colId="srcAddr" sortable>Src Address </td>
+                        <td colId="dstAddr" sortable>Dst Address </td>
+                        <td colId="srcPort" sortable>Src Port </td>
+                        <td colId="dstPort" sortable>Dst Port </td>
+                        <td colId="protocol" sortable>Protocol </td>
+                        <td colId="metadata" sortable>Metadata </td>
+                    </tr>
+                </table>
+            </div>
+
+            <div class="table-body">
+                <table>
+                    <tr ng-repeat="item in tableData track by $index"
+                        ng-click="selectCallback($event, item)"
+                        ng-class="{selected: item.id === selId}">
+                        <td>{{item.id}}</td>
+                        <td>{{item.srcAddr}}</td>
+                        <td>{{item.dstAddr}}</td>
+                        <td>{{item.srcPort}}</td>
+                        <td>{{item.dstPort}}</td>
+                        <td>{{item.protocol}}</td>
+                        <td>{{item.metadata}}</td>
+                    </tr>
+                </table>
+            </div>
+
+        </div>
+    </div>
+</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
new file mode 100644
index 0000000..6b2b6cc
--- /dev/null
+++ b/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.js
@@ -0,0 +1,194 @@
+(function() {
+    'use strict';
+
+    // injected refs
+    var $log, $scope, $interval, $timeout, fs, wss, ks, ls;
+
+    // constants
+    var intIntentAddReq = 'intIntentAddRequest';
+    var intIntentDelReq = 'intIntentDelRequest';
+    var intConfigAddReq = 'intConfigAddRequest';
+
+    var refreshInterval = 1000;
+
+    var propOrder = ['id', 'srcAddr', 'dstAddr', 'srcPort', 'dstPort', 'insMask'];
+    var friendlyProps = ['IntIntent ID', 'Src Address', 'Dst Address', 'Src Port', 'Dst Port', 'Ins Mask'];
+
+    function sendIntConfigString() {
+        var configObjectNode = {
+            "collectorIp": $scope.collectorIp,
+            "collectorPort": $scope.collectorPort
+        };
+        wss.sendEvent(intConfigAddReq, configObjectNode);
+    }
+
+    function sendIntIntentString() {
+        var inst = [];
+        if ($scope.metaSwId) inst.push("SWITCH_ID");
+        if ($scope.metaPortId) inst.push("PORT_ID");
+        if ($scope.metaHopLatency) inst.push("HOP_LATENCY");
+        if ($scope.metaQOccupancy) inst.push("QUEUE_OCCUPANCY");
+        if ($scope.metaIngressTstamp) inst.push("INGRESS_TIMESTAMP");
+        if ($scope.metaEgressTstamp) inst.push("EGRESS_TIMESTAMP");
+        if ($scope.metaQCongestion) inst.push("QUEUE_CONGESTION");
+        if ($scope.metaEgressTx) inst.push("EGRESS_TX_UTIL");
+
+        var intentObjectNode = {
+            "ip4SrcPrefix": $scope.ip4SrcPrefix,
+            "ip4DstPrefix": $scope.ip4DstPrefix,
+            "l4SrcPort": $scope.l4SrcPort,
+            "l4DstPort": $scope.l4DstPort,
+            "protocol": $scope.protocol,
+            "metadata": inst
+        };
+        wss.sendEvent(intIntentAddReq, intentObjectNode);
+    }
+
+    function delIntIntent() {
+        if ($scope.selId) {
+            wss.sendEvent(intIntentDelReq, {
+                "intentId": $scope.selId
+            });
+        }
+    }
+
+    function intIntentBuildTable(o) {
+        var handlers = {},
+            root = o.tag,
+            req = o.tag + 'DataRequest',
+            resp = o.tag + 'DataResponse',
+            onSel = fs.isF(o.selCb),
+            onResp = fs.isF(o.respCb),
+            idKey = o.idKey || 'id',
+            oldTableData = [],
+            refreshPromise;
+
+        o.scope.tableData = [];
+        o.scope.changedData = [];
+        o.scope.sortParams = o.sortParams || {};
+        o.scope.autoRefresh = true;
+        o.scope.autoRefreshTip = 'Toggle auto refresh';
+
+        // === websocket functions --------------------
+        // response
+        function respCb(data) {
+            ls.stop();
+            o.scope.tableData = data[root];
+            o.scope.annots = data.annots;
+            onResp && onResp();
+
+            // checks if data changed for row flashing
+            if (!angular.equals(o.scope.tableData, oldTableData)) {
+                o.scope.changedData = [];
+                // only flash the row if the data already exists
+                if (oldTableData.length) {
+                    angular.forEach(o.scope.tableData, function (item) {
+                        if (!fs.containsObj(oldTableData, item)) {
+                            o.scope.changedData.push(item);
+                        }
+                    });
+                }
+                angular.copy(o.scope.tableData, oldTableData);
+            }
+        }
+        handlers[resp] = respCb;
+        wss.bindHandlers(handlers);
+
+        // request
+        function sortCb(params) {
+            var p = angular.extend({}, params, o.query);
+            if (wss.isConnected()) {
+                wss.sendEvent(req, p);
+                ls.start();
+            }
+        }
+        o.scope.sortCallback = sortCb;
+
+        // === selecting a row functions ----------------
+        function selCb($event, selRow) {
+            var selId = selRow[idKey];
+            o.scope.selId = (o.scope.selId === selId) ? null : selId;
+            onSel && onSel($event, selRow);
+        }
+        o.scope.selectCallback = selCb;
+
+        // === autoRefresh functions ------------------
+        function fetchDataIfNotWaiting() {
+            if (!ls.waiting()) {
+                if (fs.debugOn('widget')) {
+                    $log.debug('Refreshing ' + root + ' page');
+                }
+                sortCb(o.scope.sortParams);
+            }
+        }
+
+        function startRefresh() {
+            refreshPromise = $interval(fetchDataIfNotWaiting, refreshInterval);
+        }
+
+        function stopRefresh() {
+            if (refreshPromise) {
+                $interval.cancel(refreshPromise);
+                refreshPromise = null;
+            }
+        }
+
+        function toggleRefresh() {
+            o.scope.autoRefresh = !o.scope.autoRefresh;
+            o.scope.autoRefresh ? startRefresh() : stopRefresh();
+        }
+        o.scope.toggleRefresh = toggleRefresh;
+
+        // === Cleanup on destroyed scope -----------------
+        o.scope.$on('$destroy', function () {
+            wss.unbindHandlers(handlers);
+            stopRefresh();
+            ls.stop();
+        });
+
+        sortCb(o.scope.sortParams);
+        startRefresh();
+    }
+
+    var app1 = angular.module('ovIntApp', []);
+    app1.controller('OvIntAppCtrl',
+        ['$log', '$scope', '$interval', '$timeout', 'TableBuilderService',
+            'FnService', 'WebSocketService', 'KeyService', 'LoadingService',
+
+            function(_$log_, _$scope_, _$interval_, _$timeout_, tbs, _fs_, _wss_, _ks_, _ls_) {
+                $log = _$log_;
+                $scope = _$scope_;
+                $interval = _$interval_;
+                $timeout = _$timeout_;
+                fs = _fs_;
+                wss = _wss_;
+                ks = _ks_;
+                ls = _ls_;
+
+                // custom selection callback
+                function selCb($event, row) {
+                }
+                intIntentBuildTable({
+                    scope: $scope,
+                    tag: 'intAppIntIntent'
+                    // selCb: selCb
+                });
+
+                $scope.sendIntIntentString = sendIntIntentString;
+                $scope.delIntIntent = delIntIntent;
+                $scope.sendIntConfigString = sendIntConfigString;
+
+                // get data the first time...
+                // getData();
+
+                // cleanup
+                $scope.$on('$destroy', function() {
+                    // wss.unbindHandlers(handlers);
+                    /*ks.unbindKeys();*/
+                    $log.log('OvIntAppCtrl has been destroyed');
+                });
+
+                $log.log('OvIntAppCtrl has been created');
+            }
+        ]);
+}());
diff --git a/apps/inbandtelemetry/app/src/main/resources/intApp/css.html b/apps/inbandtelemetry/app/src/main/resources/intApp/css.html
new file mode 100644
index 0000000..4996aa6
--- /dev/null
+++ b/apps/inbandtelemetry/app/src/main/resources/intApp/css.html
@@ -0,0 +1 @@
+<link rel="stylesheet" href="app/view/intApp/intApp.css">
diff --git a/apps/inbandtelemetry/app/src/main/resources/intApp/js.html b/apps/inbandtelemetry/app/src/main/resources/intApp/js.html
new file mode 100644
index 0000000..9a7b0ac
--- /dev/null
+++ b/apps/inbandtelemetry/app/src/main/resources/intApp/js.html
@@ -0,0 +1 @@
+<script src="app/view/intApp/intApp.js"></script>
diff --git a/apps/inbandtelemetry/impl/BUCK b/apps/inbandtelemetry/impl/BUCK
new file mode 100644
index 0000000..9c87c92
--- /dev/null
+++ b/apps/inbandtelemetry/impl/BUCK
@@ -0,0 +1,28 @@
+BUNDLES = [
+    '//apps/inbandtelemetry/api:onos-apps-inbandtelemetry-api',
+    ':onos-apps-inbandtelemetry-impl'
+]
+COMPILE_DEPS = [
+    '//lib:CORE_DEPS',
+    '//lib:KRYO',
+    '//core/store/serializers:onos-core-serializers',
+    '//pipelines/basic:onos-pipelines-basic',
+    '//apps/inbandtelemetry/api:onos-apps-inbandtelemetry-api',
+]
+
+TEST_DEPS = [
+    '//lib:TEST_ADAPTERS',
+]
+
+osgi_jar_with_tests (
+    deps = COMPILE_DEPS,
+    test_deps = TEST_DEPS,
+)
+
+onos_app(
+    title = 'P4 In-band Network Telemetry Service',
+    description = 'Provides managements of INT-capable devices. Specifies flows to enable INT and' +
+    'types of metadata to collect. Sets up INT-related information.',
+    category = 'Monitoring',
+    included_bundles = BUNDLES,
+)
\ No newline at end of file
diff --git a/apps/inbandtelemetry/impl/pom.xml b/apps/inbandtelemetry/impl/pom.xml
new file mode 100644
index 0000000..455d12e
--- /dev/null
+++ b/apps/inbandtelemetry/impl/pom.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2016-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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>onos-apps-inbandtelemetry</artifactId>
+        <groupId>org.onosproject</groupId>
+        <version>1.14.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>onos-apps-inbandtelemetry-impl</artifactId>
+    <packaging>bundle</packaging>
+
+    <url>http://onosproject.org</url>
+
+    <description>P4 INT Management Implementation</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-apps-inbandtelemetry-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-core-serializers</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/apps/inbandtelemetry/impl/src/main/java/org/onosproject/inbandtelemetry/impl/IntManager.java b/apps/inbandtelemetry/impl/src/main/java/org/onosproject/inbandtelemetry/impl/IntManager.java
new file mode 100644
index 0000000..5cf32da
--- /dev/null
+++ b/apps/inbandtelemetry/impl/src/main/java/org/onosproject/inbandtelemetry/impl/IntManager.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2015-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.inbandtelemetry.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.inbandtelemetry.api.IntConfig;
+import org.onosproject.inbandtelemetry.api.IntIntent;
+import org.onosproject.inbandtelemetry.api.IntIntentId;
+import org.onosproject.inbandtelemetry.api.IntObjective;
+import org.onosproject.inbandtelemetry.api.IntProgrammable;
+import org.onosproject.inbandtelemetry.api.IntService;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.AtomicIdGenerator;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of IntService, for controlling INT-capable pipelines.
+ */
+@Component(immediate = true)
+@Service
+public class IntManager implements IntService {
+    private final String appName = "org.onosproject.inbandtelemetry";
+    private ApplicationId appId;
+    private final Logger log = getLogger(getClass());
+    private ConsistentMap<IntIntentId, IntIntent> intentConsistentMap;
+    private ConsistentMap<DeviceId, IntDeviceRole> deviceRoleConsistentMap;
+    private IntConfig cfg;
+    private AtomicIdGenerator intentIds;
+
+    private InternalHostListener hostListener = new InternalHostListener();
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private StorageService storageService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private FlowRuleService flowRuleService;
+
+    @Activate
+    public void activate() {
+        appId = coreService.registerApplication(appName);
+
+        KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
+                .register(KryoNamespaces.API)
+                .register(IntIntent.class)
+                .register(IntIntentId.class)
+                .register(IntDeviceRole.class)
+                .register(IntIntent.IntHeaderType.class)
+                .register(IntIntent.IntMetadataType.class)
+                .register(IntIntent.IntReportType.class)
+                .register(IntIntent.TelemetryMode.class);
+
+        intentConsistentMap = storageService.<IntIntentId, IntIntent>consistentMapBuilder()
+                .withSerializer(Serializer.using(serializer.build()))
+                .withName("int-intents")
+                .withApplicationId(appId)
+                .withPurgeOnUninstall()
+                .build();
+
+        deviceRoleConsistentMap = storageService.<DeviceId, IntDeviceRole>consistentMapBuilder()
+                .withSerializer(Serializer.using(serializer.build()))
+                .withName("int-device-roles")
+                .withApplicationId(appId)
+                .withPurgeOnUninstall()
+                .build();
+
+        // Assign IntDeviceRole to each device
+        deviceService.getAvailableDevices().forEach(device ->
+                deviceRoleConsistentMap.put(device.id(),
+                                            hostService.getConnectedHosts(device.id()).isEmpty() ?
+                                                    IntDeviceRole.TRANSIT :
+                                                    IntDeviceRole.SOURCE_SINK)
+        );
+        hostService.addListener(hostListener);
+        intentIds = storageService.getAtomicIdGenerator("int-intent-id-generator");
+        startInt();
+        log.info("Started", appId.id());
+    }
+
+    @Deactivate
+    public void deactivate() {
+        hostService.removeListener(hostListener);
+        log.info("Deactivated");
+    }
+
+    @Override
+    public void startInt() {
+        deviceService.getAvailableDevices().forEach(device -> {
+            if (device.is(IntProgrammable.class)) {
+                IntProgrammable intDevice = device.as(IntProgrammable.class);
+                intDevice.init();
+            }
+        });
+    }
+
+    @Override
+    public void startInt(Set<DeviceId> deviceIds) {
+        deviceIds.forEach(deviceId -> {
+            Device device = deviceService.getDevice(deviceId);
+            if (device.is(IntProgrammable.class) &&
+                    getIntRole(deviceId) == IntDeviceRole.TRANSIT) {
+                IntProgrammable intDevice = device.as(IntProgrammable.class);
+                intDevice.init();
+            }
+        });
+    }
+
+    @Override
+    public void stopInt() {
+        flowRuleService.removeFlowRulesById(appId);
+    }
+
+    @Override
+    public void stopInt(Set<DeviceId> deviceIds) {
+
+    }
+
+    @Override
+    public void setConfig(IntConfig cfg) {
+        this.cfg = cfg;
+        deviceService.getAvailableDevices().forEach(device -> {
+            if (device.is(IntProgrammable.class)) {
+                IntProgrammable intDevice = device.as(IntProgrammable.class);
+                intDevice.setupIntConfig(cfg);
+            }
+        });
+    }
+
+    @Override
+    public IntConfig getConfig() {
+        return cfg;
+    }
+
+    @Override
+    public IntIntentId installIntIntent(IntIntent intent) {
+        Integer intentId = (int) intentIds.nextId();
+        IntIntentId intIntentId = IntIntentId.valueOf(intentId);
+        intentConsistentMap.put(intIntentId, intent);
+
+        // Convert IntIntent into an IntObjective
+        IntObjective obj = new IntObjective.Builder()
+                .withSelector(intent.selector())
+                .withMetadataTypes(intent.metadataTypes())
+                .withHeaderType(intent.headerType())
+                .build();
+
+        // Install IntObjective on each INT source device
+        deviceService.getAvailableDevices().forEach(device -> {
+            if (device.is(IntProgrammable.class)
+                    && deviceRoleConsistentMap.get(device.id()).value() == IntDeviceRole.SOURCE_SINK) {
+                IntProgrammable intDevice = device.as(IntProgrammable.class);
+                intDevice.addIntObjective(obj);
+            }
+        });
+        return intIntentId;
+    }
+
+    @Override
+    public void removeIntIntent(IntIntentId intentId) {
+        IntIntent intent = intentConsistentMap.remove(intentId).value();
+
+        // Convert IntIntent into an IntObjective
+        IntObjective obj = new IntObjective.Builder()
+                .withSelector(intent.selector())
+                .withMetadataTypes(intent.metadataTypes())
+                .withHeaderType(intent.headerType())
+                .build();
+
+        // Remove IntObjective on each INT source device
+        deviceService.getAvailableDevices().forEach(device -> {
+            if (device.is(IntProgrammable.class)
+                    && deviceRoleConsistentMap.get(device.id()).value() == IntDeviceRole.SOURCE_SINK) {
+                IntProgrammable intDevice = device.as(IntProgrammable.class);
+                intDevice.removeIntObjective(obj);
+            }
+        });
+    }
+
+    @Override
+    public IntIntent getIntIntent(IntIntentId intentId) {
+        return Optional.ofNullable(intentConsistentMap.get(intentId).value()).orElse(null);
+    }
+
+    @Override
+    public Map<IntIntentId, IntIntent> getIntIntents() {
+        return intentConsistentMap.asJavaMap();
+    }
+
+    private IntDeviceRole getIntRole(DeviceId deviceId) {
+        return deviceRoleConsistentMap.get(deviceId).value();
+    }
+
+    private void setIntRole(DeviceId deviceId, IntDeviceRole role) {
+        deviceRoleConsistentMap.put(deviceId, role);
+    }
+
+    private class InternalHostListener implements HostListener {
+        @Override
+        public void event(HostEvent event) {
+            DeviceId deviceId = event.subject().location().deviceId();
+            if (!deviceService.getDevice(deviceId).is(IntProgrammable.class)) {
+                return;
+            }
+            switch (event.type()) {
+                case HOST_ADDED:
+                    // When a host is attached to the switch, we can configure it
+                    // to work as SOURCE_SINK switch.
+                    if (deviceRoleConsistentMap.getOrDefault(deviceId, IntDeviceRole.TRANSIT).value()
+                            != IntDeviceRole.SOURCE_SINK) {
+                        setIntRole(deviceId, IntDeviceRole.SOURCE_SINK);
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+}
diff --git a/apps/inbandtelemetry/impl/src/main/java/org/onosproject/inbandtelemetry/impl/package-info.java b/apps/inbandtelemetry/impl/src/main/java/org/onosproject/inbandtelemetry/impl/package-info.java
new file mode 100644
index 0000000..d045fe9
--- /dev/null
+++ b/apps/inbandtelemetry/impl/src/main/java/org/onosproject/inbandtelemetry/impl/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2015-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.
+ */
+
+/**
+ * Service to control a network of devices capable of collecting and exporting
+ * data plane telemetry via in-band mechanism.
+ */
+package org.onosproject.inbandtelemetry.impl;
diff --git a/apps/inbandtelemetry/pom.xml b/apps/inbandtelemetry/pom.xml
index 57a8351..0105fbe 100644
--- a/apps/inbandtelemetry/pom.xml
+++ b/apps/inbandtelemetry/pom.xml
@@ -32,5 +32,6 @@
 
     <modules>
         <module>api</module>
+        <module>impl</module>
     </modules>
 </project>
diff --git a/modules.defs b/modules.defs
index 16e8d8d..d3ae3b8 100644
--- a/modules.defs
+++ b/modules.defs
@@ -255,6 +255,7 @@
     '//apps/mcast:onos-apps-mcast-oar',
     '//apps/layout:onos-apps-layout-oar',
     '//apps/imr:onos-apps-imr-oar',
+    '//apps/inbandtelemetry/app:onos-apps-inbandtelemetry-app-oar',
     # nodemetrics application
     '//apps/nodemetrics:onos-apps-nodemetrics-oar',
     '//web/gui2:onos-web-gui2-oar',