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/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