Fixed service port create failure

Also enhanced service network and port codec and added more unit tests

Change-Id: I523acc49dc1472520bd15a47f9a591cd95297ea0
diff --git a/src/test/java/org/opencord/cordvtn/codec/ServiceNetworkCodecTest.java b/src/test/java/org/opencord/cordvtn/codec/ServiceNetworkCodecTest.java
index 35d9a1b..95701bd 100644
--- a/src/test/java/org/opencord/cordvtn/codec/ServiceNetworkCodecTest.java
+++ b/src/test/java/org/opencord/cordvtn/codec/ServiceNetworkCodecTest.java
@@ -16,15 +16,16 @@
 package org.opencord.cordvtn.codec;
 
 import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.NullNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
-import com.google.common.collect.ImmutableMap;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.ExpectedException;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
 import org.onosproject.codec.JsonCodec;
 import org.opencord.cordvtn.api.net.NetworkId;
+import org.opencord.cordvtn.api.net.SegmentId;
 import org.opencord.cordvtn.api.net.ServiceNetwork;
 import org.opencord.cordvtn.api.net.ServiceNetwork.DependencyType;
 import org.opencord.cordvtn.impl.DefaultServiceNetwork;
@@ -38,7 +39,6 @@
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.opencord.cordvtn.api.net.ServiceNetwork.DependencyType.BIDIRECTIONAL;
-import static org.opencord.cordvtn.api.net.ServiceNetwork.NetworkType.MANAGEMENT_LOCAL;
 import static org.opencord.cordvtn.api.net.ServiceNetwork.NetworkType.PRIVATE;
 import static org.opencord.cordvtn.codec.ServiceNetworkJsonMatcher.matchesServiceNetwork;
 
@@ -51,30 +51,35 @@
     private static final String NAME = "name";
     private static final String TYPE = "type";
     private static final String PROVIDERS = "providers";
+    private static final String SEGMENT_ID = "segment_id";
+    private static final String SUBNET = "subnet";
+    private static final String SERVICE_IP = "service_ip";
+    private static final String DEP_TYPE = "bidirectional";
 
-    private final Map<NetworkId, DependencyType> providerA =
+    private static final String NAME_1 = "network_1";
+    private static final NetworkId ID_1 = NetworkId.of("network_1");
+    private static final NetworkId PROVIDER_ID_1 = NetworkId.of("provider_1");
+    private static final SegmentId SEGMENT_ID_1 = SegmentId.of(1L);
+    private static final IpPrefix SUBNET_1 = IpPrefix.valueOf("192.168.0.0/24");
+    private static final IpAddress SERVICE_IP_1 = IpAddress.valueOf("192.168.0.1");
+
+    private static final Map<NetworkId, DependencyType> PROVIDER_1 =
             new HashMap<NetworkId, DependencyType>() {
                 {
-                    put(NetworkId.of("A"), BIDIRECTIONAL);
+                    put(NetworkId.of("provider_1"), BIDIRECTIONAL);
                 }
             };
 
-    private final ServiceNetwork networkA = DefaultServiceNetwork.builder()
-            .id(NetworkId.of("A"))
-            .name("A")
-            .type(MANAGEMENT_LOCAL)
-            .build();
-
-    private final ServiceNetwork networkB = DefaultServiceNetwork.builder()
-            .id(NetworkId.of("B"))
-            .name("B")
+    private static final ServiceNetwork NETWORK_1 = DefaultServiceNetwork.builder()
+            .id(ID_1)
+            .name(NAME_1)
             .type(PRIVATE)
-            .providers(providerA)
+            .segmentId(SEGMENT_ID_1)
+            .subnet(SUBNET_1)
+            .serviceIp(SERVICE_IP_1)
+            .providers(PROVIDER_1)
             .build();
 
-    @Rule
-    public ExpectedException exception = ExpectedException.none();
-
     private JsonCodec<ServiceNetwork> codec;
     private MockCodecContext context;
 
@@ -89,89 +94,332 @@
         assertThat(codec, notNullValue());
     }
 
