Add CLI for listing all openstack instance ports

Change-Id: I05c826102a257c9d924397d22c368d35ff0587cf
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/InstancePortListCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/InstancePortListCommand.java
new file mode 100644
index 0000000..369b27a
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/InstancePortListCommand.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.openstacknetworking.cli;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+
+import java.util.Comparator;
+import java.util.List;
+
+import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
+
+/**
+ * Lists OpenStack instance ports.
+ */
+@Command(scope = "onos", name = "openstack-instance-ports",
+        description = "Lists all OpenStack instance ports")
+public class InstancePortListCommand extends AbstractShellCommand {
+
+    private static final String FORMAT = "%-40s%-10s%-25s%-15s%-20s";
+
+    @Override
+    protected void execute() {
+        InstancePortService service = get(InstancePortService.class);
+        List<InstancePort> instancePorts = Lists.newArrayList(service.instancePorts());
+        instancePorts.sort(Comparator.comparing(InstancePort::portId));
+
+        if (outputJson()) {
+            print("%s", json(this, instancePorts));
+        } else {
+            print(FORMAT, "ID", "State", "Device ID", "Port Number", "Fixed IP");
+            for (InstancePort port : instancePorts) {
+                print(FORMAT, port.portId(), port.state(), port.deviceId().toString(),
+                        port.portNumber().toLong(), port.ipAddress().toString());
+            }
+        }
+    }
+
+    private JsonNode json(AbstractShellCommand context, List<InstancePort> ports) {
+        ObjectMapper mapper = new ObjectMapper();
+        ArrayNode result = mapper.enable(INDENT_OUTPUT).createArrayNode();
+        ports.forEach(p -> result.add(context.jsonForEntity(p, InstancePort.class)));
+
+        return result;
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/codec/InstancePortCodec.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/codec/InstancePortCodec.java
new file mode 100644
index 0000000..c4b3cdc
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/codec/InstancePortCodec.java
@@ -0,0 +1,94 @@
+/*
+ * 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.openstacknetworking.codec;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.impl.DefaultInstancePort;
+import org.slf4j.Logger;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Openstack instance port codec used for serializing and de-serializing JSON string.
+ */
+public class InstancePortCodec extends JsonCodec<InstancePort> {
+
+    private final Logger log = getLogger(getClass());
+
+    private static final String NETWORK_ID = "networkId";
+    private static final String PORT_ID = "portId";
+    private static final String MAC_ADDRESS = "macAddress";
+    private static final String IP_ADDRESS = "ipAddress";
+    private static final String DEVICE_ID = "deviceId";
+    private static final String PORT_NUMBER = "portNumber";
+    private static final String STATE = "state";
+
+    private static final String MISSING_MESSAGE = " is required in InstancePort";
+
+    @Override
+    public ObjectNode encode(InstancePort port, CodecContext context) {
+        checkNotNull(port, "Instance port cannot be null");
+
+        return context.mapper().createObjectNode()
+                .put(NETWORK_ID, port.networkId())
+                .put(PORT_ID, port.portId())
+                .put(MAC_ADDRESS, port.macAddress().toString())
+                .put(IP_ADDRESS, port.ipAddress().toString())
+                .put(DEVICE_ID, port.deviceId().toString())
+                .put(PORT_NUMBER, port.portNumber().toString())
+                .put(STATE, port.state().name());
+    }
+
+    @Override
+    public InstancePort decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        String networkId = nullIsIllegal(json.get(NETWORK_ID).asText(),
+                NETWORK_ID + MISSING_MESSAGE);
+        String portId = nullIsIllegal(json.get(PORT_ID).asText(),
+                PORT_ID + MISSING_MESSAGE);
+        String macAddress = nullIsIllegal(json.get(MAC_ADDRESS).asText(),
+                MAC_ADDRESS + MISSING_MESSAGE);
+        String ipAddress = nullIsIllegal(json.get(IP_ADDRESS).asText(),
+                IP_ADDRESS + MISSING_MESSAGE);
+        String deviceId = nullIsIllegal(json.get(DEVICE_ID).asText(),
+                DEVICE_ID + MISSING_MESSAGE);
+        String portNumber = nullIsIllegal(json.get(PORT_NUMBER).asText(),
+                PORT_NUMBER + MISSING_MESSAGE);
+        String state = nullIsIllegal(json.get(STATE).asText(),
+                STATE + MISSING_MESSAGE);
+
+        return DefaultInstancePort.builder()
+                .networkId(networkId)
+                .portId(portId)
+                .macAddress(MacAddress.valueOf(macAddress))
+                .ipAddress(IpAddress.valueOf(ipAddress))
+                .deviceId(DeviceId.deviceId(deviceId))
+                .portNumber(PortNumber.fromString(portNumber))
+                .state(InstancePort.State.valueOf(state)).build();
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/codec/package-info.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/codec/package-info.java
new file mode 100644
index 0000000..39000c0
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/codec/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 networking entity JSON codecs.
+ */
+package org.onosproject.openstacknetworking.codec;
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackNetworkingCodecRegister.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackNetworkingCodecRegister.java
new file mode 100644
index 0000000..55077f3
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackNetworkingCodecRegister.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.web;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.codec.CodecService;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.codec.InstancePortCodec;
+import org.slf4j.Logger;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the JSON codec brokering service for OpenstackNetworking.
+ */
+@Component(immediate = true)
+public class OpenstackNetworkingCodecRegister {
+
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CodecService codecService;
+
+    @Activate
+    protected void activate() {
+
+        codecService.registerCodec(InstancePort.class, new InstancePortCodec());
+
+        log.info("Started");
+
+    }
+
+    @Deactivate
+    protected void deactivate() {
+
+        codecService.unregisterCodec(InstancePort.class);
+
+        log.info("Stopped");
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/openstacknetworking/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 8fb8642..da387fb 100644
--- a/apps/openstacknetworking/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/apps/openstacknetworking/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -75,10 +75,13 @@
             </completers>
         </command>
         <command>
-            <action class="org.onosproject.openstacknetworking.cli.OpenstackAddAclCommand"></action>
+            <action class="org.onosproject.openstacknetworking.cli.OpenstackAddAclCommand" />
         </command>
         <command>
-            <action class="org.onosproject.openstacknetworking.cli.OpenstackRemoveAclCommand"></action>
+            <action class="org.onosproject.openstacknetworking.cli.OpenstackRemoveAclCommand" />
+        </command>
+        <command>
+            <action class="org.onosproject.openstacknetworking.cli.InstancePortListCommand" />
         </command>
     </command-bundle>
 
diff --git a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/codec/InstancePortCodecTest.java b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/codec/InstancePortCodecTest.java
new file mode 100644
index 0000000..7c5e61c
--- /dev/null
+++ b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/codec/InstancePortCodecTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.openstacknetworking.codec;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.hamcrest.MatcherAssert;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.codec.impl.CodecManager;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.impl.DefaultInstancePort;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onosproject.openstacknetworking.codec.InstancePortJsonMatcher.matchesInstancePort;
+
+/**
+ * Unit tests for InstancePort codec.
+ */
+public class InstancePortCodecTest {
+
+    MockCodecContext context;
+    JsonCodec<InstancePort> instancePortCodec;
+
+
+    @Before
+    public void setUp() {
+        context = new MockCodecContext();
+        instancePortCodec = new InstancePortCodec();
+
+        assertThat(instancePortCodec, notNullValue());
+    }
+
+    /**
+     * Tests the instance port encoding.
+     */
+    @Test
+    public void testInstancePortEncode() {
+        InstancePort port = DefaultInstancePort.builder()
+                .networkId("net-id-1")
+                .portId("port-id-1")
+                .deviceId(DeviceId.deviceId("of:000000000000000a"))
+                .portNumber(PortNumber.portNumber(1, "tap-1"))
+                .ipAddress(IpAddress.valueOf("10.10.10.1"))
+                .macAddress(MacAddress.valueOf("11:22:33:44:55:66"))
+                .state(InstancePort.State.valueOf("ACTIVE"))
+                .build();
+
+        ObjectNode portJson = instancePortCodec.encode(port, context);
+        assertThat(portJson, matchesInstancePort(port));
+    }
+
+    /**
+     * Tests the instance port decoding.
+     */
+    @Test
+    public void testInstancePortDecode() throws IOException {
+        InstancePort port = getInstancePort("InstancePort.json");
+
+        assertThat(port.networkId(), is("net-id-1"));
+        assertThat(port.portId(), is("port-id-1"));
+        assertThat(port.deviceId(), is(DeviceId.deviceId("of:000000000000000a")));
+        assertThat(port.portNumber(), is(PortNumber.portNumber(1, "tap-1")));
+        assertThat(port.ipAddress(), is(IpAddress.valueOf("10.10.10.1")));
+        assertThat(port.macAddress(), is(MacAddress.valueOf("11:22:33:44:55:66")));
+        assertThat(port.state().name(), is("ACTIVE"));
+    }
+
+    /**
+     * Reads in an instance port from the given resource and decodes it.
+     *
+     * @param resourceName resource to use to read the JSON for the rule
+     * @return decoded instance port
+     * @throws IOException if processing the resource fails
+     */
+    private InstancePort getInstancePort(String resourceName) throws IOException {
+        InputStream jsonStream = InstancePortCodecTest.class.getResourceAsStream(resourceName);
+        JsonNode json = context.mapper().readTree(jsonStream);
+        MatcherAssert.assertThat(json, notNullValue());
+        InstancePort port = instancePortCodec.decode((ObjectNode) json, context);
+        assertThat(port, notNullValue());
+        return port;
+    }
+
+    /**
+     * 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 == InstancePort.class) {
+                return (JsonCodec<T>) instancePortCodec;
+            }
+            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/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/codec/InstancePortJsonMatcher.java b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/codec/InstancePortJsonMatcher.java
new file mode 100644
index 0000000..b816339
--- /dev/null
+++ b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/codec/InstancePortJsonMatcher.java
@@ -0,0 +1,117 @@
+/*
+ * 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.openstacknetworking.codec;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onosproject.openstacknetworking.api.InstancePort;
+
+/**
+ * Hamcrest matcher for instance port.
+ */
+public final class InstancePortJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+    private final InstancePort port;
+    private static final String NETWORK_ID = "networkId";
+    private static final String PORT_ID = "portId";
+    private static final String MAC_ADDRESS = "macAddress";
+    private static final String IP_ADDRESS = "ipAddress";
+    private static final String DEVICE_ID = "deviceId";
+    private static final String PORT_NUMBER = "portNumber";
+    private static final String STATE = "state";
+
+    private InstancePortJsonMatcher(InstancePort port) {
+        this.port = port;
+    }
+
+    @Override
+    protected boolean matchesSafely(JsonNode jsonNode, Description description) {
+
+        // check network ID
+        String jsonNetworkId = jsonNode.get(NETWORK_ID).asText();
+        String networkId = port.networkId();
+        if (!jsonNetworkId.equals(networkId)) {
+            description.appendText("networkId was " + jsonNetworkId);
+            return false;
+        }
+
+        // check port ID
+        String jsonPortId = jsonNode.get(PORT_ID).asText();
+        String portId = port.portId();
+        if (!jsonPortId.equals(portId)) {
+            description.appendText("portId was " + jsonPortId);
+            return false;
+        }
+
+        // check MAC address
+        String jsonMacAddress = jsonNode.get(MAC_ADDRESS).asText();
+        String macAddress = port.macAddress().toString();
+        if (!jsonMacAddress.equals(macAddress)) {
+            description.appendText("macAddress was " + jsonMacAddress);
+            return false;
+        }
+
+        // check IP address
+        String jsonIpAddress = jsonNode.get(IP_ADDRESS).asText();
+        String ipAddress = port.ipAddress().toString();
+        if (!jsonIpAddress.equals(ipAddress)) {
+            description.appendText("ipAddress was " + jsonIpAddress);
+            return false;
+        }
+
+        // check device ID
+        String jsonDeviceId = jsonNode.get(DEVICE_ID).asText();
+        String deviceId = port.deviceId().toString();
+        if (!jsonDeviceId.equals(deviceId)) {
+            description.appendText("deviceId was " + jsonDeviceId);
+            return false;
+        }
+
+        // check port number
+        String jsonPortNumber = jsonNode.get(PORT_NUMBER).asText();
+        String portNumber = port.portNumber().toString();
+        if (!jsonPortNumber.equals(portNumber)) {
+            description.appendText("portNumber was " + jsonPortNumber);
+            return false;
+        }
+
+        // check state
+        String jsonState = jsonNode.get(STATE).asText();
+        String state = port.state().name();
+        if (!jsonState.equals(state)) {
+            description.appendText("state was " + jsonState);
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void describeTo(Description description) {
+        description.appendText(port.toString());
+    }
+
+    /**
+     * Factory to allocate an instance port matcher.
+     *
+     * @param port openstack instance port we are looking for
+     * @return matcher
+     */
+    public static InstancePortJsonMatcher matchesInstancePort(InstancePort port) {
+        return new InstancePortJsonMatcher(port);
+    }
+}
diff --git a/apps/openstacknetworking/app/src/test/resources/org/onosproject/openstacknetworking/codec/InstancePort.json b/apps/openstacknetworking/app/src/test/resources/org/onosproject/openstacknetworking/codec/InstancePort.json
new file mode 100644
index 0000000..040bca2
--- /dev/null
+++ b/apps/openstacknetworking/app/src/test/resources/org/onosproject/openstacknetworking/codec/InstancePort.json
@@ -0,0 +1,9 @@
+{
+  "networkId": "net-id-1",
+  "portId": "port-id-1",
+  "deviceId": "of:000000000000000a",
+  "portNumber": "[tap-1](1)",
+  "ipAddress": "10.10.10.1",
+  "macAddress": "11:22:33:44:55:66",
+  "state": "ACTIVE"
+}
\ No newline at end of file