Topology REST API modifications and unit tests

- added a Serializer for Topology operations
- added a REST test harness class for Topology tests
- added REST API tests for topology get of all
  topology, links, switches, and devices.

Change-Id: I13e3654aad7c0f5af41f07be66da82330933d3a3
diff --git a/src/main/java/net/onrc/onos/core/topology/Topology.java b/src/main/java/net/onrc/onos/core/topology/Topology.java
index 3b90f89..844507d 100644
--- a/src/main/java/net/onrc/onos/core/topology/Topology.java
+++ b/src/main/java/net/onrc/onos/core/topology/Topology.java
@@ -1,12 +1,15 @@
 package net.onrc.onos.core.topology;
 
 import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.core.topology.serializers.TopologySerializer;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
 
 /**
  * The northbound interface to the topology. This interface
  * is presented to the rest of ONOS. It is currently read-only, as we want
  * only the discovery modules to be allowed to modify the topology.
  */
+@JsonSerialize(using = TopologySerializer.class)
 public interface Topology {
     /**
      * Get the switch for a given switch DPID.
diff --git a/src/main/java/net/onrc/onos/core/topology/serializers/TopologySerializer.java b/src/main/java/net/onrc/onos/core/topology/serializers/TopologySerializer.java
new file mode 100644
index 0000000..1bc4ff2
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/topology/serializers/TopologySerializer.java
@@ -0,0 +1,68 @@
+package net.onrc.onos.core.topology.serializers;
+
+import net.onrc.onos.core.topology.Device;
+import net.onrc.onos.core.topology.Link;
+import net.onrc.onos.core.topology.Switch;
+import net.onrc.onos.core.topology.Topology;
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.codehaus.jackson.map.ser.std.SerializerBase;
+
+import java.io.IOException;
+
+/**
+ * JSON serializer for Topology objects.  Used by REST implementation of the
+ * topology APIs.
+ */
+public class TopologySerializer extends SerializerBase<Topology> {
+
+    /**
+     * Default constructor. Performs basic initialization of the JSON
+     * serializer.
+     */
+    public TopologySerializer() {
+        super(Topology.class);
+    }
+
+    /**
+     * Serialize a Topology object in JSON.  The resulting JSON contains the
+     * switches, links and ports provided by the Topology object.
+     *
+     * @param topology the Topology that is being converted to JSON
+     * @param jsonGenerator generator to place the serialized JSON into
+     * @param serializerProvider unused but required for method override
+     * @throws IOException if the JSON serialization process fails
+     */
+    @Override
+    public void serialize(Topology topology,
+                          JsonGenerator jsonGenerator,
+                          SerializerProvider serializerProvider)
+            throws IOException {
+        // Start the object
+        jsonGenerator.writeStartObject();
+
+        // Output the switches array
+        jsonGenerator.writeArrayFieldStart("switches");
+        for (final Switch swtch : topology.getSwitches()) {
+            jsonGenerator.writeObject(swtch);
+        }
+        jsonGenerator.writeEndArray();
+
+        // Output the links array
+        jsonGenerator.writeArrayFieldStart("links");
+        for (final Link link : topology.getLinks()) {
+            jsonGenerator.writeObject(link);
+        }
+        jsonGenerator.writeEndArray();
+
+        // Output the devices array
+        jsonGenerator.writeArrayFieldStart("devices");
+        for (final Device device : topology.getDevices()) {
+            jsonGenerator.writeObject(device);
+        }
+        jsonGenerator.writeEndArray();
+
+        // All done
+        jsonGenerator.writeEndObject();
+    }
+}
diff --git a/src/test/java/net/onrc/onos/api/rest/TestRestTopology.java b/src/test/java/net/onrc/onos/api/rest/TestRestTopology.java
new file mode 100644
index 0000000..e87d232
--- /dev/null
+++ b/src/test/java/net/onrc/onos/api/rest/TestRestTopology.java
@@ -0,0 +1,109 @@
+package net.onrc.onos.api.rest;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.onrc.onos.core.intent.runtime.IntentTestMocks;
+import net.onrc.onos.core.intent.runtime.PathCalcRuntimeModule;
+import net.onrc.onos.core.intent.runtime.web.IntentWebRoutable;
+import net.onrc.onos.core.topology.ITopologyService;
+import net.onrc.onos.core.topology.web.TopologyWebRoutable;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.restlet.resource.ClientResource;
+
+/**
+ * Test harness for Topology based REST API tests.  This class maintains the
+ * web server and mocks required for testing topology APIs.  REST API tests
+ * for topology should inherit from this class.
+ */
+public class TestRestTopology extends TestRest {
+
+    private IntentTestMocks mocks;
+
+    /**
+     * Fetch the Intent mocking object.
+     *
+     * @return intent mocking object
+     */
+    IntentTestMocks getMocks() {
+        return mocks;
+    }
+
+    /**
+     * Create the web server and mocks required for the topology tests.
+     */
+    @Override
+    public void setUp() {
+        mocks = new IntentTestMocks();
+        mocks.setUpIntentMocks();
+
+        addRestlet(new TopologyWebRoutable());
+        super.setUp();
+
+        final PathCalcRuntimeModule runtime = new PathCalcRuntimeModule();
+        final FloodlightModuleContext moduleContext = getMocks().getModuleContext();
+        try {
+            runtime.init(moduleContext);
+        } catch (FloodlightModuleException floodlightEx) {
+            throw new IllegalArgumentException(floodlightEx);
+        }
+        runtime.startUp(moduleContext);
+
+        getRestApiServer().addAttribute(ITopologyService.class.getCanonicalName(),
+                                        mocks.getTopologyService());
+    }
+
+    /**
+     * Remove anything that will interfere with the next test running correctly.
+     * Shuts down the test REST web server and removes the mocks.
+     */
+    @Override
+    public void tearDown() {
+        getMocks().tearDownIntentMocks();
+        super.tearDown();
+    }
+
+    /**
+     * Fetch the base URL for Topology REST APIs.
+     *
+     * @return base URL
+     */
+    String getBaseRestTopologyUrl() {
+        return getBaseRestUrl() + "/topology";
+    }
+
+    /**
+     * Get the JSON object representation for the top level object referred
+     * to by the given client.
+     *
+     * @param client the ClientResource that references the JSON object
+     * @return JSONObject that represents the object, null if it can't be
+     *         fetched
+     */
+    JSONObject getJSONObject(final ClientResource client) {
+        try {
+            final String topologyJSONString = client.get(String.class);
+            return new JSONObject(topologyJSONString);
+        } catch (JSONException jsonException) {
+            return null;
+        }
+    }
+
+    /**
+     * Get the JSON array representation for the array referred to by
+     * the given client.
+     *
+     * @param client the ClientResource that references the JSON array
+     * @return JSONArray that represents the array, null if it can't be
+     *         fetched.
+     */
+    JSONArray getJSONArray(final ClientResource client) {
+        try {
+            final String topologyJSONString = client.get(String.class);
+            return new JSONArray(topologyJSONString);
+        } catch (JSONException jsonException) {
+            return null;
+        }
+    }
+}
diff --git a/src/test/java/net/onrc/onos/api/rest/TestRestTopologyGet.java b/src/test/java/net/onrc/onos/api/rest/TestRestTopologyGet.java
new file mode 100644
index 0000000..eb8ee2e
--- /dev/null
+++ b/src/test/java/net/onrc/onos/api/rest/TestRestTopologyGet.java
@@ -0,0 +1,211 @@
+package net.onrc.onos.api.rest;
+
+import net.onrc.onos.core.intent.runtime.PathCalcRuntimeModule;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.restlet.data.Status;
+import org.restlet.resource.ClientResource;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static net.onrc.onos.api.rest.ClientResourceStatusMatcher.hasStatusOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasItems;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * Tests for topology REST get operations.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(PathCalcRuntimeModule.class)
+public class TestRestTopologyGet extends TestRestTopology {
+
+    /**
+     * Create the web server and mocks required for
+     * all of the tests.
+     */
+    @Before
+    @SuppressWarnings("ununsed")
+    public void beforeTest() {
+        setRestPort(generateRandomPort());
+        setUp();
+    }
+
+
+    /**
+     * Remove anything that will interfere with the next test running correctly.
+     * Shuts down the test REST web server and removes the mocks.
+     */
+    @After
+    @SuppressWarnings("unused")
+    public void afterTest() {
+        tearDown();
+    }
+
+    /**
+     * Check that the JSON array returned for the switches element matches
+     * the data in the mocked topology.
+     *
+     * @param switches JSON array of switches
+     * @throws JSONException if the JSON is not properly specified
+     */
+    private void checkSwitches(final JSONArray switches) throws JSONException {
+        assertThat(switches.length(), is(equalTo(4)));
+
+        // Check that the first switch has the proper data
+        final JSONObject switch0 = switches.getJSONObject(0);
+        assertThat(switch0.length(), is(equalTo(3)));
+        assertThat(switch0.getString("dpid"), is(equalTo("00:00:00:00:00:00:00:02")));
+        assertThat(switch0.getString("state"), is(equalTo("ACTIVE")));
+
+        // Check that the ports array for the switch is correct
+        final JSONArray switch0Ports = switch0.getJSONArray("ports");
+
+        // check the length of the port array
+        assertThat(switch0Ports.length(), equalTo(4));
+
+        // check the contents of the ports array.  All of the ports should be
+        // active and refer to this switch.
+        for (int portIndex = 0; portIndex < switch0Ports.length(); portIndex++) {
+            final JSONObject switchPort = switch0Ports.getJSONObject(portIndex);
+            assertThat(switchPort.getString("dpid"), is(equalTo("00:00:00:00:00:00:00:02")));
+            assertThat(switchPort.getString("state"), is(equalTo("ACTIVE")));
+        }
+    }
+
+    /**
+     * Check that the JSON array returned for the links element matches
+     * the data in the mocked topology.
+     *
+     * @param links JSON array of links
+     * @throws JSONException if the JSON is not properly specified
+     */
+    private void checkLinks(final JSONArray links) throws JSONException {
+        // Check the length of the links array
+        assertThat(links.length(), is(equalTo(10)));
+
+        final Set<String> fromLinks = new HashSet<>();
+        final Set<String> toLinks = new HashSet<>();
+
+        // Check that the source and destination of links to switch 0 are
+        // correct
+        for (int linkIndex = 0; linkIndex < links.length(); linkIndex++) {
+            final JSONObject link = links.getJSONObject(linkIndex);
+            final String src = link.getString("src-switch");
+            final String dst = link.getString("dst-switch");
+            assertThat(src, is(notNullValue()));
+            assertThat(dst, is(notNullValue()));
+
+            if (src.equals("00:00:00:00:00:00:00:02")) {
+                toLinks.add(dst);
+            }
+
+            if (dst.equals("00:00:00:00:00:00:00:02")) {
+                fromLinks.add(src);
+            }
+        }
+
+        assertThat(toLinks, hasItems("00:00:00:00:00:00:00:01",
+                                     "00:00:00:00:00:00:00:03",
+                                     "00:00:00:00:00:00:00:04"));
+
+        assertThat(fromLinks, hasItems("00:00:00:00:00:00:00:01",
+                                       "00:00:00:00:00:00:00:03",
+                                       "00:00:00:00:00:00:00:04"));
+    }
+
+    /**
+     * Check that the JSON array returned for the devices element matches
+     * the data in the mocked topology.
+     *
+     * @param devices JSON array of switches
+     */
+    private void checkDevices(final JSONArray devices) {
+        // devices array should be empty
+        assertThat(devices.length(), is(equalTo(0)));
+    }
+
+    /**
+     * Test that the GET of all Topology REST call returns the proper result.
+     * The call to get all Topology should return 3 items (switches, links,
+     * and devices), an HTTP status of OK, and the proper topology data.
+     */
+    @Test
+    public void testFetchOfAllTopology() throws Exception {
+        final ClientResource client = new ClientResource(getBaseRestTopologyUrl());
+        final JSONObject topology = getJSONObject(client);
+
+        // HTTP status should be OK
+        assertThat(client, hasStatusOf(Status.SUCCESS_OK));
+
+        // Check the number of top level members in the topology object
+        assertThat(topology.length(), is(equalTo(3)));
+
+        //  Check the switches element
+        final JSONArray switches = topology.getJSONArray("switches");
+        checkSwitches(switches);
+
+        // Check the values in the links array
+        final JSONArray links = topology.getJSONArray("links");
+        checkLinks(links);
+
+        // Check the devices array
+        final JSONArray devices = topology.getJSONArray("devices");
+        checkDevices(devices);
+    }
+
+    /**
+     * Test that the GET of all switches REST call returns the proper result.
+     * The call to get all switches should return the correct switch data.
+     */
+    @Test
+    public void testFetchOfAllSwitches() throws Exception {
+        final ClientResource client = new ClientResource(getBaseRestTopologyUrl() + "/switches");
+        final JSONArray switches = getJSONArray(client);
+
+        // HTTP status should be OK
+        assertThat(client, hasStatusOf(Status.SUCCESS_OK));
+
+        checkSwitches(switches);
+    }
+
+    /**
+     * Test that the GET of all links REST call returns the proper result.
+     * The call to get all links should return the proper link data.
+     */
+    @Test
+    public void testFetchOfAllLinks() throws Exception {
+        final ClientResource client = new ClientResource(getBaseRestTopologyUrl() + "/links");
+        final JSONArray links = getJSONArray(client);
+
+        // HTTP status should be OK
+        assertThat(client, hasStatusOf(Status.SUCCESS_OK));
+
+        checkLinks(links);
+    }
+
+    /**
+     * Test that the GET of all devices REST call returns the proper result.
+     * The call to get all devices should return no devices.
+     */
+    @Test
+    public void testFetchOfAllDevices() throws Exception {
+        final ClientResource client = new ClientResource(getBaseRestTopologyUrl() + "/switches");
+        final JSONArray devices = getJSONArray(client);
+
+        // HTTP status should be OK
+        assertThat(client, hasStatusOf(Status.SUCCESS_OK));
+
+        checkSwitches(devices);
+    }
+}
diff --git a/src/test/java/net/onrc/onos/core/intent/runtime/IntentTestMocks.java b/src/test/java/net/onrc/onos/core/intent/runtime/IntentTestMocks.java
index aa5223e..3d3cdcb 100644
--- a/src/test/java/net/onrc/onos/core/intent/runtime/IntentTestMocks.java
+++ b/src/test/java/net/onrc/onos/core/intent/runtime/IntentTestMocks.java
@@ -150,4 +150,13 @@
     public MockTopology getTopology() {
         return topology;
     }
+
+    /**
+     * Fetch the mocked topology service.
+     *
+     * @return mocked topology service
+     */
+    public ITopologyService getTopologyService() {
+        return topologyService;
+    }
 }