+    /**
+     * Checks if encoding service network works properly.
+     */
     @Test
     public void testServiceNetworkEncode() {
-        ObjectNode networkJson = codec.encode(networkA, context);
+        ObjectNode networkJson = codec.encode(NETWORK_1, context);
         assertThat(networkJson, notNullValue());
-        assertThat(networkJson, matchesServiceNetwork(networkA));
-
-        networkJson = codec.encode(networkB, context);
-        assertThat(networkJson, notNullValue());
-        assertThat(networkJson, matchesServiceNetwork(networkB));
+        assertThat(networkJson, matchesServiceNetwork(NETWORK_1));
     }
 
+    /**
+     * Checks if decoding service network works properly.
+     */
     @Test
     public void testServiceNetworkDecode() throws IOException {
-        ServiceNetwork snet = getServiceNetwork("service-network.json");
-        assertThat(snet.id(), is(NetworkId.of("A")));
-        assertThat(snet.name(), is("A"));
-        assertThat(snet.type(), is(MANAGEMENT_LOCAL));
-        assertThat(snet.providers(), is(ImmutableMap.of()));
-
-        snet = getServiceNetwork("service-network-with-provider.json");
-        assertThat(snet.id(), is(NetworkId.of("B")));
-        assertThat(snet.name(), is("B"));
-        assertThat(snet.type(), is(PRIVATE));
-        assertThat(snet.providers(), is(providerA));
+        ServiceNetwork sNet = getServiceNetwork("service-network.json");
+        assertThat(sNet.id(), is(ID_1));
+        assertThat(sNet.name(), is(NAME_1));
+        assertThat(sNet.type(), is(PRIVATE));
+        assertThat(sNet.providers(), is(PROVIDER_1));
     }
 
