Implementation for ONOS-5327. Creating rest api for finding intent statistics.

Change-Id: Ic7facfe1507f8ce8507df0940d2c0bb8b62781be
(cherry picked from commit 5dd3d174e477528a05b0ead429bb629f46e4eb44)
diff --git a/web/api/src/test/java/org/onosproject/rest/resources/IntentsResourceTest.java b/web/api/src/test/java/org/onosproject/rest/resources/IntentsResourceTest.java
index e87b94a..a42855b 100644
--- a/web/api/src/test/java/org/onosproject/rest/resources/IntentsResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/resources/IntentsResourceTest.java
@@ -15,17 +15,10 @@
  */
 package org.onosproject.rest.resources;
 
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.util.Collections;
-import java.util.HashSet;
-
-import javax.ws.rs.NotFoundException;
-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 com.eclipsesource.json.Json;
+import com.eclipsesource.json.JsonArray;
+import com.eclipsesource.json.JsonObject;
+import com.eclipsesource.json.JsonValue;
 import org.hamcrest.Description;
 import org.hamcrest.Matchers;
 import org.hamcrest.TypeSafeMatcher;
@@ -34,35 +27,52 @@
 import org.junit.Test;
 import org.onlab.osgi.ServiceDirectory;
 import org.onlab.osgi.TestServiceDirectory;
+import org.onlab.packet.MacAddress;
 import org.onlab.rest.BaseResource;
 import org.onosproject.codec.CodecService;
 import org.onosproject.codec.impl.CodecManager;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.core.GroupId;
 import org.onosproject.core.IdGenerator;
+import org.onosproject.net.DeviceId;
 import org.onosproject.net.NetworkResource;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowId;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleExtPayLoad;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.instructions.Instruction;
 import org.onosproject.net.intent.FakeIntentManager;
+import org.onosproject.net.intent.FlowRuleIntent;
 import org.onosproject.net.intent.Intent;
 import org.onosproject.net.intent.IntentService;
 import org.onosproject.net.intent.IntentState;
 import org.onosproject.net.intent.Key;
 
-import com.eclipsesource.json.Json;
-import com.eclipsesource.json.JsonArray;
-import com.eclipsesource.json.JsonObject;
-import com.eclipsesource.json.JsonValue;
+import javax.ws.rs.NotFoundException;
+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.io.InputStream;
+import java.net.HttpURLConnection;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
 
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.verify;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.hasSize;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.notNullValue;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.easymock.EasyMock.*;
+import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
 import static org.onosproject.net.intent.IntentTestsMocks.MockIntent;
