Add JSON decoders for Host and HostLocation
- HostsWebResource will use the Host codec in parsing JSON for create-host requests
Change-Id: If51bf3433a4ab45889a94a6d11bbd3db6b96d074
(cherry picked from commit 46d2462e4e49855b0d533035250776589fd05d88)
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/HostCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/HostCodec.java
index d2890a4..dc6538e 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/HostCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/HostCodec.java
@@ -15,57 +15,136 @@
*/
package org.onosproject.codec.impl;
-import org.onlab.packet.IpAddress;
-import org.onosproject.codec.CodecContext;
-import org.onosproject.codec.JsonCodec;
-import org.onosproject.net.Host;
-import org.onosproject.net.HostLocation;
-
+import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.packet.EthType;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.provider.ProviderId;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
/**
- * Host JSON codec.
+ * JSON codec for Host class.
*/
public final class HostCodec extends AnnotatedCodec<Host> {
+ // JSON field names
+ public static final String HOST_ID = "id";
+ public static final String MAC = "mac";
+ public static final String VLAN = "vlan";
+ public static final String INNER_VLAN = "innerVlan";
+ public static final String OUTER_TPID = "outerTpid";
+ public static final String IS_CONFIGURED = "configured";
+ public static final String IS_SUSPENDED = "suspended";
+ public static final String IP_ADDRESSES = "ipAddresses";
+ public static final String HOST_LOCATIONS = "locations";
+ public static final String AUX_LOCATIONS = "auxLocations";
+
+ private static final String NULL_OBJECT_MSG = "Host cannot be null";
+ private static final String MISSING_MEMBER_MESSAGE = " member is required in Host";
+
@Override
public ObjectNode encode(Host host, CodecContext context) {
- checkNotNull(host, "Host cannot be null");
+ checkNotNull(host, NULL_OBJECT_MSG);
+
final JsonCodec<HostLocation> locationCodec =
context.codec(HostLocation.class);
+ // keep fields in string for compatibility
final ObjectNode result = context.mapper().createObjectNode()
- .put("id", host.id().toString())
- .put("mac", host.mac().toString())
- .put("vlan", host.vlan().toString())
- .put("innerVlan", host.innerVlan().toString())
- .put("outerTpid", host.tpid().toString())
- .put("configured", host.configured());
+ .put(HOST_ID, host.id().toString())
+ .put(MAC, host.mac().toString())
+ .put(VLAN, host.vlan().toString())
+ .put(INNER_VLAN, host.innerVlan().toString())
+ // use a 4-digit hex string in coding an ethernet type
+ .put(OUTER_TPID, String.format("0x%04x", host.tpid().toShort()))
+ .put(IS_CONFIGURED, host.configured())
+ .put(IS_SUSPENDED, host.suspended());
- final ArrayNode jsonIpAddresses = result.putArray("ipAddresses");
+ final ArrayNode jsonIpAddresses = result.putArray(IP_ADDRESSES);
for (final IpAddress ipAddress : host.ipAddresses()) {
jsonIpAddresses.add(ipAddress.toString());
}
- result.set("ipAddresses", jsonIpAddresses);
+ result.set(IP_ADDRESSES, jsonIpAddresses);
- final ArrayNode jsonLocations = result.putArray("locations");
+ final ArrayNode jsonLocations = result.putArray(HOST_LOCATIONS);
for (final HostLocation location : host.locations()) {
jsonLocations.add(locationCodec.encode(location, context));
}
- result.set("locations", jsonLocations);
+ result.set(HOST_LOCATIONS, jsonLocations);
if (host.auxLocations() != null) {
- final ArrayNode jsonAuxLocations = result.putArray("auxLocations");
+ final ArrayNode jsonAuxLocations = result.putArray(AUX_LOCATIONS);
for (final HostLocation auxLocation : host.auxLocations()) {
jsonAuxLocations.add(locationCodec.encode(auxLocation, context));
}
- result.set("auxLocations", jsonAuxLocations);
+ result.set(AUX_LOCATIONS, jsonAuxLocations);
}
return annotate(result, host, context);
}
-}
+ @Override
+ public Host decode(ObjectNode json, CodecContext context) {
+ if (json == null || !json.isObject()) {
+ return null;
+ }
+ MacAddress mac = MacAddress.valueOf(nullIsIllegal(
+ json.get(MAC), MAC + MISSING_MEMBER_MESSAGE).asText());
+ VlanId vlanId = VlanId.vlanId(nullIsIllegal(
+ json.get(VLAN), VLAN + MISSING_MEMBER_MESSAGE).asText());
+ HostId id = HostId.hostId(mac, vlanId);
+
+ ArrayNode locationNodes = nullIsIllegal(
+ (ArrayNode) json.get(HOST_LOCATIONS), HOST_LOCATIONS + MISSING_MEMBER_MESSAGE);
+ Set<HostLocation> hostLocations =
+ context.codec(HostLocation.class).decode(locationNodes, context)
+ .stream().collect(Collectors.toSet());
+
+ ArrayNode ipNodes = nullIsIllegal(
+ (ArrayNode) json.get(IP_ADDRESSES), IP_ADDRESSES + MISSING_MEMBER_MESSAGE);
+ Set<IpAddress> ips = new HashSet<>();
+ ipNodes.forEach(ipNode -> {
+ ips.add(IpAddress.valueOf(ipNode.asText()));
+ });
+
+ // check optional fields
+ JsonNode innerVlanIdNode = json.get(INNER_VLAN);
+ VlanId innerVlanId = (null == innerVlanIdNode) ? VlanId.NONE :
+ VlanId.vlanId(innerVlanIdNode.asText());
+ JsonNode outerTpidNode = json.get(OUTER_TPID);
+ EthType outerTpid = (null == outerTpidNode) ? EthType.EtherType.UNKNOWN.ethType() :
+ EthType.EtherType.lookup((short) (Integer.decode(outerTpidNode.asText()) & 0xFFFF)).ethType();
+ JsonNode configuredNode = json.get(IS_CONFIGURED);
+ boolean configured = (null == configuredNode) ? false : configuredNode.asBoolean();
+ JsonNode suspendedNode = json.get(IS_SUSPENDED);
+ boolean suspended = (null == suspendedNode) ? false : suspendedNode.asBoolean();
+
+ ArrayNode auxLocationNodes = (ArrayNode) json.get(AUX_LOCATIONS);
+ Set<HostLocation> auxHostLocations = (null == auxLocationNodes) ? null :
+ context.codec(HostLocation.class).decode(auxLocationNodes, context)
+ .stream().collect(Collectors.toSet());
+
+ Annotations annotations = extractAnnotations(json, context);
+
+ return new DefaultHost(ProviderId.NONE, id, mac, vlanId,
+ hostLocations, auxHostLocations, ips, innerVlanId,
+ outerTpid, configured, suspended, annotations);
+ }
+
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/HostLocationCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/HostLocationCodec.java
index efcd133..da4405d 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/HostLocationCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/HostLocationCodec.java
@@ -17,23 +17,45 @@
import org.onosproject.codec.CodecContext;
import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.DeviceId;
import org.onosproject.net.HostLocation;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.net.PortNumber;
import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
/**
- * Host JSON codec.
+ * HostLocation JSON codec.
*/
public final class HostLocationCodec extends JsonCodec<HostLocation> {
+ public static final String ELEMENT_ID = "elementId";
+ public static final String PORT = "port";
+
+ private static final String MISSING_MEMBER_MESSAGE =
+ " member is required in HostLocation";
+
@Override
public ObjectNode encode(HostLocation hostLocation, CodecContext context) {
checkNotNull(hostLocation, "Host location cannot be null");
return context.mapper().createObjectNode()
- .put("elementId", hostLocation.elementId().toString())
- .put("port", hostLocation.port().toString());
+ .put(ELEMENT_ID, hostLocation.elementId().toString())
+ .put(PORT, hostLocation.port().toString());
}
+ @Override
+ public HostLocation decode(ObjectNode json, CodecContext context) {
+ if (json == null || !json.isObject()) {
+ return null;
+ }
+
+ DeviceId deviceId = DeviceId.deviceId(nullIsIllegal(
+ json.get(ELEMENT_ID), ELEMENT_ID + MISSING_MEMBER_MESSAGE).asText());
+ PortNumber portNumber = PortNumber.portNumber(nullIsIllegal(
+ json.get(PORT), PORT + MISSING_MEMBER_MESSAGE).asText());
+
+ return new HostLocation(deviceId, portNumber, 0);
+ }
}
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/HostCodecTest.java b/core/common/src/test/java/org/onosproject/codec/impl/HostCodecTest.java
new file mode 100644
index 0000000..4edbc70
--- /dev/null
+++ b/core/common/src/test/java/org/onosproject/codec/impl/HostCodecTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2015-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.codec.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.junit.Test;
+import org.onlab.packet.EthType;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.provider.ProviderId;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.onosproject.codec.impl.JsonCodecUtils.assertJsonEncodable;
+
+/**
+ * Unit test for HostCodec.
+ */
+public class HostCodecTest {
+
+ // Make sure this Host and the corresponding JSON file have the same values
+ private static final MacAddress MAC;
+ private static final VlanId VLAN_ID;
+ private static final HostId HOST_ID;
+ private static final DeviceId DEVICE_ID;
+ private static final PortNumber PORT_NUM;
+ private static final Set<HostLocation> HOST_LOCATIONS = new HashSet<>();
+ private static final PortNumber AUX_PORT_NUM;
+ private static final Set<HostLocation> AUX_HOST_LOCATIONS = new HashSet<>();
+ private static final Set<IpAddress> IPS;
+ private static final VlanId INNER_VLAN_ID;
+ private static final EthType OUTER_TPID;
+ private static final Annotations ANNOTATIONS;
+ private static final Host HOST;
+
+ private MockCodecContext context = new MockCodecContext();
+ private JsonCodec<Host> hostCodec = context.codec(Host.class);
+
+ private static final String JSON_FILE = "simple-host.json";
+
+ static {
+ // Make sure these members have same values with the corresponding JSON fields
+ MAC = MacAddress.valueOf("46:E4:3C:A4:17:C8");
+ VLAN_ID = VlanId.vlanId("None");
+ HOST_ID = HostId.hostId(MAC, VLAN_ID);
+ DEVICE_ID = DeviceId.deviceId("of:0000000000000002");
+ PORT_NUM = PortNumber.portNumber("3");
+ HOST_LOCATIONS.add(new HostLocation(DEVICE_ID, PORT_NUM, 0));
+ AUX_PORT_NUM = PortNumber.portNumber("4");
+ AUX_HOST_LOCATIONS.add(new HostLocation(DEVICE_ID, AUX_PORT_NUM, 0));
+ IPS = new HashSet<>();
+ IPS.add(IpAddress.valueOf("127.0.0.1"));
+ INNER_VLAN_ID = VlanId.vlanId("10");
+ OUTER_TPID = EthType.EtherType.lookup((short) (Integer.decode("0x88a8") & 0xFFFF)).ethType();
+ ANNOTATIONS = DefaultAnnotations.builder().set("key1", "val1").build();
+ HOST = new DefaultHost(ProviderId.NONE, HOST_ID, MAC, VLAN_ID,
+ HOST_LOCATIONS, AUX_HOST_LOCATIONS, IPS, INNER_VLAN_ID,
+ OUTER_TPID, false, false, ANNOTATIONS);
+ }
+
+ @Test
+ public void testCodec() {
+ assertNotNull(hostCodec);
+ assertJsonEncodable(context, hostCodec, HOST);
+ }
+
+ @Test
+ public void testDecode() throws IOException {
+ InputStream jsonStream = HostCodec.class.getResourceAsStream(JSON_FILE);
+ JsonNode jsonString = context.mapper().readTree(jsonStream);
+
+ Host expected = hostCodec.decode((ObjectNode) jsonString, context);
+ assertEquals(expected, HOST);
+ }
+
+ @Test
+ public void testEncode() throws IOException {
+ InputStream jsonStream = HostCodec.class.getResourceAsStream(JSON_FILE);
+ JsonNode jsonString = context.mapper().readTree(jsonStream);
+
+ ObjectNode expected = hostCodec.encode(HOST, context);
+ // Host ID is not a field in Host but rather derived from MAC + VLAN.
+ // Derived information should not be part of the JSON really.
+ // However, we keep it as is for backward compatibility.
+ expected.remove(HostCodec.HOST_ID);
+
+ assertEquals(expected, jsonString);
+ }
+
+}
\ No newline at end of file
diff --git a/core/common/src/test/resources/org/onosproject/codec/impl/simple-host.json b/core/common/src/test/resources/org/onosproject/codec/impl/simple-host.json
new file mode 100644
index 0000000..bd547e0
--- /dev/null
+++ b/core/common/src/test/resources/org/onosproject/codec/impl/simple-host.json
@@ -0,0 +1,26 @@
+{
+ "mac": "46:E4:3C:A4:17:C8",
+ "vlan": "None",
+ "ipAddresses": [
+ "127.0.0.1"
+ ],
+ "locations": [
+ {
+ "elementId": "of:0000000000000002",
+ "port": "3"
+ }
+ ],
+ "auxLocations": [
+ {
+ "elementId": "of:0000000000000002",
+ "port": "4"
+ }
+ ],
+ "innerVlan": "10",
+ "outerTpid": "0x88a8",
+ "configured": false,
+ "suspended": false,
+ "annotations": {
+ "key1": "val1"
+ }
+}
diff --git a/web/api/src/main/java/org/onosproject/rest/resources/HostsWebResource.java b/web/api/src/main/java/org/onosproject/rest/resources/HostsWebResource.java
index 6bdd483..1dcbb5d 100644
--- a/web/api/src/main/java/org/onosproject/rest/resources/HostsWebResource.java
+++ b/web/api/src/main/java/org/onosproject/rest/resources/HostsWebResource.java
@@ -16,17 +16,9 @@
package org.onosproject.rest.resources;
import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
-import org.onlab.packet.EthType;
-import org.onlab.packet.IpAddress;
-import org.onlab.packet.MacAddress;
-import org.onlab.packet.VlanId;
-import org.onosproject.net.ConnectPoint;
-import org.onosproject.net.DefaultAnnotations;
import org.onosproject.net.Host;
import org.onosproject.net.HostId;
-import org.onosproject.net.HostLocation;
import org.onosproject.net.SparseAnnotations;
import org.onosproject.net.host.DefaultHostDescription;
import org.onosproject.net.host.HostAdminService;
@@ -52,10 +44,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
import static org.onlab.util.Tools.nullIsNotFound;
import static org.onlab.util.Tools.readTreeFromStream;
@@ -70,8 +58,6 @@
@Context
private UriInfo uriInfo;
private static final String HOST_NOT_FOUND = "Host is not found";
- private static final String[] REMOVAL_KEYS = {"mac", "vlan", "locations", "ipAddresses",
- "auxLocations", "innerVlan", "outerTpid"};
/**
* Get all end-station hosts.
@@ -221,107 +207,16 @@
* @return host ID of new host created
*/
private HostId parseHost(JsonNode node) {
- MacAddress mac = MacAddress.valueOf(node.get("mac").asText());
- VlanId vlanId = VlanId.vlanId((short) node.get("vlan").asInt(VlanId.UNTAGGED));
+ Host host = codec(Host.class).decode((ObjectNode) node, HostsWebResource.this);
- // Parse locations
- if (null == node.get("locations")) {
- throw new IllegalArgumentException("location isn't specified");
- }
- Iterator<JsonNode> locationNodes = node.get("locations").elements();
- Set<HostLocation> locations = new HashSet<>();
- while (locationNodes.hasNext()) {
- JsonNode locationNode = locationNodes.next();
- String deviceAndPort = locationNode.get("elementId").asText() + "/" +
- locationNode.get("port").asText();
- HostLocation hostLocation = new HostLocation(ConnectPoint.deviceConnectPoint(deviceAndPort), 0);
- locations.add(hostLocation);
- }
-
- // Parse ipAddresses
- if (null == node.get("ipAddresses")) {
- throw new IllegalArgumentException("ipAddress isn't specified");
- }
- Iterator<JsonNode> ipNodes = node.get("ipAddresses").elements();
- Set<IpAddress> ips = new HashSet<>();
- while (ipNodes.hasNext()) {
- ips.add(IpAddress.valueOf(ipNodes.next().asText()));
- }
-
- // Parse auxLocations
- Set<HostLocation> auxLocations;
- JsonNode auxLocationsNode = node.get("auxLocations");
- if (null == auxLocationsNode) {
- auxLocations = null;
- } else {
- Iterator<JsonNode> auxLocationNodes = auxLocationsNode.elements();
- auxLocations = new HashSet<>();
- while (auxLocationNodes.hasNext()) {
- JsonNode auxLocationNode = auxLocationNodes.next();
- String deviceAndPort = auxLocationNode.get("elementId").asText() + "/" +
- auxLocationNode.get("port").asText();
- HostLocation auxLocation = new HostLocation(ConnectPoint.deviceConnectPoint(deviceAndPort), 0);
- auxLocations.add(auxLocation);
- }
- }
-
- // Parse innerVlan
- JsonNode innerVlanNode = node.get("innerVlan");
- VlanId innerVlan = (null == innerVlanNode) ? VlanId.NONE : VlanId.vlanId(innerVlanNode.asText());
-
- // Parse outerTpid
- JsonNode outerTpidNode = node.get("outerTpid");
- EthType outerTpid = (null == outerTpidNode) ? EthType.EtherType.UNKNOWN.ethType() :
- EthType.EtherType.lookup((short) (Integer.decode(outerTpidNode.asText()) & 0xFFFF)).ethType();
-
- // try to remove elements from json node after reading them
- SparseAnnotations annotations = annotations(removeElements(node, REMOVAL_KEYS));
- // Update host inventory
-
- HostId hostId = HostId.hostId(mac, vlanId);
- DefaultHostDescription desc = new DefaultHostDescription(mac, vlanId, locations, auxLocations,
- ips, innerVlan, outerTpid, true, annotations);
+ HostId hostId = host.id();
+ DefaultHostDescription desc = new DefaultHostDescription(
+ host.mac(), host.vlan(), host.locations(), host.ipAddresses(), host.innerVlan(),
+ host.tpid(), host.configured(), (SparseAnnotations) host.annotations());
hostProviderService.hostDetected(hostId, desc, false);
+
return hostId;
}
-
- /**
- * Remove a set of elements from JsonNode by specifying keys.
- *
- * @param node JsonNode containing host information
- * @param removalKeys key of elements that need to be removed
- * @return removal keys
- */
- private JsonNode removeElements(JsonNode node, String[] removalKeys) {
- ObjectMapper mapper = new ObjectMapper();
- Map<String, Object> map = mapper.convertValue(node, Map.class);
- for (String key : removalKeys) {
- map.remove(key);
- }
- return mapper.convertValue(map, JsonNode.class);
- }
-
- /**
- * Produces annotations from specified JsonNode. Copied from the ConfigProvider
- * class for use in the POST method.
- *
- * @param node node to be annotated
- * @return SparseAnnotations object with information about node
- */
- private SparseAnnotations annotations(JsonNode node) {
- if (node == null) {
- return DefaultAnnotations.EMPTY;
- }
-
- DefaultAnnotations.Builder builder = DefaultAnnotations.builder();
- Iterator<String> it = node.fieldNames();
- while (it.hasNext()) {
- String k = it.next();
- builder.set(k, node.get(k).asText());
- }
- return builder.build();
- }
-
}
}
diff --git a/web/api/src/test/java/org/onosproject/rest/resources/HostResourceTest.java b/web/api/src/test/java/org/onosproject/rest/resources/HostResourceTest.java
index 808fb42..70c82fd 100644
--- a/web/api/src/test/java/org/onosproject/rest/resources/HostResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/resources/HostResourceTest.java
@@ -236,7 +236,7 @@
@Override
public boolean matchesSafely(JsonArray json) {
boolean hostFound = false;
- final int expectedAttributes = 8;
+ final int expectedAttributes = 9;
for (int jsonHostIndex = 0; jsonHostIndex < json.size();
jsonHostIndex++) {