-    @Test
-    public void testServiceNetworkDecodeMissingId() throws IllegalArgumentException {
+    /**
+     * Checks if decoding service network without ID fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeMissingId() {
         final JsonNode jsonMissingId = context.mapper().createObjectNode()
-                .put(NAME, "A")
+                .put(NAME, NAME_1)
                 .put(TYPE, PRIVATE.name())
-                .put(PROVIDERS, "");
-        exception.expect(IllegalArgumentException.class);
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString());
         codec.decode((ObjectNode) jsonMissingId, context);
     }
 
-    @Test
-    public void testServiceNetworkDecodeEmptyId() throws IllegalArgumentException {
+    /**
+     * Checks if decoding service network with empty ID fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeEmptyId() {
         final JsonNode jsonEmptyId = context.mapper().createObjectNode()
                 .put(ID, "")
-                .put(NAME, "A")
+                .put(NAME, NAME_1)
                 .put(TYPE, PRIVATE.name())
-                .put(PROVIDERS, "");
-        exception.expect(IllegalArgumentException.class);
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString());
         codec.decode((ObjectNode) jsonEmptyId, context);
     }
 
-    @Test
-    public void testServiceNetworkDecodeNullId() throws NullPointerException {
+    /**
+     * Checks if decoding service network with null ID fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeNullId() {
         final JsonNode jsonNullId = context.mapper().createObjectNode()
-                .put(NAME, "A")
+                .put(NAME, NAME_1)
                 .put(TYPE, PRIVATE.name())
-                .put(PROVIDERS, "")
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
                 .set(ID, NullNode.getInstance());
-        exception.expect(IllegalArgumentException.class);
         codec.decode((ObjectNode) jsonNullId, context);
     }
 
-    @Test
-    public void testServiceNetworkDecodeWithMissingProviderId() throws NullPointerException {
-        final JsonNode jsonMissingProviderId = context.mapper().createObjectNode()
-                .put(TYPE, "B");
-        final JsonNode jsonWithProvider = context.mapper().createObjectNode()
-                .put(ID, "A")
-                .put(NAME, "A")
-                .put(TYPE, PRIVATE.name())
-                .set(PROVIDERS, jsonMissingProviderId);
-        exception.expect(IllegalArgumentException.class);
-        codec.decode((ObjectNode) jsonWithProvider, context);
+    /**
+     * Checks if decoding service network with invalid type fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeInvalidType() {
+        final JsonNode jsonInvalidType = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, "type")
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString());
+        codec.decode((ObjectNode) jsonInvalidType, context);
     }
 
-    @Test
-    public void testServiceNetworkDecodeWithWrongProviderType() throws NullPointerException {
-        final JsonNode jsonWrongProviderType = context.mapper().createObjectNode()
-                .put(ID, "B")
-                .put(TYPE, "none");
-        final JsonNode jsonWithProvider = context.mapper().createObjectNode()
-                .put(ID, "A")
-                .put(NAME, "A")
+    /**
+     * Checks if decoding service network with null type fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeNullType() {
+        final JsonNode jsonNullType = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(TYPE, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonNullType, context);
+    }
+
+    /**
+     * Checks if decoding service network with invalid segment ID fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeInvalidSegmentId() {
+        final JsonNode jsonInvalidSeg = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
                 .put(TYPE, PRIVATE.name())
-                .set(PROVIDERS, jsonWrongProviderType);
-        exception.expect(IllegalArgumentException.class);
-        codec.decode((ObjectNode) jsonWithProvider, context);
+                .put(SEGMENT_ID, "segmentId")
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString());
+        codec.decode((ObjectNode) jsonInvalidSeg, context);
+    }
+
+    /**
+     * Checks if decoding service network with 0 segment ID fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeZeroSegmentId() {
+        final JsonNode jsonInvalidSeg = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, 0)
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString());
+        codec.decode((ObjectNode) jsonInvalidSeg, context);
+    }
+
+    /**
+     * Checks if decoding service network with 0 segment ID fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeNullSegmentId() {
+        final JsonNode jsonInvalidSeg = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(SEGMENT_ID, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidSeg, context);
+    }
+
+    /**
+     * Checks if decoding service network with invalid subnet fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeInvalidSubnet() {
+        final JsonNode jsonInvalidSubnet = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, "")
+                .put(SERVICE_IP, SERVICE_IP_1.toString());
+        codec.decode((ObjectNode) jsonInvalidSubnet, context);
+    }
+
+    /**
+     * Checks if decoding service network with invalid subnet fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeNullSubnet() {
+        final JsonNode jsonInvalidSubnet = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(SUBNET, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidSubnet, context);
+    }
+
+    /**
+     * Checks if decoding service network with invalid service IP fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeInvalidServiceIp() {
+        final JsonNode jsonInvalidServiceIp = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, "");
+        codec.decode((ObjectNode) jsonInvalidServiceIp, context);
+    }
+
+    /**
+     * Checks if decoding service network with null service IP fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeNullServiceIp() {
+        final JsonNode jsonInvalidServiceIp = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .set(SERVICE_IP, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidServiceIp, context);
+    }
+
+    /**
+     * Checks if decoding service network with null and empty providers allowed.
+     */
+    @Test
+    public void testServiceNetworkDecodeNullAndEmptyProviders() {
+        JsonNode jsonInvalidProvider = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(PROVIDERS, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidProvider, context);
+
+        jsonInvalidProvider = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(PROVIDERS, context.mapper().createArrayNode());
+        codec.decode((ObjectNode) jsonInvalidProvider, context);
+    }
+
+    /**
+     * Checks if decoding service network with non-array providers value fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkNonArrayProviders() {
+        final JsonNode jsonProvider = context.mapper().createObjectNode()
+                .put(ID, PROVIDER_ID_1.id())
+                .put(DEP_TYPE, true);
+        final JsonNode jsonInvalidProvider = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(PROVIDERS, jsonProvider);
+        codec.decode((ObjectNode) jsonInvalidProvider, context);
+    }
+
+    /**
+     * Checks if decoding service network with invalid provider fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkMissingProviderId() {
+        final ArrayNode jsonProviders = context.mapper().createArrayNode();
+        final JsonNode jsonProvider = context.mapper().createObjectNode()
+                .put(DEP_TYPE, true);
+        jsonProviders.add(jsonProvider);
+        final JsonNode jsonInvalidProvider = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(PROVIDERS, jsonProvider);
+        codec.decode((ObjectNode) jsonInvalidProvider, context);
+    }
+
+    /**
+     * Checks if decoding service network with invalid provider fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkMissingProviderType() {
+        final ArrayNode jsonProviders = context.mapper().createArrayNode();
+        final JsonNode jsonProvider = context.mapper().createObjectNode()
+                .put(ID, PROVIDER_ID_1.id());
+        jsonProviders.add(jsonProvider);
+        final JsonNode jsonInvalidProvider = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(PROVIDERS, jsonProvider);
+        codec.decode((ObjectNode) jsonInvalidProvider, context);
+    }
+
+    /**
+     * Checks if decoding service network with invalid provider fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkNullProviderId() {
+        final ArrayNode jsonProviders = context.mapper().createArrayNode();
+        final JsonNode jsonProvider = context.mapper().createObjectNode()
+                .put(DEP_TYPE, true)
+                .set(ID, NullNode.getInstance());
+        jsonProviders.add(jsonProvider);
+        final JsonNode jsonInvalidProvider = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(PROVIDERS, jsonProvider);
+        codec.decode((ObjectNode) jsonInvalidProvider, context);
+    }
+
+    /**
+     * Checks if decoding service network with invalid provider fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkNullProviderType() {
+        final ArrayNode jsonProviders = context.mapper().createArrayNode();
+        final JsonNode jsonProvider = context.mapper().createObjectNode()
+                .put(ID, PROVIDER_ID_1.id())
+                .set(DEP_TYPE, NullNode.getInstance());
+        jsonProviders.add(jsonProvider);
+        final JsonNode jsonInvalidProvider = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(PROVIDERS, jsonProvider);
+        codec.decode((ObjectNode) jsonInvalidProvider, context);
     }
 
     private ServiceNetwork getServiceNetwork(String resource) throws IOException {
diff --git a/src/test/java/org/opencord/cordvtn/codec/ServiceNetworkJsonMatcher.java b/src/test/java/org/opencord/cordvtn/codec/ServiceNetworkJsonMatcher.java
index da321e3..f5a484b 100644
--- a/src/test/java/org/opencord/cordvtn/codec/ServiceNetworkJsonMatcher.java
+++ b/src/test/java/org/opencord/cordvtn/codec/ServiceNetworkJsonMatcher.java
@@ -66,31 +66,40 @@
         }
 
         JsonNode jsonProviders = jsonNet.get("providers");
-        if (jsonProviders == null || jsonProviders == NullNode.getInstance()) {
-            description.appendText("provider networks were empty");
-            return false;
-        }
-
-        if (jsonProviders.size() != network.providers().size()) {
-            return false;
-        }
-
-        for (JsonNode provider : jsonProviders) {
-            NetworkId id = NetworkId.of(provider.get("id").asText());
-            boolean bidirectional = provider.get("bidirectional").asBoolean();
-
-            if (!network.providers().containsKey(id)) {
-                final String msg = String.format("provider id:%s couldn't find", id);
-                description.appendText(msg);
+        if (network.providers().isEmpty()) {
+            if (jsonProviders != null &&
+                    jsonProviders != NullNode.getInstance() &&
+                    jsonProviders.size() != 0) {
+                description.appendText("provider networks did not match");
                 return false;
             }
-
-            if (network.providers().get(id).equals(BIDIRECTIONAL) != bidirectional) {
-                final String msg = String.format(
-                        "mismatch provider id:%s, bidirectional: %s",
-                        id, bidirectional);
-                description.appendText(msg);
+        } else {
+            if (jsonProviders == null ||
+                    jsonProviders == NullNode.getInstance() ||
+                    jsonProviders.size() == 0) {
+                description.appendText("provider networks did not match");
                 return false;
+            } else if (jsonProviders.size() != network.providers().size()) {
+                description.appendText("provider networks did not match");
+                return false;
+            } else {
+                for (JsonNode provider : jsonProviders) {
+                    NetworkId id = NetworkId.of(provider.get("id").asText());
+                    boolean bidirectional = provider.get("bidirectional").asBoolean();
+
+                    if (!network.providers().containsKey(id)) {
+                        final String msg = String.format("provider id:%s couldn't find", id);
+                        description.appendText(msg);
+                        return false;
+                    }
+                    if (network.providers().get(id).equals(BIDIRECTIONAL) != bidirectional) {
+                        final String msg = String.format(
+                                "mismatch provider id:%s, bidirectional: %s",
+                                id, bidirectional);
+                        description.appendText(msg);
+                        return false;
+                    }
+                }
             }
         }
         return true;
diff --git a/src/test/java/org/opencord/cordvtn/codec/ServicePortCodecTest.java b/src/test/java/org/opencord/cordvtn/codec/ServicePortCodecTest.java
new file mode 100644
index 0000000..02d0103
--- /dev/null
+++ b/src/test/java/org/opencord/cordvtn/codec/ServicePortCodecTest.java
@@ -0,0 +1,500 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * 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.opencord.cordvtn.codec;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.NullNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.codec.JsonCodec;
+import org.opencord.cordvtn.api.net.AddressPair;
+import org.opencord.cordvtn.api.net.NetworkId;
+import org.opencord.cordvtn.api.net.PortId;
+import org.opencord.cordvtn.api.net.ServicePort;
+import org.opencord.cordvtn.impl.DefaultServicePort;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.opencord.cordvtn.codec.ServicePortJsonMatcher.matchesServicePort;
+
+/**
+ * Unit tests for ServiceNetwork codec.
+ */
+public final class ServicePortCodecTest {
+
+    private static final String SERVICE_PORT = "servicePort";
+    private static final String ID = "id";
+    private static final String NETWORK_ID = "network_id";
+    private static final String NAME = "name";
+    private static final String IP_ADDRESS = "ip_address";
+    private static final String MAC_ADDRESS = "mac_address";
+    private static final String FLOATING_ADDRESS_PAIRS = "floating_address_pairs";
+    private static final String VLAN_ID = "vlan_id";
+
+    private static final PortId PORT_ID_1 = PortId.of("port-1");
+    private static final NetworkId NETWORK_ID_1 = NetworkId.of("network-1");
+    private static final String PORT_NAME_1 = "tap1";
+    private static final MacAddress MAC_ADDRESS_1 = MacAddress.valueOf("00:00:00:00:00:01");
+    private static final IpAddress IP_ADDRESS_1 = IpAddress.valueOf("10.0.0.1");
+    private static final VlanId VLAN_ID_1 = VlanId.vlanId("222");
+
+    private static final AddressPair ADDRESS_PAIR_1 = AddressPair.of(
+            IpAddress.valueOf("192.168.0.1"),
+            MacAddress.valueOf("02:42:0a:06:01:01"));
+    private static final AddressPair ADDRESS_PAIR_2 = AddressPair.of(
+            IpAddress.valueOf("192.168.0.2"),
+            MacAddress.valueOf("02:42:0a:06:01:02"));
+
+    private static final Set<AddressPair> ADDRESS_PAIRS_1 =
+            new HashSet<AddressPair>() {
+                {
+                    add(ADDRESS_PAIR_1);
+                    add(ADDRESS_PAIR_2);
+                }
+            };
+
+    private static final ServicePort PORT_1 = DefaultServicePort.builder()
+            .id(PORT_ID_1)
+            .networkId(NETWORK_ID_1)
+            .name(PORT_NAME_1)
+            .ip(IP_ADDRESS_1)
+            .mac(MAC_ADDRESS_1)
+            .addressPairs(ADDRESS_PAIRS_1)
+            .vlanId(VLAN_ID_1)
+            .build();
+
+    private JsonCodec<ServicePort> codec;
+    private MockCodecContext context;
+
+    /**
+     * Creates a context and gets the servicePort codec for each test.
+     */
+    @Before
+    public void setUp() {
+        context = new MockCodecContext();
+        codec = context.codec(ServicePort.class);
+        assertThat(codec, notNullValue());
+    }
+
+    /**
+     * Checks if encoding service port works properly.
+     */
+    @Test
+    public void testServicePortEncode() {
+        ObjectNode jsonPort = codec.encode(PORT_1, context);
+        assertThat(jsonPort, notNullValue());
+        assertThat(jsonPort, matchesServicePort(PORT_1));
+    }
+
+    /**
+     * Checks if decoding service port works properly.
+     */
+    @Test
+    public void testServicePortDecode() throws IOException {
+        ServicePort sPort = getServicePort("service-port.json");
+        assertThat(sPort.id(), is(PORT_ID_1));
+        assertThat(sPort.networkId(), is(NETWORK_ID_1));
+        assertThat(sPort.name(), is(PORT_NAME_1));
+        assertThat(sPort.ip(), is(IP_ADDRESS_1));
+        assertThat(sPort.mac(), is(MAC_ADDRESS_1));
+        assertThat(sPort.addressPairs(), is(ADDRESS_PAIRS_1));
+        assertThat(sPort.vlanId(), is(VLAN_ID_1));
+    }
+
+    /**
+     * Checks if decoding service port without ID fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeMissingId() {
+        final JsonNode jsonInvalidId = context.mapper().createObjectNode()
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort());
+        codec.decode((ObjectNode) jsonInvalidId, context);
+    }
+
+    /**
+     * Checks if decoding service port with empty ID fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeEmptyId() {
+        final JsonNode jsonInvalidId = context.mapper().createObjectNode()
+                .put(ID, "")
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort());
+        codec.decode((ObjectNode) jsonInvalidId, context);
+    }
+
+    /**
+     * Checks if decoding service port with null ID fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeNullId() {
+        final JsonNode jsonInvalidId = context.mapper().createObjectNode()
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(ID, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidId, context);
+    }
+
+    /**
+     * Checks if empty string is not allowed for network ID.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeEmptyNetworkId() {
+        final JsonNode jsonInvalidId = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, "")
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort());
+        codec.decode((ObjectNode) jsonInvalidId, context);
+    }
+
+    /**
+     * Checks if null is not allowed for network ID.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeNullNetworkId() {
+        final JsonNode jsonInvalidId = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(NETWORK_ID, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidId, context);
+    }
+
+    /**
+     * Checks if empty string is not allowed for port name.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeEmptyName() {
+        JsonNode jsonInvalidName = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, "")
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort());
+        codec.decode((ObjectNode) jsonInvalidName, context);
+    }
+
+    /**
+     * Checks if null is not allowed for port name.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeNullName() {
+        final JsonNode jsonInvalidName = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(NAME, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidName, context);
+    }
+
+    /**
+     * Checks if invalid IP address string is not allowed.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeInvalidIpAddress() {
+        final JsonNode jsonInvalidIp = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, "ipAddress")
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort());
+        codec.decode((ObjectNode) jsonInvalidIp, context);
+    }
+
+    /**
+     * Checks if invalid IP address string is not allowed.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeNullIpAddress() {
+        final JsonNode jsonInvalidIp = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(IP_ADDRESS, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidIp, context);
+    }
+
+    /**
+     * Checks if invalid MAC address string is not allowed.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeInvalidMacAddress() {
+        final JsonNode jsonInvalidMac = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, "macAddress")
+                .put(VLAN_ID, VLAN_ID_1.toShort());
+        codec.decode((ObjectNode) jsonInvalidMac, context);
+    }
+
+    /**
+     * Checks if null is not allowed for MAC address.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeNullMacAddress() {
+        final JsonNode jsonInvalidMac = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(MAC_ADDRESS, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidMac, context);
+    }
+
+    /**
+     * Checks if invalid VLAN ID is not allowed.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeInvalidVlanId() {
+        final JsonNode jsonInvalidVlan = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, "vlanId");
+        codec.decode((ObjectNode) jsonInvalidVlan, context);
+    }
+
+    /**
+     * Checks if null is not allowed for VLAN ID.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeNullVlanId() {
+        final JsonNode jsonInvalidVlan = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .set(VLAN_ID, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidVlan, context);
+    }
+
+    /**
+     * Checks if only array node is allowed for floating address pairs.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeNonArrayAddressPairs() {
+        final JsonNode jsonAddrPair = context.mapper().createObjectNode()
+                .put(IP_ADDRESS, ADDRESS_PAIR_1.ip().toString())
+                .put(MAC_ADDRESS, ADDRESS_PAIR_1.mac().toString());
+        final JsonNode jsonInvalidAddrPair = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(FLOATING_ADDRESS_PAIRS, jsonAddrPair);
+        codec.decode((ObjectNode) jsonInvalidAddrPair, context);
+    }
+
+    /**
+     * Checks if null and empty array node is allowed for floating address pairs.
+     */
+    @Test
+    public void testServicePortDecodeNullAndEmptyAddressPairs() {
+        JsonNode jsonInvalidAddrPair = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(FLOATING_ADDRESS_PAIRS, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidAddrPair, context);
+
+        jsonInvalidAddrPair = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(FLOATING_ADDRESS_PAIRS, context.mapper().createArrayNode());
+        codec.decode((ObjectNode) jsonInvalidAddrPair, context);
+    }
+
+    /**
+     * Checks if floating address pair without IP address field fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeMissingFloatingAddrPairIp() {
+        final ArrayNode jsonAddrPairs = context.mapper().createArrayNode();
+        final JsonNode jsonAddrPair = context.mapper().createObjectNode()
+                .put(MAC_ADDRESS, ADDRESS_PAIR_1.mac().toString());
+        jsonAddrPairs.add(jsonAddrPair);
+        final JsonNode jsonInvalidAddrPair = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(FLOATING_ADDRESS_PAIRS, jsonAddrPairs);
+        codec.decode((ObjectNode) jsonInvalidAddrPair, context);
+    }
+
+    /**
+     * Checks if floating address pair without MAC address field fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeMissingFloatingAddrPairMac() {
+        final ArrayNode jsonAddrPairs = context.mapper().createArrayNode();
+        final JsonNode jsonAddrPair = context.mapper().createObjectNode()
+                .put(IP_ADDRESS, ADDRESS_PAIR_1.ip().toString());
+        jsonAddrPairs.add(jsonAddrPair);
+        final JsonNode jsonInvalidAddrPair = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(FLOATING_ADDRESS_PAIRS, jsonAddrPairs);
+        codec.decode((ObjectNode) jsonInvalidAddrPair, context);
+    }
+
+    /**
+     * Checks if null IP address for floating address pair fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeNullFloatingAddrPairIp() {
+        final ArrayNode jsonAddrPairs = context.mapper().createArrayNode();
+        final JsonNode jsonAddrPair = context.mapper().createObjectNode()
+                .put(MAC_ADDRESS, ADDRESS_PAIR_1.mac().toString())
+                .set(IP_ADDRESS, NullNode.getInstance());
+        jsonAddrPairs.add(jsonAddrPair);
+        final JsonNode jsonInvalidAddrPair = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(FLOATING_ADDRESS_PAIRS, jsonAddrPairs);
+        codec.decode((ObjectNode) jsonInvalidAddrPair, context);
+    }
+
+    /**
+     * Checks if null MAC address for floating address pair fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeNullFloatingAddrPairMac() {
+        final ArrayNode jsonAddrPairs = context.mapper().createArrayNode();
+        final JsonNode jsonAddrPair = context.mapper().createObjectNode()
+                .put(IP_ADDRESS, ADDRESS_PAIR_1.ip().toString())
+                .set(MAC_ADDRESS, NullNode.getInstance());
+        jsonAddrPairs.add(jsonAddrPair);
+        final JsonNode jsonInvalidAddrPair = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(FLOATING_ADDRESS_PAIRS, jsonAddrPairs);
+        codec.decode((ObjectNode) jsonInvalidAddrPair, context);
+    }
+
+    /**
+     * Checks if invalid IP address for floating address pair fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeInvalidFloatingAddrPairIp() {
+        final ArrayNode jsonAddrPairs = context.mapper().createArrayNode();
+        final JsonNode jsonAddrPair = context.mapper().createObjectNode()
+                .put(IP_ADDRESS, "ipAddress")
+                .put(MAC_ADDRESS, ADDRESS_PAIR_1.mac().toString());
+        jsonAddrPairs.add(jsonAddrPair);
+        final JsonNode jsonInvalidAddrPair = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(FLOATING_ADDRESS_PAIRS, jsonAddrPairs);
+        codec.decode((ObjectNode) jsonInvalidAddrPair, context);
+    }
+
+    /**
+     * Checks if invalid MAC address for floating address pair fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeInvalidFloatingAddrPairMac() {
+        final ArrayNode jsonAddrPairs = context.mapper().createArrayNode();
+        final JsonNode jsonAddrPair = context.mapper().createObjectNode()
+                .put(IP_ADDRESS, ADDRESS_PAIR_1.ip().toString())
+                .put(MAC_ADDRESS, "macAddress");
+        jsonAddrPairs.add(jsonAddrPair);
+        final JsonNode jsonInvalidAddrPair = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(FLOATING_ADDRESS_PAIRS, jsonAddrPairs);
+        codec.decode((ObjectNode) jsonInvalidAddrPair, context);
+    }
+
+    private ServicePort getServicePort(String resource) throws IOException {
+        InputStream jsonStream = ServicePortCodecTest.class.getResourceAsStream(resource);
+        JsonNode jsonNode = context.mapper().readTree(jsonStream).get(SERVICE_PORT);
+        assertThat(jsonNode, notNullValue());
+
+        ServicePort sPort = codec.decode((ObjectNode) jsonNode, context);
+        assertThat(sPort, notNullValue());
+        return sPort;
+    }
+}
diff --git a/src/test/java/org/opencord/cordvtn/codec/ServicePortJsonMatcher.java b/src/test/java/org/opencord/cordvtn/codec/ServicePortJsonMatcher.java
new file mode 100644
index 0000000..08aa126
--- /dev/null
+++ b/src/test/java/org/opencord/cordvtn/codec/ServicePortJsonMatcher.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * 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.opencord.cordvtn.codec;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.NullNode;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.opencord.cordvtn.api.net.AddressPair;
+import org.opencord.cordvtn.api.net.ServicePort;
+
+import java.util.Objects;
+
+/**
+ * Json matcher for ServicePort.
+ */
+public final class ServicePortJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+    private final ServicePort port;
+
+    private ServicePortJsonMatcher(ServicePort port) {
+        this.port = port;
+    }
+
+    /**
+     * Factory to allocate ServicePort matcher.
+     *
+     * @param port service port object to match
+     * @return matcher
+     */
+    public static ServicePortJsonMatcher matchesServicePort(ServicePort port) {
+        return new ServicePortJsonMatcher(port);
+    }
+
+    @Override
+    protected boolean matchesSafely(JsonNode jsonNode, Description description) {
+        final String jsonPortId = jsonNode.get("id").asText();
+        if (!Objects.equals(jsonPortId, port.id().id())) {
+            description.appendText("Port id was " + jsonPortId);
+            return false;
+        }
+
+        final String jsonPortName = jsonNode.get("name").asText();
+        if (!Objects.equals(jsonPortName, port.name())) {
+            description.appendText("Port name was " + jsonPortName);
+            return false;
+        }
+
+        final String jsonPortNetId = jsonNode.get("network_id").asText();
+        if (!Objects.equals(jsonPortNetId, port.networkId().id())) {
+            description.appendText("Network id was " + jsonPortNetId);
+            return false;
+        }
+
+        final String jsonMacAddr = jsonNode.get("mac_address").asText();
+        if (!Objects.equals(jsonMacAddr, port.mac().toString())) {
+            description.appendText("MAC address was " + jsonMacAddr);
+            return false;
+        }
+
+        final String jsonIpAddr = jsonNode.get("ip_address").asText();
+        if (!Objects.equals(jsonIpAddr, port.ip().toString())) {
+            description.appendText("IP address was " + jsonIpAddr);
+            return false;
+        }
+
+        final String jsonVlanId = jsonNode.get("vlan_id").asText();
+        if (!Objects.equals(jsonVlanId, port.vlanId().toString())) {
+            description.appendText("VLAN id was " + jsonVlanId);
+            return false;
+        }
+
+        final JsonNode jsonAddrPairs = jsonNode.get("floating_address_pairs");
+        if (port.addressPairs().isEmpty()) {
+            if (jsonAddrPairs != null &&
+                    jsonAddrPairs != NullNode.getInstance() &&
+                    jsonAddrPairs.size() != 0) {
+                description.appendText("Floating address pairs did not match");
+                return false;
+            }
+        } else {
+            if (jsonAddrPairs == null ||
+                    jsonAddrPairs == NullNode.getInstance() ||
+                    jsonAddrPairs.size() == 0) {
+                description.appendText("Floating address pairs was empty");
+                return false;
+            } else if (jsonAddrPairs.size() != port.addressPairs().size()) {
+                description.appendText("Floating address pairs size was " +
+                        jsonAddrPairs.size());
+                return false;
+            } else {
+                for (JsonNode addrPair : jsonAddrPairs) {
+                    final AddressPair tmp = AddressPair.of(
+                            IpAddress.valueOf(addrPair.get("ip_address").asText()),
+                            MacAddress.valueOf(addrPair.get("mac_address").asText())
+                    );
+                    if (!port.addressPairs().contains(tmp)) {
+                        description.appendText("Floating address pairs did not match " + tmp);
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void describeTo(Description description) {
+        description.appendText(port.toString());
+    }
+}