Add REST API for query/update/delete/enable/disable telemetry config

1. Add unit test for newly added REST APIs
2. Add codec and unit tests for TelemetryConfig
3. Split web package out from app package due to dep conflict

Change-Id: I85f52b2a7d059622e98832843bc9613cb8befa98
diff --git a/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/codec/rest/FlowInfoJsonCodec.java b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/codec/rest/FlowInfoJsonCodec.java
new file mode 100644
index 0000000..a6d75ec
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/codec/rest/FlowInfoJsonCodec.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2018-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.openstacktelemetry.codec.rest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.DeviceId;
+import org.onosproject.openstacktelemetry.api.FlowInfo;
+import org.onosproject.openstacktelemetry.api.StatsInfo;
+import org.onosproject.openstacktelemetry.api.DefaultFlowInfo;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.packet.VlanId.NO_VID;
+
+/**
+ * Openstack telemetry codec used for serializing and de-serializing JSON string.
+ */
+public final class FlowInfoJsonCodec extends JsonCodec<FlowInfo> {
+
+    private static final String FLOW_TYPE = "flowType";
+    private static final String DEVICE_ID = "deviceId";
+    private static final String INPUT_INTERFACE_ID = "inputInterfaceId";
+    private static final String OUTPUT_INTERFACE_ID = "outputInterfaceId";
+
+    private static final String VLAN_ID = "vlanId";
+    private static final String VXLAN_ID = "vxlanId";
+    private static final String SRC_IP = "srcIp";
+    private static final String SRC_IP_PREFIX_LEN = "srcIpPrefixLength";
+    private static final String DST_IP = "dstIp";
+    private static final String DST_IP_PREFIX_LEN = "dstIpPrefixLength";
+    private static final String SRC_PORT = "srcPort";
+    private static final String DST_PORT = "dstPort";
+    private static final String PROTOCOL = "protocol";
+    private static final String SRC_MAC = "srcMac";
+    private static final String DST_MAC = "dstMac";
+    private static final String STATS_INFO = "statsInfo";
+
+    @Override
+    public ObjectNode encode(FlowInfo info, CodecContext context) {
+        checkNotNull(info, "FlowInfo cannot be null");
+
+        ObjectNode result = context.mapper().createObjectNode()
+                .put(FLOW_TYPE, info.flowType())
+                .put(DEVICE_ID, info.deviceId().toString())
+                .put(INPUT_INTERFACE_ID, info.inputInterfaceId())
+                .put(OUTPUT_INTERFACE_ID, info.outputInterfaceId())
+                .put(SRC_IP, info.srcIp().address().toString())
+                .put(SRC_IP_PREFIX_LEN, info.srcIp().prefixLength())
+                .put(DST_IP, info.dstIp().address().toString())
+                .put(DST_IP_PREFIX_LEN, info.dstIp().prefixLength())
+                .put(SRC_PORT, info.srcPort().toString())
+                .put(DST_PORT, info.dstPort().toString())
+                .put(PROTOCOL, info.protocol())
+                .put(SRC_MAC, info.srcMac().toString())
+                .put(DST_MAC, info.dstMac().toString());
+
+        if (info.vlanId() != null) {
+            result.put(VLAN_ID, info.vlanId().toString());
+        } else {
+            result.put(VXLAN_ID, info.vxlanId());
+        }
+
+        ObjectNode statsInfoJson =
+                context.codec(StatsInfo.class).encode(info.statsInfo(), context);
+
+        result.put(STATS_INFO, statsInfoJson);
+
+        return result;
+    }
+
+    @Override
+    public FlowInfo decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        String flowType = json.get(FLOW_TYPE).asText();
+        String deviceId = json.get(DEVICE_ID).asText();
+        int inputInterfaceId = json.get(INPUT_INTERFACE_ID).asInt();
+        int outputInterfaceId = json.get(OUTPUT_INTERFACE_ID).asInt();
+        String srcIp = json.get(SRC_IP).asText();
+        int srcIpPrefixLength = json.get(SRC_IP_PREFIX_LEN).asInt();
+        String dstIp = json.get(DST_IP).asText();
+        int dstIpPrefixLength = json.get(DST_IP_PREFIX_LEN).asInt();
+        int srcPort = json.get(SRC_PORT).asInt();
+        int dstPort = json.get(DST_PORT).asInt();
+        String protocol = json.get(PROTOCOL).asText();
+        String srcMac = json.get(SRC_MAC).asText();
+        String dstMac = json.get(DST_MAC).asText();
+
+        VlanId vlanId;
+        short vxlanId = 0;
+        try {
+            if (json.get(VLAN_ID).isNull()) {
+                vlanId = VlanId.vlanId(NO_VID);
+                if (!(json.get(VXLAN_ID).isNull())) {
+                    vxlanId = (short) json.get(VXLAN_ID).asInt();
+                }
+            } else {
+                vlanId = VlanId.vlanId((short) json.get(VLAN_ID).asInt());
+            }
+        } catch (NullPointerException ex) {
+            vlanId = VlanId.vlanId();
+        }
+
+        JsonNode statsInfoJson = json.get(STATS_INFO);
+
+        JsonCodec<StatsInfo> statsInfoCodec = context.codec(StatsInfo.class);
+        StatsInfo statsInfo = statsInfoCodec.decode((ObjectNode) statsInfoJson.deepCopy(), context);
+
+        return new DefaultFlowInfo.DefaultBuilder()
+                .withFlowType(Byte.valueOf(flowType))
+                .withDeviceId(DeviceId.deviceId(deviceId))
+                .withInputInterfaceId(inputInterfaceId)
+                .withOutputInterfaceId(outputInterfaceId)
+                .withSrcIp(IpPrefix.valueOf(IpAddress.valueOf(srcIp), srcIpPrefixLength))
+                .withDstIp(IpPrefix.valueOf(IpAddress.valueOf(dstIp), dstIpPrefixLength))
+                .withSrcPort(TpPort.tpPort(srcPort))
+                .withDstPort(TpPort.tpPort(dstPort))
+                .withProtocol(Byte.valueOf(protocol))
+                .withSrcMac(MacAddress.valueOf(srcMac))
+                .withDstMac(MacAddress.valueOf(dstMac))
+                .withVlanId(vlanId)
+                .withVxlanId(vxlanId)
+                .withStatsInfo(statsInfo)
+                .build();
+    }
+}
diff --git a/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/codec/rest/StatsFlowRuleJsonCodec.java b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/codec/rest/StatsFlowRuleJsonCodec.java
new file mode 100644
index 0000000..3d2b13f
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/codec/rest/StatsFlowRuleJsonCodec.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2018-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.openstacktelemetry.codec.rest;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.TpPort;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.openstacktelemetry.api.DefaultStatsFlowRule;
+import org.onosproject.openstacktelemetry.api.StatsFlowRule;
+import org.slf4j.Logger;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * JSON codec for StatsFlowRule.
+ */
+public class StatsFlowRuleJsonCodec extends JsonCodec<StatsFlowRule> {
+
+    private final Logger log = getLogger(getClass());
+
+    private static final String PROTOCOL_NAME_TCP = "tcp";
+    private static final String PROTOCOL_NAME_UDP = "udp";
+    private static final String PROTOCOL_NAME_ANY = "any";
+    private static final int ARBITRARY_PROTOCOL = 0x0;
+
+    public static final String SRC_IP_PREFIX = "srcIpPrefix";
+    public static final String DST_IP_PREFIX = "dstIpPrefix";
+    public static final String IP_PROTOCOL   = "ipProtocol";
+    public static final String SRC_TP_PORT   = "srcTpPort";
+    public static final String DST_TP_PORT   = "dstTpPort";
+
+    public ObjectNode encode(StatsFlowRule flowRule, CodecContext context) {
+        checkNotNull(flowRule, "FlowInfo cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                            .put(SRC_IP_PREFIX, flowRule.srcIpPrefix().toString())
+                            .put(DST_IP_PREFIX, flowRule.dstIpPrefix().toString())
+                            .put(IP_PROTOCOL, flowRule.ipProtocol())
+                            .put(SRC_TP_PORT, flowRule.srcTpPort().toString())
+                            .put(DST_TP_PORT, flowRule.dstTpPort().toString());
+        return result;
+    }
+
+    @Override
+    public StatsFlowRule decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+        try {
+            String srcIpPrefix = json.get(SRC_IP_PREFIX).asText();
+            String dstIpPrefix = json.get(DST_IP_PREFIX).asText();
+
+            DefaultStatsFlowRule.Builder flowRuleBuilder;
+
+            if (json.get(IP_PROTOCOL) == null) {
+                log.info("ipProtocol: null");
+                flowRuleBuilder = DefaultStatsFlowRule.builder()
+                        .srcIpPrefix(IpPrefix.valueOf(srcIpPrefix))
+                        .dstIpPrefix(IpPrefix.valueOf(dstIpPrefix));
+            } else {
+                byte ipProtocol = getProtocolTypeFromString(json.get(IP_PROTOCOL).asText());
+                int srcTpPort = json.get(SRC_TP_PORT).asInt();
+                int dstTpPort = json.get(DST_TP_PORT).asInt();
+
+                flowRuleBuilder = DefaultStatsFlowRule.builder()
+                                    .srcIpPrefix(IpPrefix.valueOf(srcIpPrefix))
+                                    .dstIpPrefix(IpPrefix.valueOf(dstIpPrefix))
+                                    .ipProtocol(ipProtocol)
+                                    .srcTpPort(TpPort.tpPort(srcTpPort))
+                                    .dstTpPort(TpPort.tpPort(dstTpPort));
+            }
+            return flowRuleBuilder.build();
+        } catch (Exception ex) {
+            log.error("Exception Stack:\n{}", ExceptionUtils.getStackTrace(ex));
+        }
+        return null;
+    }
+
+    /**
+     * Obtains transport protocol type from the given string.
+     *
+     * @param str transport protocol name
+     * @return transport protocol type
+     */
+    private byte getProtocolTypeFromString(String str) {
+        switch (str.toLowerCase()) {
+            case PROTOCOL_NAME_TCP:
+                return IPv4.PROTOCOL_TCP;
+            case PROTOCOL_NAME_UDP:
+                return IPv4.PROTOCOL_UDP;
+            default:
+                return ARBITRARY_PROTOCOL;
+        }
+    }
+}
diff --git a/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/codec/rest/StatsInfoJsonCodec.java b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/codec/rest/StatsInfoJsonCodec.java
new file mode 100644
index 0000000..de8a7ef
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/codec/rest/StatsInfoJsonCodec.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2018-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.openstacktelemetry.codec.rest;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.openstacktelemetry.api.StatsInfo;
+import org.onosproject.openstacktelemetry.api.DefaultStatsInfo;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Openstack telemetry codec used for serializing and de-serializing JSON string.
+ */
+public final class StatsInfoJsonCodec extends JsonCodec<StatsInfo> {
+
+    private static final String STARTUP_TIME = "startupTime";
+    private static final String FST_PKT_ARR_TIME = "fstPktArrTime";
+    private static final String LST_PKT_OFFSET = "lstPktOffset";
+    private static final String PREV_ACC_BYTES = "prevAccBytes";
+    private static final String PREV_ACC_PKTS = "prevAccPkts";
+    private static final String CURR_ACC_BYTES = "currAccBytes";
+    private static final String CURR_ACC_PKTS = "currAccPkts";
+    private static final String ERROR_PKTS = "errorPkts";
+    private static final String DROP_PKTS = "dropPkts";
+
+    @Override
+    public ObjectNode encode(StatsInfo info, CodecContext context) {
+        checkNotNull(info, "StatsInfo cannot be null");
+
+        return context.mapper().createObjectNode()
+                .put(STARTUP_TIME, info.startupTime())
+                .put(FST_PKT_ARR_TIME, info.fstPktArrTime())
+                .put(LST_PKT_OFFSET, info.lstPktOffset())
+                .put(PREV_ACC_BYTES, info.prevAccBytes())
+                .put(PREV_ACC_PKTS, info.prevAccPkts())
+                .put(CURR_ACC_BYTES, info.prevAccBytes())
+                .put(CURR_ACC_PKTS, info.prevAccPkts())
+                .put(ERROR_PKTS, info.errorPkts())
+                .put(DROP_PKTS, info.dropPkts());
+    }
+
+    @Override
+    public StatsInfo decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        return new DefaultStatsInfo.DefaultBuilder()
+                .withStartupTime(json.get(STARTUP_TIME).asLong())
+                .withFstPktArrTime(json.get(FST_PKT_ARR_TIME).asLong())
+                .withLstPktOffset(json.get(LST_PKT_OFFSET).asInt())
+                .withPrevAccBytes(json.get(PREV_ACC_BYTES).asLong())
+                .withPrevAccPkts(json.get(PREV_ACC_PKTS).asInt())
+                .withCurrAccBytes(json.get(CURR_ACC_BYTES).asLong())
+                .withCurrAccPkts(json.get(CURR_ACC_PKTS).asInt())
+                .withErrorPkts((short) json.get(ERROR_PKTS).asInt())
+                .withDropPkts((short) json.get(DROP_PKTS).asInt())
+                .build();
+    }
+}
diff --git a/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/codec/rest/TelemetryConfigJsonCodec.java b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/codec/rest/TelemetryConfigJsonCodec.java
new file mode 100644
index 0000000..8c0a6e3
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/codec/rest/TelemetryConfigJsonCodec.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2018-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.openstacktelemetry.codec.rest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.openstacktelemetry.api.DefaultTelemetryConfig;
+import org.onosproject.openstacktelemetry.api.config.TelemetryConfig;
+
+import java.util.Map;
+import java.util.stream.IntStream;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+
+/**
+ * Openstack telemetry config codec used for serializing and de-serializing JSON string.
+ */
+public final class TelemetryConfigJsonCodec extends JsonCodec<TelemetryConfig> {
+
+    private static final String NAME = "name";
+    private static final String TYPE = "type";
+    private static final String MANUFACTURER = "manufacturer";
+    private static final String SW_VERSION = "swVersion";
+    private static final String ENABLED = "enabled";
+    private static final String PROPS = "props";
+    private static final String KEY = "key";
+    private static final String VALUE = "value";
+
+    private static final String MISSING_MESSAGE = " is required in TelemetryConfig";
+
+    @Override
+    public ObjectNode encode(TelemetryConfig config, CodecContext context) {
+        checkNotNull(config, "TelemetryConfig cannot be null");
+
+        ObjectNode node = context.mapper().createObjectNode()
+                .put(NAME, config.name())
+                .put(TYPE, config.type().name())
+                .put(MANUFACTURER, config.manufacturer())
+                .put(SW_VERSION, config.swVersion())
+                .put(ENABLED, config.enabled());
+
+        Map<String, String> props = config.properties();
+        ArrayNode propsJson = context.mapper().createArrayNode();
+        props.forEach((k, v) -> {
+            ObjectNode propNode = context.mapper().createObjectNode();
+            propNode.put(KEY, k);
+            propNode.put(VALUE, v);
+            propsJson.add(propNode);
+        });
+        node.set(PROPS, propsJson);
+        return node;
+    }
+
+    @Override
+    public TelemetryConfig decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        // parse name
+        String name = nullIsIllegal(json.get(NAME),
+                NAME + MISSING_MESSAGE).asText();
+
+        // parse type
+        String type = nullIsIllegal(json.get(TYPE),
+                TYPE + MISSING_MESSAGE).asText();
+
+        TelemetryConfig.ConfigType configType = configType(type);
+
+        // parse manufacturer
+        String manufacturer = nullIsIllegal(json.get(MANUFACTURER).asText(),
+                MANUFACTURER + MISSING_MESSAGE);
+
+        // parse software version
+        String swVersion = nullIsIllegal(json.get(SW_VERSION),
+                SW_VERSION + MISSING_MESSAGE).asText();
+
+        // parse enabled flag
+        boolean enabled = nullIsIllegal(json.get(ENABLED),
+                ENABLED + MISSING_MESSAGE).asBoolean();
+
+        JsonNode propertiesJson = json.get(PROPS);
+        Map<String, String> properties = Maps.newConcurrentMap();
+        if (propertiesJson != null) {
+            IntStream.range(0, propertiesJson.size()).forEach(i -> {
+                ObjectNode propertyJson = get(propertiesJson, i);
+                properties.put(propertyJson.get(KEY).asText(),
+                        propertyJson.get(VALUE).asText());
+            });
+        }
+
+        return new DefaultTelemetryConfig(name, configType,
+                ImmutableList.of(), manufacturer, swVersion, enabled, properties);
+    }
+
+    private TelemetryConfig.ConfigType configType(String type) {
+        switch (type.toUpperCase()) {
+            case "KAFKA" :
+                return TelemetryConfig.ConfigType.KAFKA;
+            case "GRPC" :
+                return TelemetryConfig.ConfigType.GRPC;
+            case "INFLUXDB" :
+                return TelemetryConfig.ConfigType.INFLUXDB;
+            case "PROMETHEUS" :
+                return TelemetryConfig.ConfigType.PROMETHEUS;
+            case "REST" :
+                return TelemetryConfig.ConfigType.REST;
+            default:
+                return TelemetryConfig.ConfigType.UNKNOWN;
+        }
+    }
+}
diff --git a/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/codec/rest/package-info.java b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/codec/rest/package-info.java
new file mode 100644
index 0000000..3a699c1
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/codec/rest/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-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.
+ */
+
+/**
+ * Implementations of the codec broker and openstack telemetry entity codecs.
+ */
+package org.onosproject.openstacktelemetry.codec.rest;
\ No newline at end of file
diff --git a/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryCodecRegister.java b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryCodecRegister.java
new file mode 100644
index 0000000..6bd8636
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryCodecRegister.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2018-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.openstacktelemetry.web;
+
+import org.onosproject.codec.CodecService;
+import org.onosproject.openstacktelemetry.api.FlowInfo;
+import org.onosproject.openstacktelemetry.api.StatsFlowRule;
+import org.onosproject.openstacktelemetry.api.StatsInfo;
+import org.onosproject.openstacktelemetry.api.config.TelemetryConfig;
+import org.onosproject.openstacktelemetry.codec.rest.FlowInfoJsonCodec;
+import org.onosproject.openstacktelemetry.codec.rest.StatsFlowRuleJsonCodec;
+import org.onosproject.openstacktelemetry.codec.rest.StatsInfoJsonCodec;
+import org.onosproject.openstacktelemetry.codec.rest.TelemetryConfigJsonCodec;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the JSON codec brokering service for OpenstackTelemetry.
+ */
+@Component(immediate = true)
+public class OpenstackTelemetryCodecRegister {
+
+    private final org.slf4j.Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected CodecService codecService;
+
+    @Activate
+    protected void activate() {
+        codecService.registerCodec(StatsInfo.class, new StatsInfoJsonCodec());
+        codecService.registerCodec(FlowInfo.class, new FlowInfoJsonCodec());
+        codecService.registerCodec(StatsFlowRule.class, new StatsFlowRuleJsonCodec());
+        codecService.registerCodec(TelemetryConfig.class, new TelemetryConfigJsonCodec());
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        codecService.unregisterCodec(StatsInfo.class);
+        codecService.unregisterCodec(FlowInfo.class);
+        codecService.unregisterCodec(StatsFlowRule.class);
+        codecService.unregisterCodec(TelemetryConfig.class);
+
+        log.info("Stopped");
+    }
+}
diff --git a/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryConfigWebResource.java b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryConfigWebResource.java
new file mode 100644
index 0000000..7484d04
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryConfigWebResource.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2018-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.openstacktelemetry.web;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.Maps;
+import org.onosproject.openstacktelemetry.api.TelemetryConfigAdminService;
+import org.onosproject.openstacktelemetry.api.config.TelemetryConfig;
+import org.onosproject.rest.AbstractWebResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.Map;
+
+import static org.onlab.util.Tools.nullIsIllegal;
+import static org.onlab.util.Tools.nullIsNotFound;
+
+/**
+ * Handles REST API call of openstack telemetry configuration.
+ */
+@Path("config")
+public class OpenstackTelemetryConfigWebResource extends AbstractWebResource {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final String MESSAGE_CONFIG = "Received config %s request";
+    private static final String CONFIG = "config";
+    private static final String ADDRESS = "address";
+    private static final String QUERY = "QUERY";
+    private static final String UPDATE = "UPDATE";
+    private static final String DELETE = "DELETE";
+    private static final String CONFIG_NAME = "config name";
+    private static final String NOT_NULL_MESSAGE = " cannot be null";
+    private static final String CONFIG_NOT_FOUND = "Config is not found";
+
+    private final TelemetryConfigAdminService configService =
+                                        get(TelemetryConfigAdminService.class);
+
+    @Context
+    private UriInfo uriInfo;
+
+    /**
+     * Updates the telemetry configuration address from the JSON input stream.
+     *
+     * @param configName telemetry config name
+     * @param address telemetry config address
+     * @return 200 OK with the updated telemetry config, 400 BAD_REQUEST
+     * if the JSON is malformed, and 304 NOT_MODIFIED without the updated config
+     * due to incorrect configuration name so that we cannot find the existing config
+     */
+    @PUT
+    @Path("address/{name}/{address}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response updateConfigAddress(@PathParam("name") String configName,
+                                        @PathParam("address") String address) {
+        log.trace(String.format(MESSAGE_CONFIG, UPDATE));
+
+        try {
+            TelemetryConfig config = configService.getConfig(
+                    nullIsIllegal(configName, CONFIG_NAME + NOT_NULL_MESSAGE));
+
+            if (config == null) {
+                log.warn("There is no config found to modify for {}", configName);
+                return Response.notModified().build();
+            } else {
+                Map<String, String> updatedProperties =
+                        Maps.newHashMap(config.properties());
+                updatedProperties.put(ADDRESS,
+                        nullIsIllegal(address, ADDRESS + NOT_NULL_MESSAGE));
+                TelemetryConfig updatedConfig =
+                        config.updateProperties(updatedProperties);
+
+                configService.updateTelemetryConfig(updatedConfig);
+                return Response.ok().build();
+            }
+
+        } catch (Exception e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /**
+     * Deletes the telemetry configuration by referring to configuration name.
+     *
+     * @param configName telemetry configuration name
+     * @return 204 NO_CONTENT, 400 BAD_REQUEST if the JSON is malformed,
+     * and 304 NOT_MODIFIED without removing config, due to incorrect
+     * configuration name so that we cannot find the existing config
+     */
+    @DELETE
+    @Path("{name}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response deleteTelemetryConfig(@PathParam("name") String configName) {
+        log.trace(String.format(MESSAGE_CONFIG, DELETE));
+
+        TelemetryConfig config = configService.getConfig(
+                nullIsIllegal(configName, CONFIG_NAME + NOT_NULL_MESSAGE));
+
+        if (config == null) {
+            log.warn("There is no config found to delete for {}", configName);
+            return Response.notModified().build();
+        } else {
+            configService.removeTelemetryConfig(configName);
+            return Response.noContent().build();
+        }
+    }
+
+    /**
+     * Get details of telemetry config.
+     * Returns detailed properties of the specified telemetry config.
+     *
+     * @param configName telemetry configName
+     * @return 200 OK with detailed properties of the specific telemetry config
+     * @onos.rsModel TelemetryConfig
+     */
+    @GET
+    @Path("{name}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response getConfig(@PathParam("name") String configName) {
+        log.trace(String.format(MESSAGE_CONFIG, QUERY));
+
+        final TelemetryConfig config =
+                nullIsNotFound(configService.getConfig(configName), CONFIG_NOT_FOUND);
+        final ObjectNode root = codec(TelemetryConfig.class).encode(config, this);
+        return ok(root).build();
+    }
+
+    /**
+     * Enables the telemetry configuration with the given config name.
+     *
+     * @param configName telemetry configuration name
+     * @return 200 OK with the enabled telemetry config,
+     * 400 BAD_REQUEST if the JSON is malformed,
+     * and 304 NOT_MODIFIED without removing config, due to incorrect
+     * configuration name so that we cannot find the existing config
+     */
+    @PUT
+    @Path("enable/{name}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response enableConfig(@PathParam("name") String configName) {
+        log.trace(String.format(MESSAGE_CONFIG, UPDATE));
+
+        TelemetryConfig config = configService.getConfig(
+                nullIsIllegal(configName, CONFIG_NAME + NOT_NULL_MESSAGE));
+
+        if (config == null) {
+            log.warn("There is no config found to enable for {}", configName);
+            return Response.notModified().build();
+        } else {
+            TelemetryConfig updatedConfig = config.updateEnabled(true);
+            configService.updateTelemetryConfig(updatedConfig);
+            return Response.ok().build();
+        }
+    }
+
+    /**
+     * Disables the telemetry configuration with the given config name.
+     *
+     * @param configName telemetry configuration name
+     * @return 200 OK with the disabled telemetry config
+     * 400 BAD_REQUEST if the JSON is malformed,
+     * and 304 NOT_MODIFIED without removing config, due to incorrect
+     * configuration name so that we cannot find the existing config
+     */
+    @PUT
+    @Path("disable/{name}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response disableConfig(@PathParam("name") String configName) {
+        log.trace(String.format(MESSAGE_CONFIG, UPDATE));
+
+        TelemetryConfig config = configService.getConfig(
+                nullIsIllegal(configName, CONFIG_NAME + NOT_NULL_MESSAGE));
+
+        if (config == null) {
+            log.warn("There is no config found to disable for {}", configName);
+            return Response.notModified().build();
+        } else {
+            TelemetryConfig updatedConfig = config.updateEnabled(false);
+            configService.updateTelemetryConfig(updatedConfig);
+            return Response.ok().build();
+        }
+    }
+}
diff --git a/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryWebApplication.java b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryWebApplication.java
new file mode 100644
index 0000000..4deff1c
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryWebApplication.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018-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.openstacktelemetry.web;
+
+import org.onlab.rest.AbstractWebApplication;
+
+import java.util.Set;
+
+/**
+ * Openstack telemetry REST APIs web application.
+ */
+public class OpenstackTelemetryWebApplication extends AbstractWebApplication {
+    @Override
+    public Set<Class<?>> getClasses() {
+        return getClasses(OpenstackTelemetryWebResource.class,
+                OpenstackTelemetryConfigWebResource.class);
+    }
+}
diff --git a/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryWebResource.java b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryWebResource.java
new file mode 100644
index 0000000..40b5c93
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryWebResource.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2018-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.openstacktelemetry.web;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.Sets;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.openstacktelemetry.api.FlowInfo;
+import org.onosproject.openstacktelemetry.api.StatsFlowRule;
+import org.onosproject.openstacktelemetry.api.StatsFlowRuleAdminService;
+import org.onosproject.openstacktelemetry.codec.rest.FlowInfoJsonCodec;
+import org.onosproject.rest.AbstractWebResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.InputStream;
+import java.util.Set;
+
+import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
+import static javax.ws.rs.core.Response.created;
+import static org.onlab.util.Tools.readTreeFromStream;
+
+/**
+ * Handles REST API call of openstack telemetry.
+ */
+@Path("telemetry")
+public class OpenstackTelemetryWebResource extends AbstractWebResource {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private final ObjectNode root = mapper().createObjectNode();
+
+    private static final String JSON_NODE_FLOW_RULE = "rules";
+    private static final String FLOW_RULE_ID = "STATS_FLOW_RULE_ID";
+
+    private final StatsFlowRuleAdminService
+                    statsFlowRuleService = get(StatsFlowRuleAdminService.class);
+
+    @Context
+    private UriInfo uriInfo;
+
+    /**
+     * Creates a flow rule for metric.
+     *
+     * @param input openstack flow rule JSON input stream
+     * @return 201 CREATED if the JSON is correct,
+     *         400 BAD_REQUEST if the JSON is malformed.
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createBulkFlowRule(InputStream input) {
+        log.info("CREATE BULK FLOW RULE: {}", input.toString());
+
+        readNodeConfiguration(input).forEach(flowRule -> {
+                log.debug("FlowRule: {}", flowRule.toString());
+                statsFlowRuleService.createStatFlowRule(flowRule);
+            });
+
+        UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+                                            .path(JSON_NODE_FLOW_RULE)
+                                            .path(FLOW_RULE_ID);
+
+        return created(locationBuilder.build()).build();
+    }
+
+    /**
+     * Delete flow rules.
+     *
+     * @param input openstack flow rule JSON input stream
+     * @return 200 OK if processing is correct.
+     */
+    @DELETE
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response deleteBulkFlowRule(InputStream input) {
+        log.info("DELETE BULK FLOW RULE: {}", input.toString());
+
+        readNodeConfiguration(input).forEach(flowRule -> {
+            log.debug("FlowRule: {}", flowRule.toString());
+            statsFlowRuleService.deleteStatFlowRule(flowRule);
+        });
+
+        return ok(root).build();
+    }
+
+    /**
+     * Get flow rules which is installed on ONOS.
+     *
+     * @return 200 OK
+     */
+    public Response readBulkFlowRule() {
+        log.info("READ BULK FLOW RULE");
+
+        return ok(root).build();
+    }
+
+    /**
+     * Get flow information list.
+     *
+     * @return Flow information list
+     */
+    @GET
+    @Path("list")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response getFlowInfoBulk() {
+        log.info("GET BULK FLOW RULE");
+
+        Set<FlowInfo> flowInfoSet;
+        flowInfoSet = statsFlowRuleService.getOverlayFlowInfos();
+
+        JsonCodec<FlowInfo> flowInfoCodec = new FlowInfoJsonCodec();
+
+        ObjectNode nodeJson;
+        int idx = 0;
+        for (FlowInfo flowInfo: flowInfoSet) {
+            nodeJson = flowInfoCodec.encode(flowInfo, this);
+            root.put("FlowInfo" + idx++, nodeJson.toString());
+        }
+        return ok(root).build();
+    }
+
+    @GET
+    @Path("list/{srcIpPrefix}/{dstIpPrefix}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response getFlowRule(@PathParam("srcIpPrefix") String srcIpPrefix,
+                                @PathParam("dstIpPrefix") String dstIpPrefix) {
+        return ok(root).build();
+    }
+
+    private Set<StatsFlowRule> readNodeConfiguration(InputStream input) {
+        log.info("Input JSON Data: \n\t\t{}", input.toString());
+        Set<StatsFlowRule> flowRuleSet = Sets.newHashSet();
+        try {
+            JsonNode jsonTree = readTreeFromStream(mapper().enable(INDENT_OUTPUT), input);
+            ArrayNode nodes = (ArrayNode) jsonTree.path(JSON_NODE_FLOW_RULE);
+            nodes.forEach(node -> {
+                try {
+                    ObjectNode objectNode = node.deepCopy();
+                    log.debug("ObjectNode: {}", objectNode.toString());
+                    StatsFlowRule statsFlowRule = codec(StatsFlowRule.class)
+                                                        .decode(objectNode, this);
+                    log.debug("StatsFlowRule: {}", statsFlowRule.toString());
+                    flowRuleSet.add(statsFlowRule);
+                } catch (Exception ex) {
+                    log.error("Exception Stack:\n{}", ExceptionUtils.getStackTrace(ex));
+                    throw new IllegalArgumentException();
+                }
+            });
+        } catch (Exception ex) {
+            log.error("Exception Stack:\n{}", ExceptionUtils.getStackTrace(ex));
+            throw new IllegalArgumentException(ex);
+        }
+
+        return flowRuleSet;
+    }
+}
diff --git a/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/web/package-info.java b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/web/package-info.java
new file mode 100644
index 0000000..24c5ed66
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/main/java/org/onosproject/openstacktelemetry/web/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-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 GUI and REST API of openstack telemetry.
+ */
+package org.onosproject.openstacktelemetry.web;
\ No newline at end of file
diff --git a/apps/openstacktelemetry/web/src/main/resources/definitions/TelemetryConfig.json b/apps/openstacktelemetry/web/src/main/resources/definitions/TelemetryConfig.json
new file mode 100644
index 0000000..f4d1288
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/main/resources/definitions/TelemetryConfig.json
@@ -0,0 +1,53 @@
+{
+  "type": "object",
+  "required": [
+    "name",
+    "type",
+    "manufacturer",
+    "swVersion",
+    "enabled",
+    "properties"
+  ],
+  "properties": {
+    "name": {
+      "type": "string",
+      "example": "grpc config"
+    },
+    "type": {
+      "type": "string",
+      "example": "GRPC"
+    },
+    "manufacturer": {
+      "type": "string",
+      "example": "grpc.io"
+    },
+    "swVersion": {
+      "type": "string",
+      "example": "1.0"
+    },
+    "enabled": {
+      "type": "boolean",
+      "example": true
+    },
+    "props": {
+      "type": "array",
+      "items": {
+        "type": "object",
+        "required": [
+          "key",
+          "value"
+        ],
+        "properties": {
+          "key": {
+            "type": "string",
+            "example": "address"
+          },
+          "value": {
+            "type": "string",
+            "example": "127.0.0.1"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/apps/openstacktelemetry/web/src/main/webapp/WEB-INF/web.xml b/apps/openstacktelemetry/web/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..1181c43
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2018-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-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+         id="ONOS" version="2.5">
+    <display-name>Openstack Telemetry REST API v1.0</display-name>
+
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Secured</web-resource-name>
+            <url-pattern>/*</url-pattern>
+        </web-resource-collection>
+        <auth-constraint>
+            <role-name>admin</role-name>
+            <role-name>viewer</role-name>
+        </auth-constraint>
+    </security-constraint>
+
+    <security-role>
+        <role-name>admin</role-name>
+        <role-name>viewer</role-name>
+    </security-role>
+
+    <login-config>
+        <auth-method>BASIC</auth-method>
+        <realm-name>karaf</realm-name>
+    </login-config>
+
+    <servlet>
+        <servlet-name>JAX-RS Service</servlet-name>
+        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+        <init-param>
+            <param-name>javax.ws.rs.Application</param-name>
+            <param-value>org.onosproject.openstacktelemetry.web.OpenstackTelemetryWebApplication</param-value>
+        </init-param>
+        <load-on-startup>1</load-on-startup>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>JAX-RS Service</servlet-name>
+        <url-pattern>/*</url-pattern>
+    </servlet-mapping>
+</web-app>
diff --git a/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/codec/rest/FlowInfoJsonCodecTest.java b/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/codec/rest/FlowInfoJsonCodecTest.java
new file mode 100644
index 0000000..4d3cb47
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/codec/rest/FlowInfoJsonCodecTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2018-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.openstacktelemetry.codec.rest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.testing.EqualsTester;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.codec.impl.CodecManager;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.openstacktelemetry.api.FlowInfo;
+import org.onosproject.openstacktelemetry.api.StatsInfo;
+import org.onosproject.openstacktelemetry.api.DefaultFlowInfo;
+import org.onosproject.openstacktelemetry.api.DefaultStatsInfo;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.openstacktelemetry.codec.rest.FlowInfoJsonMatcher.matchesFlowInfo;
+
+/**
+ * Unit tests for FlowInfo codec.
+ */
+public class FlowInfoJsonCodecTest {
+    MockCodecContext context;
+    JsonCodec<FlowInfo> flowInfoCodec;
+    JsonCodec<StatsInfo> statsInfoCodec;
+    final CoreService mockCoreService = createMock(CoreService.class);
+    private static final String REST_APP_ID = "org.onosproject.rest";
+
+    private static final int INPUT_INTERFACE_ID = 1;
+    private static final int OUTPUT_INTERFACE_ID = 2;
+
+    private static final int VLAN_ID = 1;
+    private static final short VXLAN_ID = 10;
+    private static final int PROTOCOL = 1;
+    private static final int FLOW_TYPE = 1;
+    private static final String DEVICE_ID = "of:00000000000000a1";
+
+    private static final String SRC_IP_ADDRESS = "10.10.10.1";
+    private static final int SRC_IP_PREFIX = 24;
+    private static final String DST_IP_ADDRESS = "20.20.20.1";
+    private static final int DST_IP_PREFIX = 24;
+    private static final int SRC_PORT = 1000;
+    private static final int DST_PORT = 2000;
+    private static final String SRC_MAC_ADDRESS = "AA:BB:CC:DD:EE:FF";
+    private static final String DST_MAC_ADDRESS = "FF:EE:DD:CC:BB:AA";
+
+    private static final long LONG_VALUE = 1L;
+    private static final int INTEGER_VALUE = 1;
+    private static final short SHORT_VALUE = (short) 1;
+
+    /**
+     * Initial setup for this unit test.
+     */
+    @Before
+    public void setUp() {
+        context = new MockCodecContext();
+        flowInfoCodec = new FlowInfoJsonCodec();
+        statsInfoCodec = new StatsInfoJsonCodec();
+
+        assertThat(flowInfoCodec, notNullValue());
+        assertThat(statsInfoCodec, notNullValue());
+
+        expect(mockCoreService.registerApplication(REST_APP_ID))
+                .andReturn(APP_ID).anyTimes();
+        replay(mockCoreService);
+        context.registerService(CoreService.class, mockCoreService);
+    }
+
+    /**
+     * Tests the flow info encoding.
+     */
+    @Test
+    public void testEncode() {
+        StatsInfo statsInfo = new DefaultStatsInfo.DefaultBuilder()
+                                    .withStartupTime(LONG_VALUE)
+                                    .withFstPktArrTime(LONG_VALUE)
+                                    .withLstPktOffset(INTEGER_VALUE)
+                                    .withPrevAccBytes(LONG_VALUE)
+                                    .withPrevAccPkts(INTEGER_VALUE)
+                                    .withCurrAccBytes(LONG_VALUE)
+                                    .withCurrAccPkts(INTEGER_VALUE)
+                                    .withErrorPkts(SHORT_VALUE)
+                                    .withDropPkts(SHORT_VALUE)
+                                    .build();
+        FlowInfo flowInfo = new DefaultFlowInfo.DefaultBuilder()
+                                    .withFlowType((byte) FLOW_TYPE)
+                                    .withDeviceId(DeviceId.deviceId(DEVICE_ID))
+                                    .withInputInterfaceId(INPUT_INTERFACE_ID)
+                                    .withOutputInterfaceId(OUTPUT_INTERFACE_ID)
+                                    .withVlanId(VlanId.vlanId((short) VLAN_ID))
+                                    .withSrcIp(IpPrefix.valueOf(
+                                            IpAddress.valueOf(SRC_IP_ADDRESS), SRC_IP_PREFIX))
+                                    .withDstIp(IpPrefix.valueOf(
+                                            IpAddress.valueOf(DST_IP_ADDRESS), DST_IP_PREFIX))
+                                    .withSrcPort(TpPort.tpPort(SRC_PORT))
+                                    .withDstPort(TpPort.tpPort(DST_PORT))
+                                    .withProtocol((byte) PROTOCOL)
+                                    .withSrcMac(MacAddress.valueOf(SRC_MAC_ADDRESS))
+                                    .withDstMac(MacAddress.valueOf(DST_MAC_ADDRESS))
+                                    .withStatsInfo(statsInfo)
+                                    .build();
+
+        ObjectNode nodeJson = flowInfoCodec.encode(flowInfo, context);
+        assertThat(nodeJson, matchesFlowInfo(flowInfo));
+
+        FlowInfo flowInfoDecoded = flowInfoCodec.decode(nodeJson, context);
+        new EqualsTester().addEqualityGroup(flowInfo, flowInfoDecoded).testEquals();
+    }
+
+    /**
+     * Mock codec context for use in codec unit tests.
+     */
+    private class MockCodecContext implements CodecContext {
+        private final ObjectMapper mapper = new ObjectMapper();
+        private final CodecManager manager = new CodecManager();
+        private final Map<Class<?>, Object> services = new HashMap<>();
+
+        /**
+         * Constructs a new mock codec context.
+         */
+        public MockCodecContext() {
+            manager.activate();
+        }
+
+        @Override
+        public ObjectMapper mapper() {
+            return mapper;
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public <T> JsonCodec<T> codec(Class<T> entityClass) {
+            if (entityClass == FlowInfo.class) {
+                return (JsonCodec<T>) flowInfoCodec;
+            }
+            if (entityClass == StatsInfo.class) {
+                return (JsonCodec<T>) statsInfoCodec;
+            }
+            return manager.getCodec(entityClass);
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public <T> T getService(Class<T> serviceClass) {
+            return (T) services.get(serviceClass);
+        }
+
+        // for registering mock services
+        public <T> void registerService(Class<T> serviceClass, T impl) {
+            services.put(serviceClass, impl);
+        }
+    }
+}
diff --git a/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/codec/rest/FlowInfoJsonMatcher.java b/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/codec/rest/FlowInfoJsonMatcher.java
new file mode 100644
index 0000000..66ab1ba
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/codec/rest/FlowInfoJsonMatcher.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2018-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.openstacktelemetry.codec.rest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onosproject.openstacktelemetry.api.FlowInfo;
+import org.onosproject.openstacktelemetry.api.StatsInfo;
+
+/**
+ * Hamcrest matcher for flow info.
+ */
+public final class FlowInfoJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+    private final FlowInfo flowInfo;
+
+    private static final String FLOW_TYPE = "flowType";
+    private static final String DEVICE_ID = "deviceId";
+    private static final String INPUT_INTERFACE_ID = "inputInterfaceId";
+    private static final String OUTPUT_INTERFACE_ID = "outputInterfaceId";
+
+    private static final String VLAN_ID = "vlanId";
+    private static final String VXLAN_ID = "vxlanId";
+    private static final String SRC_IP = "srcIp";
+    private static final String SRC_IP_PREFIX_LEN = "srcIpPrefixLength";
+    private static final String DST_IP = "dstIp";
+    private static final String DST_IP_PREFIX_LEN = "dstIpPrefixLength";
+    private static final String SRC_PORT = "srcPort";
+    private static final String DST_PORT = "dstPort";
+    private static final String PROTOCOL = "protocol";
+    private static final String SRC_MAC = "srcMac";
+    private static final String DST_MAC = "dstMac";
+    private static final String STATS_INFO = "statsInfo";
+
+    private FlowInfoJsonMatcher(FlowInfo flowInfo) {
+        this.flowInfo = flowInfo;
+    }
+
+    @Override
+    protected boolean matchesSafely(JsonNode jsonNode, Description description) {
+
+        // check flow type
+        String jsonFlowType = jsonNode.get(FLOW_TYPE).asText();
+        String flowType = String.valueOf(flowInfo.flowType());
+        if (!jsonFlowType.equals(flowType)) {
+            description.appendText("flow type was " + jsonFlowType);
+            return false;
+        }
+
+        // check device id
+        String jsonDeviceId = jsonNode.get(DEVICE_ID).asText();
+        String deviceId = flowInfo.deviceId().toString();
+        if (!jsonDeviceId.equals(deviceId)) {
+            description.appendText("device id was " + jsonDeviceId);
+            return false;
+        }
+
+        // check input interface id
+        int jsonInputInterfaceId = jsonNode.get(INPUT_INTERFACE_ID).asInt();
+        int inputInterfaceId = flowInfo.inputInterfaceId();
+        if (jsonInputInterfaceId != inputInterfaceId) {
+            description.appendText("input interface id was " + jsonInputInterfaceId);
+            return false;
+        }
+
+        // check output interface id
+        int jsonOutputInterfaceId = jsonNode.get(OUTPUT_INTERFACE_ID).asInt();
+        int outputInterfaceId = flowInfo.outputInterfaceId();
+        if (jsonOutputInterfaceId != outputInterfaceId) {
+            description.appendText("output interface id was " + jsonInputInterfaceId);
+            return false;
+        }
+
+        // check vlan id
+        try {
+            if (!(jsonNode.get(VLAN_ID).isNull())) {
+                String jsonVlanId = jsonNode.get(VLAN_ID).asText();
+                String vlanId = flowInfo.vlanId().toString();
+                if (!jsonVlanId.equals(vlanId)) {
+                    description.appendText("VLAN id was " + jsonVlanId);
+                    return false;
+                }
+            }
+        } catch (NullPointerException ex) {
+            description.appendText("VLAN id was null");
+        }
+
+        // check source IP
+        String jsonSrcIp = jsonNode.get(SRC_IP).asText();
+        String srcIp = flowInfo.srcIp().address().toString();
+        if (!jsonSrcIp.equals(srcIp)) {
+            description.appendText("Source IP was " + jsonSrcIp);
+            return false;
+        }
+
+        // check destination IP
+        String jsonDstIp = jsonNode.get(DST_IP).asText();
+        String dstIp = flowInfo.dstIp().address().toString();
+        if (!jsonDstIp.equals(dstIp)) {
+            description.appendText("Destination IP was " + jsonDstIp);
+            return false;
+        }
+
+        // check source IP prefix length
+        int jsonSrcPrefixLength = jsonNode.get(SRC_IP_PREFIX_LEN).asInt();
+        int srcPrefixLength = flowInfo.srcIp().prefixLength();
+        if (jsonSrcPrefixLength != srcPrefixLength) {
+            description.appendText("Source IP prefix length was " + jsonSrcPrefixLength);
+            return false;
+        }
+
+        // check destination IP prefix length
+        int jsonDstPrefixLength = jsonNode.get(DST_IP_PREFIX_LEN).asInt();
+        int dstPrefixLength = flowInfo.dstIp().prefixLength();
+        if (jsonDstPrefixLength != dstPrefixLength) {
+            description.appendText("Destination IP prefix length was " + jsonDstPrefixLength);
+            return false;
+        }
+
+        // check source port
+        int jsonSrcPort = jsonNode.get(SRC_PORT).asInt();
+        int srcPort = flowInfo.srcPort().toInt();
+        if (jsonSrcPort != srcPort) {
+            description.appendText("Source port was " + jsonSrcPort);
+            return false;
+        }
+
+        // check destination port
+        int jsonDstPort = jsonNode.get(DST_PORT).asInt();
+        int dstPort = flowInfo.dstPort().toInt();
+        if (jsonDstPort != dstPort) {
+            description.appendText("Destination port was " + jsonDstPort);
+            return false;
+        }
+
+        // check protocol
+        String jsonProtocol = jsonNode.get(PROTOCOL).asText();
+        String protocol = String.valueOf(flowInfo.protocol());
+        if (!jsonProtocol.equals(protocol)) {
+            description.appendText("Protocol was " + jsonProtocol);
+            return false;
+        }
+
+        // check source mac
+        String jsonSrcMac = jsonNode.get(SRC_MAC).asText();
+        String srcMac = flowInfo.srcMac().toString();
+        if (!jsonSrcMac.equals(srcMac)) {
+            description.appendText("Source MAC was " + jsonSrcMac);
+            return false;
+        }
+
+        // check destination mac
+        String jsonDstMac = jsonNode.get(DST_MAC).asText();
+        String dstMac = flowInfo.dstMac().toString();
+        if (!jsonDstMac.equals(dstMac)) {
+            description.appendText("Destination MAC was " + jsonDstMac);
+            return false;
+        }
+
+        // check stats info
+        JsonNode jsonStatsInfo = jsonNode.get(STATS_INFO);
+        if (jsonStatsInfo != null) {
+            StatsInfo statsInfo = flowInfo.statsInfo();
+            StatsInfoJsonMatcher infoMatcher =
+                    StatsInfoJsonMatcher.matchStatsInfo(statsInfo);
+            return infoMatcher.matches(jsonStatsInfo);
+        }
+
+        return true;
+    }
+
+    @Override
+    public void describeTo(Description description) {
+        description.appendText(flowInfo.toString());
+    }
+
+    /**
+     * Factory to allocate an flow info matcher.
+     *
+     * @param flowInfo flow info object we are looking for
+     * @return matcher
+     */
+    public static FlowInfoJsonMatcher matchesFlowInfo(FlowInfo flowInfo) {
+        return new FlowInfoJsonMatcher(flowInfo);
+    }
+}
diff --git a/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/codec/rest/StatsInfoJsonMatcher.java b/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/codec/rest/StatsInfoJsonMatcher.java
new file mode 100644
index 0000000..4ad86fa
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/codec/rest/StatsInfoJsonMatcher.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2018-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.openstacktelemetry.codec.rest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onosproject.openstacktelemetry.api.StatsInfo;
+
+/**
+ * Hamcrest matcher for StatsInfoJsonCodec.
+ */
+public final class StatsInfoJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+    private final StatsInfo statsInfo;
+
+    private static final String STARTUP_TIME = "startupTime";
+    private static final String FST_PKT_ARR_TIME = "fstPktArrTime";
+    private static final String LST_PKT_OFFSET = "lstPktOffset";
+    private static final String PREV_ACC_BYTES = "prevAccBytes";
+    private static final String PREV_ACC_PKTS = "prevAccPkts";
+    private static final String CURR_ACC_BYTES = "currAccBytes";
+    private static final String CURR_ACC_PKTS = "currAccPkts";
+    private static final String ERROR_PKTS = "errorPkts";
+    private static final String DROP_PKTS = "dropPkts";
+
+    private StatsInfoJsonMatcher(StatsInfo statsInfo) {
+        this.statsInfo = statsInfo;
+    }
+
+    @Override
+    protected boolean matchesSafely(JsonNode jsonNode, Description description) {
+
+        // check startup time
+        long jsonStartupTime = jsonNode.get(STARTUP_TIME).asLong();
+        long startupTime = statsInfo.startupTime();
+        if (jsonStartupTime != startupTime) {
+            description.appendText("startup time was " + jsonStartupTime);
+            return false;
+        }
+
+        // check first packet arrival time
+        long jsonFstPktArrTime = jsonNode.get(FST_PKT_ARR_TIME).asLong();
+        long fstPktArrTime = statsInfo.fstPktArrTime();
+        if (jsonFstPktArrTime != fstPktArrTime) {
+            description.appendText("first packet arrival time was " + jsonFstPktArrTime);
+            return false;
+        }
+
+        // check last packet offset
+        int jsonLstPktOffset = jsonNode.get(LST_PKT_OFFSET).asInt();
+        int lstPktOffset = statsInfo.lstPktOffset();
+        if (jsonLstPktOffset != lstPktOffset) {
+            description.appendText("last packet offset was " + jsonLstPktOffset);
+            return false;
+        }
+
+        // check previous accumulated bytes
+        long jsonPrevAccBytes = jsonNode.get(PREV_ACC_BYTES).asLong();
+        long preAccBytes = statsInfo.prevAccBytes();
+        if (jsonPrevAccBytes != preAccBytes) {
+            description.appendText("previous accumulated bytes was " + jsonPrevAccBytes);
+            return false;
+        }
+
+        // check previous accumulated packets
+        int jsonPreAccPkts = jsonNode.get(PREV_ACC_PKTS).asInt();
+        int preAccPkts = statsInfo.prevAccPkts();
+        if (jsonPreAccPkts != preAccPkts) {
+            description.appendText("previous accumulated packets was " + jsonPreAccPkts);
+            return false;
+        }
+
+        // check current accumulated bytes
+        long jsonCurrAccBytes = jsonNode.get(CURR_ACC_BYTES).asLong();
+        long currAccBytes = statsInfo.currAccBytes();
+        if (jsonCurrAccBytes != currAccBytes) {
+            description.appendText("current accumulated bytes was " + jsonCurrAccBytes);
+            return false;
+        }
+
+        // check current accumulated packets
+        int jsonCurrAccPkts = jsonNode.get(CURR_ACC_PKTS).asInt();
+        int currAccPkts = statsInfo.currAccPkts();
+        if (jsonCurrAccPkts != currAccPkts) {
+            description.appendText("current accumulated packets was " + jsonCurrAccPkts);
+            return false;
+        }
+
+        // check error packets
+        short jsonErrorPkts = (short) jsonNode.get(ERROR_PKTS).asInt();
+        short errorPkts = statsInfo.errorPkts();
+        if (jsonErrorPkts != errorPkts) {
+            description.appendText("error packets was " + jsonErrorPkts);
+            return false;
+        }
+
+        // check drop packets
+        short jsonDropPkts = (short) jsonNode.get(DROP_PKTS).asInt();
+        short dropPkts = statsInfo.dropPkts();
+        if (jsonDropPkts != dropPkts) {
+            description.appendText("drop packets was " + jsonDropPkts);
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void describeTo(Description description) {
+        description.appendText(statsInfo.toString());
+    }
+
+    /**
+     * Factory to allocate a stats info matcher.
+     *
+     * @param statsInfo stats info object we are looking for
+     * @return matcher
+     */
+    public static StatsInfoJsonMatcher matchStatsInfo(StatsInfo statsInfo) {
+        return new StatsInfoJsonMatcher(statsInfo);
+    }
+}
diff --git a/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/codec/rest/TelemetryConfigCodecTest.java b/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/codec/rest/TelemetryConfigCodecTest.java
new file mode 100644
index 0000000..626aa02
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/codec/rest/TelemetryConfigCodecTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2018-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.openstacktelemetry.codec.rest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import org.hamcrest.MatcherAssert;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.codec.impl.CodecManager;
+import org.onosproject.core.CoreService;
+import org.onosproject.openstacktelemetry.api.DefaultTelemetryConfig;
+import org.onosproject.openstacktelemetry.api.config.TelemetryConfig;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertTrue;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.openstacktelemetry.codec.rest.TelemetryConfigJsonMatcher.matchesTelemetryConfig;
+
+/**
+ * Unit tests for TelemetryConfig codec.
+ */
+public class TelemetryConfigCodecTest {
+
+    MockCodecContext context;
+
+    JsonCodec<TelemetryConfig> telemetryConfigCodec;
+
+    final CoreService mockCoreService = createMock(CoreService.class);
+    private static final String REST_APP_ID = "org.onosproject.rest";
+
+    @Before
+    public void setUp() {
+        context = new MockCodecContext();
+        telemetryConfigCodec = new TelemetryConfigJsonCodec();
+
+        assertThat(telemetryConfigCodec, notNullValue());
+
+        expect(mockCoreService.registerApplication(REST_APP_ID))
+                .andReturn(APP_ID).anyTimes();
+        replay(mockCoreService);
+        context.registerService(CoreService.class, mockCoreService);
+    }
+
+    /**
+     * Tests the telemetry config encoding.
+     */
+    @Test
+    public void testTelemetryConfigEncode() {
+
+        String name = "grpc";
+        TelemetryConfig.ConfigType type = TelemetryConfig.ConfigType.GRPC;
+        String manufacturer = "grpc.io";
+        String swVersion = "1.0";
+        boolean enabled = true;
+
+        Map<String, String> properties = Maps.newConcurrentMap();
+        properties.put("key1", "value1");
+        properties.put("key2", "value2");
+
+        TelemetryConfig config = new DefaultTelemetryConfig(name, type,
+                ImmutableList.of(), manufacturer, swVersion, enabled, properties);
+
+        ObjectNode configJson = telemetryConfigCodec.encode(config, context);
+        assertThat(configJson, matchesTelemetryConfig(config));
+    }
+
+    /**
+     * Tests the telemetry config decoding.
+     */
+    @Test
+    public void testTelemetryConfigDecode() throws IOException {
+        TelemetryConfig config = getTelemetryConfig("TelemetryConfig.json");
+
+        assertEquals(config.name(), "grpc-config");
+        assertEquals(config.type().name(), "GRPC");
+        assertEquals(config.manufacturer(), "grpc.io");
+        assertEquals(config.swVersion(), "1.0");
+        assertTrue(config.enabled());
+
+        config.properties().forEach((k, v) -> {
+            if (k.equals("address")) {
+                assertEquals(v, "127.0.0.1");
+            }
+            if (k.equals("port")) {
+                assertEquals(v, "9092");
+            }
+        });
+    }
+
+    /**
+     * Reads in a telemetry config from the given resource and decodes it.
+     *
+     * @param resourceName resource to use to read the JSON for the rule
+     * @return decoded telemetry config
+     * @throws IOException if processing the resource fails
+     */
+    private TelemetryConfig getTelemetryConfig(String resourceName) throws IOException {
+        InputStream jsonStream = TelemetryConfigCodecTest.class.getResourceAsStream(resourceName);
+        JsonNode json = context.mapper().readTree(jsonStream);
+        MatcherAssert.assertThat(json, notNullValue());
+        TelemetryConfig config = telemetryConfigCodec.decode((ObjectNode) json, context);
+        assertThat(config, notNullValue());
+        return config;
+    }
+
+    /**
+     * Mock codec context for use in codec unit tests.
+     */
+    private class MockCodecContext implements CodecContext {
+        private final ObjectMapper mapper = new ObjectMapper();
+        private final CodecManager manager = new CodecManager();
+        private final Map<Class<?>, Object> services = new HashMap<>();
+
+        /**
+         * Constructs a new mock codec context.
+         */
+        public MockCodecContext() {
+            manager.activate();
+        }
+
+        @Override
+        public ObjectMapper mapper() {
+            return mapper;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public <T> JsonCodec<T> codec(Class<T> entityClass) {
+            if (entityClass == TelemetryConfig.class) {
+                return (JsonCodec<T>) telemetryConfigCodec;
+            }
+            return manager.getCodec(entityClass);
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public <T> T getService(Class<T> serviceClass) {
+            return (T) services.get(serviceClass);
+        }
+
+        // for registering mock services
+        public <T> void registerService(Class<T> serviceClass, T impl) {
+            services.put(serviceClass, impl);
+        }
+    }
+}
diff --git a/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/codec/rest/TelemetryConfigJsonMatcher.java b/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/codec/rest/TelemetryConfigJsonMatcher.java
new file mode 100644
index 0000000..9bc50a2
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/codec/rest/TelemetryConfigJsonMatcher.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2018-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.openstacktelemetry.codec.rest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onosproject.openstacktelemetry.api.config.TelemetryConfig;
+
+/**
+ * Hamcrest matcher for TelemetryConfig.
+ */
+public final class TelemetryConfigJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+    private final TelemetryConfig telemetryConfig;
+
+    private static final String NAME = "name";
+    private static final String TYPE = "type";
+    private static final String MANUFACTURER = "manufacturer";
+    private static final String SW_VERSION = "swVersion";
+    private static final String ENABLED = "enabled";
+    private static final String PROPS = "props";
+    private static final String KEY = "key";
+    private static final String VALUE = "value";
+
+    private TelemetryConfigJsonMatcher(TelemetryConfig telemetryConfig) {
+        this.telemetryConfig = telemetryConfig;
+    }
+
+    @Override
+    protected boolean matchesSafely(JsonNode jsonNode, Description description) {
+
+        // check name
+        String jsonName = jsonNode.get(NAME).asText();
+        String name = telemetryConfig.name();
+        if (!jsonName.equals(name)) {
+            description.appendText("name was " + jsonName);
+            return false;
+        }
+
+        // check type
+        String jsonType = jsonNode.get(TYPE).asText();
+        String type = telemetryConfig.type().name();
+        if (!jsonType.equalsIgnoreCase(type)) {
+            description.appendText("type was " + jsonType);
+            return false;
+        }
+
+        // check manufacturer
+        String jsonManufacturer = jsonNode.get(MANUFACTURER).asText();
+        String manufacturer = telemetryConfig.manufacturer();
+        if (!jsonManufacturer.equals(manufacturer)) {
+            description.appendText("manufacturer was " + jsonManufacturer);
+            return false;
+        }
+
+        // check software version
+        String jsonSwVersion = jsonNode.get(SW_VERSION).asText();
+        String swVersion = telemetryConfig.swVersion();
+        if (!jsonSwVersion.equals(swVersion)) {
+            description.appendText("SW version was " + jsonSwVersion);
+            return false;
+        }
+
+        // check enabled
+        JsonNode jsonEnabled = jsonNode.get(ENABLED);
+        boolean enabled = telemetryConfig.enabled();
+        if (jsonEnabled == null || jsonEnabled.asBoolean() != enabled) {
+            description.appendText("Enabled was " + jsonEnabled);
+            return false;
+        }
+
+        // check properties
+        JsonNode jsonProperties = jsonNode.get(PROPS);
+        if (jsonProperties != null) {
+            if (jsonProperties.size() != telemetryConfig.properties().size()) {
+                description.appendText("properties size was " + jsonProperties.size());
+                return false;
+            }
+
+            for (String key : telemetryConfig.properties().keySet()) {
+                boolean keyFound = false;
+                boolean valueFound = false;
+                String value = telemetryConfig.properties().get(key);
+                for (int keyIndex = 0; keyIndex < jsonProperties.size(); keyIndex++) {
+                    ObjectNode jsonProperty = get(jsonProperties, keyIndex);
+                    JsonNode jsonKey = jsonProperty.get(KEY);
+                    JsonNode jsonValue = jsonProperty.get(VALUE);
+
+                    if (jsonKey != null && jsonValue != null) {
+                        if (jsonKey.asText().equals(key)) {
+                            keyFound = true;
+                        }
+
+                        if (jsonValue.asText().equals(value)) {
+                            valueFound = true;
+                        }
+
+                        if (keyFound && valueFound) {
+                            break;
+                        }
+                    }
+                }
+
+                if (!keyFound) {
+                    description.appendText("Property key not found " + key);
+                    return false;
+                }
+
+                if (!valueFound) {
+                    description.appendText("Property value not found " + value);
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public void describeTo(Description description) {
+        description.appendText(telemetryConfig.toString());
+    }
+
+    /**
+     * Factory to allocate an flow info matcher.
+     *
+     * @param telemetryConfig telemetry config object we are looking for
+     * @return matcher
+     */
+    public static TelemetryConfigJsonMatcher
+                    matchesTelemetryConfig(TelemetryConfig telemetryConfig) {
+        return new TelemetryConfigJsonMatcher(telemetryConfig);
+    }
+
+    private static ObjectNode get(JsonNode parent, int childIndex) {
+        JsonNode node = parent.path(childIndex);
+        return node.isObject() && !node.isNull() ? (ObjectNode) node : null;
+    }
+}
diff --git a/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryCodecRegisterTest.java b/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryCodecRegisterTest.java
new file mode 100644
index 0000000..6eee616
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryCodecRegisterTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2018-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.openstacktelemetry.web;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onosproject.codec.CodecService;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.openstacktelemetry.api.FlowInfo;
+import org.onosproject.openstacktelemetry.api.StatsFlowRule;
+import org.onosproject.openstacktelemetry.api.StatsInfo;
+import org.onosproject.openstacktelemetry.api.config.TelemetryConfig;
+import org.onosproject.openstacktelemetry.codec.rest.FlowInfoJsonCodec;
+import org.onosproject.openstacktelemetry.codec.rest.StatsFlowRuleJsonCodec;
+import org.onosproject.openstacktelemetry.codec.rest.StatsInfoJsonCodec;
+import org.onosproject.openstacktelemetry.codec.rest.TelemetryConfigJsonCodec;
+
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Unit test for openstack telemetry codec register.
+ */
+public final class OpenstackTelemetryCodecRegisterTest {
+
+    private OpenstackTelemetryCodecRegister register;
+
+    /**
+     * Tests codec register activation and deactivation.
+     */
+    @Test
+    public void testActivateDeactivate() {
+        register = new OpenstackTelemetryCodecRegister();
+        CodecService codecService = new TestCodecService();
+
+        TestUtils.setField(register, "codecService", codecService);
+        register.activate();
+
+        assertEquals(StatsInfoJsonCodec.class.getName(),
+                codecService.getCodec(StatsInfo.class).getClass().getName());
+        assertEquals(FlowInfoJsonCodec.class.getName(),
+                codecService.getCodec(FlowInfo.class).getClass().getName());
+        assertEquals(StatsFlowRuleJsonCodec.class.getName(),
+                codecService.getCodec(StatsFlowRule.class).getClass().getName());
+        assertEquals(TelemetryConfigJsonCodec.class.getName(),
+                codecService.getCodec(TelemetryConfig.class).getClass().getName());
+
+        register.deactivate();
+
+        assertNull(codecService.getCodec(StatsInfo.class));
+        assertNull(codecService.getCodec(FlowInfo.class));
+        assertNull(codecService.getCodec(StatsFlowRule.class));
+        assertNull(codecService.getCodec(TelemetryConfig.class));
+    }
+
+    private static class TestCodecService implements CodecService {
+
+        private Map<String, JsonCodec> codecMap = Maps.newConcurrentMap();
+
+        @Override
+        public Set<Class<?>> getCodecs() {
+            return ImmutableSet.of();
+        }
+
+        @Override
+        public <T> JsonCodec<T> getCodec(Class<T> entityClass) {
+            return codecMap.get(entityClass.getName());
+        }
+
+        @Override
+        public <T> void registerCodec(Class<T> entityClass, JsonCodec<T> codec) {
+            codecMap.put(entityClass.getName(), codec);
+        }
+
+        @Override
+        public void unregisterCodec(Class<?> entityClass) {
+            codecMap.remove(entityClass.getName());
+        }
+    }
+}
diff --git a/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryConfigWebResourceTest.java b/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryConfigWebResourceTest.java
new file mode 100644
index 0000000..ff027bc
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/test/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryConfigWebResourceTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2018-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.openstacktelemetry.web;
+
+import com.google.common.collect.Maps;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.openstacktelemetry.api.DefaultTelemetryConfig;
+import org.onosproject.openstacktelemetry.api.TelemetryConfigAdminService;
+import org.onosproject.openstacktelemetry.api.config.TelemetryConfig;
+import org.onosproject.rest.resources.ResourceTest;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.Map;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.onosproject.openstacktelemetry.api.config.TelemetryConfig.ConfigType.GRPC;
+
+/**
+ * Unit tests for openstack telemetry config REST API.
+ */
+public class OpenstackTelemetryConfigWebResourceTest extends ResourceTest {
+
+    private static final String NAME = "grpc";
+
+    private static final TelemetryConfig.ConfigType TYPE = GRPC;
+
+    private static final String MANUFACTURER = "grpc.io";
+
+    private static final String SW_VERSION = "1.0";
+
+    private static final Map<String, String> PROP = Maps.newConcurrentMap();
+
+    private static final String PROP_KEY_1 = "key11";
+    private static final String PROP_KEY_2 = "key12";
+    private static final String PROP_VALUE_1 = "value11";
+    private static final String PROP_VALUE_2 = "value12";
+
+    private static final boolean ENABLED = true;
+
+    private final TelemetryConfigAdminService mockConfigAdminService =
+            createMock(TelemetryConfigAdminService.class);
+    private static final String PATH = "config";
+
+    /**
+     * Constructs an openstack telemetry config resource test instance.
+     */
+    public OpenstackTelemetryConfigWebResourceTest() {
+        super(ResourceConfig.forApplicationClass(OpenstackTelemetryWebApplication.class));
+    }
+
+    private TelemetryConfig telemetryConfig;
+
+    /**
+     * Sets up the global values for all the tests.
+     */
+    @Before
+    public void setUpTest() {
+        ServiceDirectory testDirectory =
+                new TestServiceDirectory()
+                        .add(TelemetryConfigAdminService.class,
+                                mockConfigAdminService);
+        setServiceDirectory(testDirectory);
+
+        PROP.put(PROP_KEY_1, PROP_VALUE_1);
+        PROP.put(PROP_KEY_2, PROP_VALUE_2);
+
+        telemetryConfig = new DefaultTelemetryConfig(NAME, TYPE, null,
+                MANUFACTURER, SW_VERSION, ENABLED, PROP);
+    }
+
+    /**
+     * Tests the results of the REST API PUT method by modifying config address.
+     */
+    @Test
+    public void testUpdateConfigAddressWithModifyOperation() {
+        expect(mockConfigAdminService.getConfig(anyString()))
+                .andReturn(telemetryConfig).once();
+        mockConfigAdminService.updateTelemetryConfig(telemetryConfig);
+        replay(mockConfigAdminService);
+
+        final WebTarget wt = target();
+        Response response = wt.path(PATH + "/address/test1/address1")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .put(Entity.json(""));
+        final int status = response.getStatus();
+
+        assertEquals(200, status);
+
+        verify(mockConfigAdminService);
+    }
+
+    /**
+     * Tests the results of the REST API PUT method without modifying config address.
+     */
+    @Test
+    public void testUpdateConfigAddressWithoutOperation() {
+        expect(mockConfigAdminService.getConfig(anyString())).andReturn(null).once();
+        replay(mockConfigAdminService);
+
+        final WebTarget wt = target();
+        Response response = wt.path(PATH + "/address/test1/address1")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .put(Entity.json(""));
+        final int status = response.getStatus();
+
+        assertEquals(304, status);
+
+        verify(mockConfigAdminService);
+    }
+
+    /**
+     * Tests the results of the REST API DELETE method by removing config.
+     */
+    @Test
+    public void testDeleteConfigWithModifyOperation() {
+        expect(mockConfigAdminService.getConfig(anyString()))
+                .andReturn(telemetryConfig).once();
+        mockConfigAdminService.removeTelemetryConfig(anyString());
+        replay(mockConfigAdminService);
+
+        final WebTarget wt = target();
+        Response response = wt.path(PATH + "/test1")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .delete();
+        final int status = response.getStatus();
+
+        assertEquals(204, status);
+
+        verify(mockConfigAdminService);
+    }
+
+    /**
+     * Tests the results of the REST API DELETE method without removing config.
+     */
+    @Test
+    public void testDeleteConfigWithoutModifyOperation() {
+        expect(mockConfigAdminService.getConfig(anyString())).andReturn(null).once();
+        replay(mockConfigAdminService);
+
+        final WebTarget wt = target();
+        Response response = wt.path(PATH + "/test1")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .delete();
+        final int status = response.getStatus();
+
+        assertEquals(304, status);
+
+        verify(mockConfigAdminService);
+    }
+
+    /**
+     * Tests the results of REST API PUT method with enabling the config.
+     */
+    @Test
+    public void testEnableConfig() {
+        expect(mockConfigAdminService.getConfig(anyString()))
+                .andReturn(telemetryConfig).once();
+        mockConfigAdminService.updateTelemetryConfig(telemetryConfig);
+        replay(mockConfigAdminService);
+
+        final WebTarget wt = target();
+        Response response = wt.path(PATH + "/enable/test1")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .put(Entity.json(""));
+        final int status = response.getStatus();
+
+        assertEquals(200, status);
+
+        verify(mockConfigAdminService);
+    }
+
+    /**
+     * Tests the results of REST API PUT method with disabling the config.
+     */
+    @Test
+    public void testDisableConfig() {
+        expect(mockConfigAdminService.getConfig(anyString()))
+                .andReturn(telemetryConfig).once();
+        mockConfigAdminService.updateTelemetryConfig(telemetryConfig);
+        replay(mockConfigAdminService);
+
+        final WebTarget wt = target();
+        Response response = wt.path(PATH + "/disable/test1")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .put(Entity.json(""));
+        final int status = response.getStatus();
+
+        assertEquals(200, status);
+
+        verify(mockConfigAdminService);
+    }
+}
diff --git a/apps/openstacktelemetry/web/src/test/resources/org/onosproject/openstacktelemetry/codec/rest/TelemetryConfig.json b/apps/openstacktelemetry/web/src/test/resources/org/onosproject/openstacktelemetry/codec/rest/TelemetryConfig.json
new file mode 100644
index 0000000..681cad3
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/test/resources/org/onosproject/openstacktelemetry/codec/rest/TelemetryConfig.json
@@ -0,0 +1,17 @@
+{
+  "name": "grpc-config",
+  "type": "GRPC",
+  "manufacturer": "grpc.io",
+  "swVersion": "1.0",
+  "enabled": true,
+  "props": [
+    {
+      "key": "address",
+      "value": "127.0.0.1"
+    },
+    {
+      "key": "port",
+      "value": "9092"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/apps/openstacktelemetry/web/src/test/resources/org/onosproject/openstacktelemetry/web/openstack-telemetry-config-address.json b/apps/openstacktelemetry/web/src/test/resources/org/onosproject/openstacktelemetry/web/openstack-telemetry-config-address.json
new file mode 100644
index 0000000..a4132c5
--- /dev/null
+++ b/apps/openstacktelemetry/web/src/test/resources/org/onosproject/openstacktelemetry/web/openstack-telemetry-config-address.json
@@ -0,0 +1,5 @@
+{
+  "config": {
+    "address": "10.10.10.10"
+  }
+}
\ No newline at end of file