@@ -72,12 +82,51 @@
  * Unit tests for Intents REST APIs.
  */
 public class IntentsResourceTest extends ResourceTest {
+
+
+    private static final String APPID = "appId";
+    private static final String CRITERIA = "criteria";
+    private static final String DEVICE_ID = "deviceId";
+    private static final String ID = "id";
+    private static final String INSTRUCTIONS = "instructions";
+    private static final String PATHS = "paths";
+    private static final String SELECTOR = "selector";
+    private static final String SPACE = " ";
+    private static final String TREATMENT = "treatment";
+    private static final String TYPE = "type";
+
     final IntentService mockIntentService = createMock(IntentService.class);
     final CoreService mockCoreService = createMock(CoreService.class);
+    final FlowRuleService mockFlowService = createMock(FlowRuleService.class);
     final HashSet<Intent> intents = new HashSet<>();
+    final List<org.onosproject.net.intent.Intent> installableIntents = new ArrayList<>();
     private static final ApplicationId APP_ID = new DefaultApplicationId(1, "test");
     private IdGenerator mockGenerator;
 
+    final DeviceId deviceId1 = DeviceId.deviceId("1");
+
+    final TrafficTreatment treatment1 = DefaultTrafficTreatment.builder()
+            .setEthDst(MacAddress.BROADCAST)
+            .build();
+    final TrafficTreatment treatment2 = DefaultTrafficTreatment.builder()
+            .setEthDst(MacAddress.IPV4_MULTICAST)
+            .build();
+
+    final TrafficSelector selector1 = DefaultTrafficSelector.builder()
+            .matchEthType((short) 3)
+            .matchIPProtocol((byte) 9)
+            .build();
+    final TrafficSelector selector2 = DefaultTrafficSelector.builder()
+            .matchEthType((short) 4)
+            .matchIPProtocol((byte) 10)
+            .build();
+
+    final MockFlowEntry flow1 = new MockFlowEntry(deviceId1, 1, treatment1, selector1);
+    final MockFlowEntry flow2 = new MockFlowEntry(deviceId1, 2, treatment2, selector2);
+
+    final MockFlowRule flowRule1 = new MockFlowRule(deviceId1, 1, treatment1, selector1);
+    final MockFlowRule flowRule2 = new MockFlowRule(deviceId1, 2, treatment2, selector2);
+
     private class MockResource implements NetworkResource {
         int id;
 
@@ -92,6 +141,237 @@
     }
 
     /**
+     * Mock class for a flow entry.
+     */
+    private static class MockFlowEntry implements FlowEntry {
+        final DeviceId deviceId;
+        final long baseValue;
+        TrafficTreatment treatment;
+        TrafficSelector selector;
+
+        public MockFlowEntry(DeviceId deviceId, long id,
+                             TrafficTreatment treatment,
+                             TrafficSelector selector) {
+            this.deviceId = deviceId;
+            this.baseValue = id * 100;
+            this.treatment = treatment;
+            this.selector = selector;
+        }
+
+        @Override
+        public FlowEntryState state() {
+            return FlowEntryState.ADDED;
+        }
+
+        @Override
+        public long life() {
+            return life(SECONDS);
+        }
+
+        @Override
+        public long life(TimeUnit timeUnit) {
+            return SECONDS.convert(baseValue + 11, timeUnit);
+        }
+
+        @Override
+        public long packets() {
+            return baseValue + 22;
+        }
+
+        @Override
+        public long bytes() {
+            return baseValue + 33;
+        }
+
+        @Override
+        public long lastSeen() {
+            return baseValue + 44;
+        }
+
+        @Override
+        public int errType() {
+            return 0;
+        }
+
+        @Override
+        public int errCode() {
+            return 0;
+        }
+
+        @Override
+        public FlowId id() {
+            final long id = baseValue + 55;
+            return FlowId.valueOf(id);
+        }
+
+        @Override
+        public GroupId groupId() {
+            return new DefaultGroupId(3);
+        }
+
+        @Override
+        public short appId() {
+            return 1;
+        }
+
+        @Override
+        public int priority() {
+            return (int) (baseValue + 66);
+        }
+
+        @Override
+        public DeviceId deviceId() {
+            return deviceId;
+        }
+
+        @Override
+        public TrafficSelector selector() {
+            return selector;
+        }
+
+        @Override
+        public TrafficTreatment treatment() {
+            return treatment;
+        }
+
+        @Override
+        public int timeout() {
+            return (int) (baseValue + 77);
+        }
+
+        @Override
+        public int hardTimeout() {
+            return 0;
+        }
+
+        @Override
+        public FlowRule.FlowRemoveReason reason() {
+            return  FlowRule.FlowRemoveReason.NO_REASON;
+        }
+
+        @Override
+        public boolean isPermanent() {
+            return false;
+        }
+
+        @Override
+        public int tableId() {
+            return 0;
+        }
+
+        @Override
+        public boolean exactMatch(FlowRule rule) {
+            return this.appId() == rule.appId() &&
+                    this.deviceId().equals(rule.deviceId()) &&
+                    this.id().equals(rule.id()) &&
+                    this.treatment.equals(rule.treatment()) &&
+                    this.selector().equals(rule.selector());
+        }
+
+        @Override
+        public FlowRuleExtPayLoad payLoad() {
+            return null;
+        }
+
+        @Override
+        public String toString() {
+            return id().id().toString();
+        }
+    }
+
+    /**
+     * Mock class for a flow rule.
+     */
+    private static class MockFlowRule implements FlowRule {
+
+        final DeviceId deviceId;
+        final long baseValue;
+        TrafficTreatment treatment;
+        TrafficSelector selector;
+
+        public MockFlowRule(DeviceId deviceId,
+                            long id,
+                            TrafficTreatment treatment,
+                            TrafficSelector selector) {
+            this.deviceId = deviceId;
+            this.baseValue = id * 100;
+            this.treatment = treatment;
+            this.selector = selector;
+        }
+
+        @Override
+        public FlowId id() {
+            long id = baseValue + 55;
+            return FlowId.valueOf(id);
+        }
+
+        @Override
+        public short appId() {
+            return 1;
+        }
+
+        @Override
+        public GroupId groupId() {
+            return new DefaultGroupId(3);
+        }
+
+        @Override
+        public int priority() {
+            return 0;
+        }
+
+        @Override
+        public DeviceId deviceId() {
+            return deviceId;
+        }
+
+        @Override
+        public TrafficSelector selector() {
+            return selector;
+        }
+
+        @Override
+        public TrafficTreatment treatment() {
+            return treatment;
+        }
+
+        @Override
+        public int timeout() {
+            return (int) (baseValue + 77);
+        }
+
+        @Override
+        public int hardTimeout() {
+            return 0;
+        }
+
+        @Override
+        public FlowRemoveReason reason() {
+            return FlowRemoveReason.NO_REASON;
+        }
+
+        @Override
+        public boolean isPermanent() {
+            return false;
+        }
+
+        @Override
+        public int tableId() {
+            return 0;
+        }
+
+        @Override
+        public boolean exactMatch(FlowRule rule) {
+            return false;
+        }
+
+        @Override
+        public FlowRuleExtPayLoad payLoad() {
+            return null;
+        }
+    }
+
+    /**
      * Hamcrest matcher to check that an intent representation in JSON matches
      * the actual intent.
      */
@@ -180,6 +460,195 @@
     }
 
     /**
+     * Factory to allocate an IntentRelatedFlows matcher.
+     *
+     * @param pathEntries list of path conatining flow entries of a particular intent
+     * @param expectedAppId expected app id we are looking for
+     * @return matcher
+     */
+    private static IntentStatsJsonMatcher matchesRelatedFlowEntries(
+            List<List<FlowEntry>> pathEntries,
+            final String expectedAppId) {
+        return new IntentStatsJsonMatcher(pathEntries, expectedAppId);
+    }
+
+    /**
+     * Hamcrest matcher to check that an list of flowEntries in JSON matches
+     * the actual list of flow entries.
+     */
+    public static class IntentStatsJsonMatcher extends
+            TypeSafeMatcher<JsonObject> {
+
+        private final List<List<FlowEntry>> pathEntries;
+        private final String expectedAppId;
+        private String reason = "";
+
+        public IntentStatsJsonMatcher(
+                final List<List<FlowEntry>> pathEntries,
+                final String expectedAppId) {
+            this.pathEntries = pathEntries;
+            this.expectedAppId = expectedAppId;
+        }
+
+        @Override
+        public boolean matchesSafely(JsonObject jsonIntent) {
+            int jsonPathIndex = 0;
+            JsonArray jsonPaths = jsonIntent.get(PATHS).asArray();
+
+            if (pathEntries != null) {
+
+                if (pathEntries.size() != jsonPaths.size()) {
+                    reason = "path entries array size of " +
+                            Integer.toString(pathEntries.size());
+                    return false;
+                }
+
+                for (List<FlowEntry> flowEntries : pathEntries) {
+                    JsonArray jsonFlowEntries = jsonPaths.get(
+                            jsonPathIndex++).asArray();
+
+                    if (flowEntries.size() != jsonFlowEntries.size()) {
+                        reason = "flow entries array size of " +
+                                Integer.toString(pathEntries.size());
+
+                        return false;
+                    }
+
+                    int jsonFlowEntryIndex = 0;
+                    for (FlowEntry flow : flowEntries) {
+
+                        JsonObject jsonFlow = jsonFlowEntries.get(
+                                jsonFlowEntryIndex++).asObject();
+
+                        String jsonId = jsonFlow.get(ID).asString();
+                        String flowId = Long.toString(flow.id().value());
+                        if (!jsonId.equals(flowId)) {
+                            reason = ID + SPACE + flow.id();
+                            return false;
+                        }
+
+                        // check application id
+                        String jsonAppId = jsonFlow.get(APPID).asString();
+                        if (!jsonAppId.equals(expectedAppId)) {
+                            reason = APPID + SPACE + Short.toString(flow.appId());
+                            return false;
+                        }
+
+                        // check device id
+                        String jsonDeviceId =
+                                jsonFlow.get(DEVICE_ID).asString();
+
+                        if (!jsonDeviceId.equals(flow.deviceId().toString())) {
+                            reason = DEVICE_ID + SPACE + flow.deviceId();
+                            return false;
+                        }
+
+                        if (!checkFlowTreatment(flow, jsonFlow)) {
+                            return false;
+                        }
+
+                        if (!checkFlowSelector(flow, jsonFlow)) {
+                            return false;
+                        }
+
+                    }
+
+                }
+            } else if (pathEntries.size() != 0) {
+                reason = "pathEntries array empty";
+                return false;
+            }
+
+            return true;
+        }
+
+        // check treatment and instructions array.
+        private boolean checkFlowTreatment(FlowEntry flow, JsonObject jsonFlow) {
+
+            if (flow.treatment() != null) {
+                JsonObject jsonTreatment =
+                        jsonFlow.get(TREATMENT).asObject();
+                JsonArray jsonInstructions =
+                        jsonTreatment.get(INSTRUCTIONS).asArray();
+
+                if (flow.treatment().immediate().size() !=
+                        jsonInstructions.size()) {
+                    reason = "instructions array size of " +
+                            flow.treatment().immediate().size();
+
+                    return false;
+                }
+                for (Instruction instruction :
+                        flow.treatment().immediate()) {
+                    boolean instructionFound = false;
+                    for (int instructionIndex = 0;
+                         instructionIndex < jsonInstructions.size();
+                         instructionIndex++) {
+                        String jsonType =
+                                jsonInstructions.get(instructionIndex)
+                                        .asObject().get(TYPE).asString();
+
+                        String instructionType =
+                                instruction.type().name();
+
+                        if (jsonType.equals(instructionType)) {
+                            instructionFound = true;
+                        }
+                    }
+                    if (!instructionFound) {
+                        reason = INSTRUCTIONS + SPACE + instruction;
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+
+        // check selector and criteria array.
+        private boolean checkFlowSelector(FlowEntry flow, JsonObject jsonFlow) {
+
+            if (flow.selector() != null) {
+                JsonObject jsonTreatment =
+                        jsonFlow.get(SELECTOR).asObject();
+
+                JsonArray jsonCriteria =
+                        jsonTreatment.get(CRITERIA).asArray();
+
+                if (flow.selector().criteria().size() != jsonCriteria.size()) {
+                    reason = CRITERIA + " array size of " +
+                            Integer.toString(flow.selector().criteria().size());
+                    return false;
+                }
+                for (Criterion criterion : flow.selector().criteria()) {
+                    boolean criterionFound = false;
+
+                    for (int criterionIndex = 0;
+                         criterionIndex < jsonCriteria.size();
+                         criterionIndex++) {
+                        String jsonType =
+                                jsonCriteria.get(criterionIndex)
+                                        .asObject().get(TYPE).asString();
+                        String criterionType = criterion.type().name();
+                        if (jsonType.equals(criterionType)) {
+                            criterionFound = true;
+                        }
+                    }
+                    if (!criterionFound) {
+                        reason = "criterion " + criterion;
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText(reason);
+        }
+    }
+
+    /**
      * Hamcrest matcher to check that an intent is represented properly in a JSON
      * array of intents.
      */
@@ -252,6 +721,7 @@
         ServiceDirectory testDirectory =
                 new TestServiceDirectory()
                         .add(IntentService.class, mockIntentService)
+                        .add(FlowRuleService.class, mockFlowService)
                         .add(CodecService.class, codecService)
                         .add(CoreService.class, mockCoreService);
 
@@ -357,6 +827,69 @@
     }
 
     /**
+     * Tests the result of a rest api GET for related flows for single intent.
+     */
+    @Test
+    public void testRelatedFlowsForIntents() {
+        List<FlowEntry> flowEntries = new ArrayList<>();
+        flowEntries.add(flow1);
+        flowEntries.add(flow2);
+        List<List<FlowEntry>> paths = new ArrayList<>();
+        paths.add(flowEntries);
+        List<FlowRule> flowRules = new ArrayList<>();
+        flowRules.add(flowRule1);
+        flowRules.add(flowRule2);
+        FlowRuleIntent flowRuleIntent = new FlowRuleIntent(
+                APP_ID,
+                flowRules,
+                new HashSet<NetworkResource>());
+        Intent intent = new MockIntent(3L);
+        installableIntents.add(flowRuleIntent);
+        intents.add(intent);
+
+        expect(mockIntentService.getIntent(Key.of(0, APP_ID)))
+                .andReturn(intent)
+                .anyTimes();
+        expect(mockIntentService.getIntent(Key.of("0", APP_ID)))
+                .andReturn(intent)
+                .anyTimes();
+        expect(mockIntentService.getIntent(Key.of(0, APP_ID)))
+                .andReturn(intent)
+                .anyTimes();
+        expect(mockIntentService.getIntent(Key.of("0x0", APP_ID)))
+                .andReturn(null)
+                .anyTimes();
+        expect(mockIntentService.getInstallableIntents(intent.key()))
+                .andReturn(installableIntents)
+                .anyTimes();
+        replay(mockIntentService);
+
+        expect(mockFlowService.getFlowEntries(deviceId1))
+                .andReturn(flowEntries).anyTimes();
+        replay(mockFlowService);
+
+        expect(mockCoreService.getAppId(APP_ID.name()))
+                .andReturn(APP_ID).anyTimes();
+        expect(mockCoreService.getAppId(APP_ID.id()))
+                .andReturn(APP_ID).anyTimes();
+        replay(mockCoreService);
+
+        final WebTarget wt = target();
+
+        // Test get using key string
+        final String response = wt.path("intents/relatedflows/" + APP_ID.name()
+                + "/0").request().get(String.class);
+        final JsonObject result = Json.parse(response).asObject();
+        assertThat(result, matchesRelatedFlowEntries(paths, APP_ID.name()));
+
+        // Test get using numeric value
+        final String responseNumeric = wt.path("intents/relatedflows/" + APP_ID.name()
+                + "/0x0").request().get(String.class);
+        final JsonObject resultNumeric = Json.parse(responseNumeric).asObject();
+        assertThat(resultNumeric, matchesRelatedFlowEntries(paths, APP_ID.name()));
+    }
+
+    /**
      * Tests that a fetch of a non-existent intent object throws an exception.
      */
     @Test