HIGUCHI Yuta
Committed by Yuta HIGUCHI

Add support to decode Device, Port, Link JSON.

- Device, Port, Link can now be encoded and decoded back to Java Object,
  which will be Object#equals to the original.
- Modified DeviceServiceAdapter to be null-safe when possible
- Modified JSON assertion/matcher not to check for exact number of attributes

Change-Id: I7cf02e2254cf17f6265fb15847912519e564b14f
......@@ -17,12 +17,14 @@ package org.onosproject.net.device;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.MastershipRole;
import org.onosproject.net.Port;
import org.onosproject.net.PortNumber;
import java.util.Collections;
import java.util.List;
/**
......@@ -36,7 +38,7 @@ public class DeviceServiceAdapter implements DeviceService {
@Override
public Iterable<Device> getDevices() {
return null;
return Collections.emptyList();
}
@Override
......@@ -58,12 +60,12 @@ public class DeviceServiceAdapter implements DeviceService {
@Override
public MastershipRole getRole(DeviceId deviceId) {
return null;
return MastershipRole.NONE;
}
@Override
public List<Port> getPorts(DeviceId deviceId) {
return null;
return Collections.emptyList();
}
@Override
......
......@@ -16,10 +16,12 @@
package org.onosproject.codec.impl;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onosproject.codec.CodecContext;
import org.onosproject.codec.JsonCodec;
import org.onosproject.net.Annotated;
import org.onosproject.net.Annotations;
import org.onosproject.net.DefaultAnnotations;
/**
* Base JSON codec for annotated entities.
......@@ -42,4 +44,21 @@ public abstract class AnnotatedCodec<T extends Annotated> extends JsonCodec<T> {
return node;
}
/**
* Extracts annotations of given Object.
*
* @param objNode annotated JSON object node
* @param context decode context
* @return extracted Annotations
*/
protected Annotations extractAnnotations(ObjectNode objNode, CodecContext context) {
JsonCodec<Annotations> codec = context.codec(Annotations.class);
if (objNode.has("annotations") && objNode.isObject()) {
return codec.decode((ObjectNode) objNode.get("annotations"), context);
} else {
return DefaultAnnotations.EMPTY;
}
}
}
......
......@@ -16,9 +16,12 @@
package org.onosproject.codec.impl;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onosproject.codec.CodecContext;
import org.onosproject.codec.JsonCodec;
import org.onosproject.net.Annotations;
import org.onosproject.net.DefaultAnnotations;
import org.onosproject.net.DefaultAnnotations.Builder;
/**
* Annotations JSON codec.
......@@ -34,4 +37,13 @@ public final class AnnotationsCodec extends JsonCodec<Annotations> {
return result;
}
@Override
public Annotations decode(ObjectNode json, CodecContext context) {
Builder builder = DefaultAnnotations.builder();
json.fields().forEachRemaining(e ->
builder.set(e.getKey(), e.getValue().asText()));
return builder.build();
}
}
......
......@@ -16,32 +16,59 @@
package org.onosproject.codec.impl;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onosproject.codec.CodecContext;
import org.onosproject.codec.JsonCodec;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.ElementId;
import org.onosproject.net.HostId;
import org.onosproject.net.PortNumber;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onosproject.net.PortNumber.portNumber;
/**
* Connection point JSON codec.
*/
public final class ConnectPointCodec extends JsonCodec<ConnectPoint> {
// JSON field names
private static final String ELEMENT_HOST = "host";
private static final String ELEMENT_DEVICE = "device";
private static final String PORT = "port";
@Override
public ObjectNode encode(ConnectPoint point, CodecContext context) {
checkNotNull(point, "Connect point cannot be null");
ObjectNode root = context.mapper().createObjectNode()
.put("port", point.port().toString());
.put(PORT, point.port().toString());
if (point.elementId() instanceof DeviceId) {
root.put("device", point.deviceId().toString());
root.put(ELEMENT_DEVICE, point.deviceId().toString());
} else if (point.elementId() instanceof HostId) {
root.put("host", point.hostId().toString());
root.put(ELEMENT_HOST, point.hostId().toString());
}
return root;
}
@Override
public ConnectPoint decode(ObjectNode json, CodecContext context) {
if (json == null || !json.isObject()) {
return null;
}
ElementId elementId;
if (json.has(ELEMENT_DEVICE)) {
elementId = DeviceId.deviceId(json.get(ELEMENT_DEVICE).asText());
} else if (json.has(ELEMENT_HOST)) {
elementId = HostId.hostId(json.get(ELEMENT_HOST).asText());
} else {
// invalid JSON
return null;
}
PortNumber portNumber = portNumber(json.get(PORT).asText());
return new ConnectPoint(elementId, portNumber);
}
}
......
......@@ -16,32 +16,78 @@
package org.onosproject.codec.impl;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onlab.packet.ChassisId;
import org.onosproject.codec.CodecContext;
import org.onosproject.net.Annotations;
import org.onosproject.net.DefaultDevice;
import org.onosproject.net.Device;
import org.onosproject.net.Device.Type;
import org.onosproject.net.DeviceId;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.provider.ProviderId;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onosproject.net.DeviceId.deviceId;
/**
* Device JSON codec.
*/
public final class DeviceCodec extends AnnotatedCodec<Device> {
// JSON fieldNames
private static final String ID = "id";
private static final String TYPE = "type";
private static final String MFR = "mfr";
private static final String HW = "hw";
private static final String SW = "sw";
private static final String SERIAL = "serial";
private static final String CHASSIS_ID = "chassisId";
@Override
public ObjectNode encode(Device device, CodecContext context) {
checkNotNull(device, "Device cannot be null");
DeviceService service = context.get(DeviceService.class);
ObjectNode result = context.mapper().createObjectNode()
.put("id", device.id().toString())
.put("type", device.type().name())
.put(ID, device.id().toString())
.put(TYPE, device.type().name())
.put("available", service.isAvailable(device.id()))
.put("role", service.getRole(device.id()).toString())
.put("mfr", device.manufacturer())
.put("hw", device.hwVersion())
.put("sw", device.swVersion())
.put("serial", device.serialNumber())
.put("chassisId", device.chassisId().toString());
.put(MFR, device.manufacturer())
.put(HW, device.hwVersion())
.put(SW, device.swVersion())
.put(SERIAL, device.serialNumber())
.put(CHASSIS_ID, device.chassisId().toString());
return annotate(result, device, context);
}
/**
* {@inheritDoc}
*
* Note: ProviderId is not part of JSON representation.
* Returned object will have random ProviderId set.
*/
@Override
public Device decode(ObjectNode json, CodecContext context) {
if (json == null || !json.isObject()) {
return null;
}
DeviceId id = deviceId(json.get(ID).asText());
// TODO: add providerId to JSON if we need to recover them.
ProviderId pid = new ProviderId(id.uri().getScheme(), "DeviceCodec");
Type type = Type.valueOf(json.get(TYPE).asText());
String mfr = json.get(MFR).asText();
String hw = json.get(HW).asText();
String sw = json.get(SW).asText();
String serial = json.get(SERIAL).asText();
ChassisId chassisId = new ChassisId(json.get(CHASSIS_ID).asText());
Annotations annotations = extractAnnotations(json, context);
return new DefaultDevice(pid, id, type, mfr, hw, sw, serial,
chassisId, annotations);
}
}
......
......@@ -16,11 +16,15 @@
package org.onosproject.codec.impl;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onosproject.codec.CodecContext;
import org.onosproject.codec.JsonCodec;
import org.onosproject.net.Annotations;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultLink;
import org.onosproject.net.Link;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.Link.Type;
import org.onosproject.net.provider.ProviderId;
import static com.google.common.base.Preconditions.checkNotNull;
......@@ -29,15 +33,44 @@ import static com.google.common.base.Preconditions.checkNotNull;
*/
public final class LinkCodec extends AnnotatedCodec<Link> {
// JSON field names
private static final String SRC = "src";
private static final String DST = "dst";
private static final String TYPE = "type";
@Override
public ObjectNode encode(Link link, CodecContext context) {
checkNotNull(link, "Link cannot be null");
DeviceService service = context.get(DeviceService.class);
JsonCodec<ConnectPoint> codec = context.codec(ConnectPoint.class);
ObjectNode result = context.mapper().createObjectNode();
result.set("src", codec.encode(link.src(), context));
result.set("dst", codec.encode(link.dst(), context));
result.set(SRC, codec.encode(link.src(), context));
result.set(DST, codec.encode(link.dst(), context));
result.put(TYPE, link.type().toString());
return annotate(result, link, context);
}
/**
* {@inheritDoc}
*
* Note: ProviderId is not part of JSON representation.
* Returned object will have random ProviderId set.
*/
@Override
public Link decode(ObjectNode json, CodecContext context) {
if (json == null || !json.isObject()) {
return null;
}
JsonCodec<ConnectPoint> codec = context.codec(ConnectPoint.class);
// TODO: add providerId to JSON if we need to recover them.
ProviderId pid = new ProviderId("json", "LinkCodec");
ConnectPoint src = codec.decode((ObjectNode) json.get(SRC), context);
ConnectPoint dst = codec.decode((ObjectNode) json.get(DST), context);
Type type = Type.valueOf(json.get(TYPE).asText());
Annotations annotations = extractAnnotations(json, context);
return new DefaultLink(pid, src, dst, type, annotations);
}
}
......
......@@ -16,9 +16,18 @@
package org.onosproject.codec.impl;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onlab.packet.ChassisId;
import org.onosproject.codec.CodecContext;
import org.onosproject.net.Annotations;
import org.onosproject.net.DefaultAnnotations;
import org.onosproject.net.DefaultPort;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Port;
import org.onosproject.net.Port.Type;
import org.onosproject.net.PortNumber;
import org.onosproject.net.provider.ProviderId;
import static com.google.common.base.Preconditions.checkNotNull;
......@@ -27,19 +36,125 @@ import static com.google.common.base.Preconditions.checkNotNull;
*/
public final class PortCodec extends AnnotatedCodec<Port> {
// JSON field names
private static final String ELEMENT = "element"; // DeviceId
private static final String PORT_NAME = "port";
private static final String IS_ENABLED = "isEnabled";
private static final String TYPE = "type";
private static final String PORT_SPEED = "portSpeed";
// Special port name alias
private static final String PORT_NAME_LOCAL = "local";
@Override
public ObjectNode encode(Port port, CodecContext context) {
checkNotNull(port, "Port cannot be null");
ObjectNode result = context.mapper().createObjectNode()
.put("port", portName(port.number()))
.put("isEnabled", port.isEnabled())
.put("type", port.type().toString().toLowerCase())
.put("portSpeed", port.portSpeed());
.put(ELEMENT, port.element().id().toString())
.put(PORT_NAME, portName(port.number()))
.put(IS_ENABLED, port.isEnabled())
.put(TYPE, port.type().toString().toLowerCase())
.put(PORT_SPEED, port.portSpeed());
return annotate(result, port, context);
}
private String portName(PortNumber port) {
return port.equals(PortNumber.LOCAL) ? "local" : port.toString();
return port.equals(PortNumber.LOCAL) ? PORT_NAME_LOCAL : port.toString();
}
private static PortNumber portNumber(String portName) {
if (portName.equalsIgnoreCase(PORT_NAME_LOCAL)) {
return PortNumber.LOCAL;
}
return PortNumber.portNumber(portName);
}
/**
* {@inheritDoc}
*
* Note: Result of {@link Port#element()} returned Port object,
* is not a full Device object.
* Only it's DeviceId can be used.
*/
@Override
public Port decode(ObjectNode json, CodecContext context) {
if (json == null || !json.isObject()) {
return null;
}
DeviceId did = DeviceId.deviceId(json.get(ELEMENT).asText());
Device device = new DummyDevice(did);
PortNumber number = portNumber(json.get(PORT_NAME).asText());
boolean isEnabled = json.get(IS_ENABLED).asBoolean();
Type type = Type.valueOf(json.get(TYPE).asText().toUpperCase());
long portSpeed = json.get(PORT_SPEED).asLong();
Annotations annotations = extractAnnotations(json, context);
return new DefaultPort(device, number, isEnabled, type, portSpeed, annotations);
}
/**
* Dummy Device which only holds DeviceId.
*/
private static final class DummyDevice implements Device {
private final DeviceId did;
/**
* Constructs Dummy Device which only holds DeviceId.
*
* @param did device Id
*/
public DummyDevice(DeviceId did) {
this.did = did;
}
@Override
public Annotations annotations() {
return DefaultAnnotations.EMPTY;
}
@Override
public ProviderId providerId() {
return new ProviderId(did.uri().getScheme(), "PortCodec");
}
@Override
public DeviceId id() {
return did;
}
@Override
public Type type() {
return Type.SWITCH;
}
@Override
public String manufacturer() {
return "dummy";
}
@Override
public String hwVersion() {
return "0";
}
@Override
public String swVersion() {
return "0";
}
@Override
public String serialNumber() {
return "0";
}
@Override
public ChassisId chassisId() {
return new ChassisId();
}
}
}
......
/*
* Copyright 2015 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.onosproject.codec.impl;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.is;
import static org.onosproject.codec.impl.JsonCodecUtils.assertJsonEncodable;
import org.junit.Test;
import org.onosproject.codec.JsonCodec;
import org.onosproject.net.DefaultDevice;
import org.onosproject.net.Device;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.device.DeviceServiceAdapter;
/**
* Unit test for DeviceCodec.
*/
public class DeviceCodecTest {
private Device device = new DefaultDevice(JsonCodecUtils.PID,
JsonCodecUtils.DID1,
Device.Type.SWITCH,
JsonCodecUtils.MFR,
JsonCodecUtils.HW,
JsonCodecUtils.SW1,
JsonCodecUtils.SN,
JsonCodecUtils.CID,
JsonCodecUtils.A1);
@Test
public void deviceCodecTest() {
final MockCodecContext context = new MockCodecContext();
context.registerService(DeviceService.class, new DeviceServiceAdapter());
final JsonCodec<Device> codec = context.codec(Device.class);
assertThat(codec, is(notNullValue()));
final Device pojoIn = device;
assertJsonEncodable(context, codec, pojoIn);
}
}
/*
* Copyright 2015 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.onosproject.codec.impl;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.onosproject.net.DeviceId.deviceId;
import org.onlab.packet.ChassisId;
import org.onosproject.codec.CodecContext;
import org.onosproject.codec.JsonCodec;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultAnnotations;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.SparseAnnotations;
import org.onosproject.net.provider.ProviderId;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* JsonCodec test utilities.
*/
public abstract class JsonCodecUtils {
/**
* Checks if given Object can be encoded to JSON and back.
*
* @param context CodecContext
* @param codec JsonCodec
* @param pojoIn Java Object to encode.
* Object is expected to have #equals implemented.
*/
public static <T> void assertJsonEncodable(final CodecContext context,
final JsonCodec<T> codec,
final T pojoIn) {
final ObjectNode json = codec.encode(pojoIn, context);
assertThat(json, is(notNullValue()));
final T pojoOut = codec.decode(json, context);
assertThat(pojoOut, is(notNullValue()));
assertEquals(pojoIn, pojoOut);
}
static final ProviderId PID = new ProviderId("of", "foo");
static final ProviderId PIDA = new ProviderId("of", "bar", true);
static final DeviceId DID1 = deviceId("of:foo");
static final DeviceId DID2 = deviceId("of:bar");
static final String MFR = "whitebox";
static final String HW = "1.1.x";
static final String SW1 = "3.8.1";
static final String SW2 = "3.9.5";
static final String SN = "43311-12345";
static final ChassisId CID = new ChassisId();
static final PortNumber P1 = PortNumber.portNumber(1);
static final PortNumber P2 = PortNumber.portNumber(2);
static final PortNumber P3 = PortNumber.portNumber(3);
static final SparseAnnotations A1 = DefaultAnnotations.builder()
.set("A1", "a1")
.set("B1", "b1")
.build();
static final ConnectPoint CP1 = new ConnectPoint(DID1, P1);
static final ConnectPoint CP2 = new ConnectPoint(DID2, P2);
}
/*
* Copyright 2015 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.onosproject.codec.impl;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.onosproject.codec.impl.JsonCodecUtils.assertJsonEncodable;
import org.junit.Test;
import org.onosproject.codec.JsonCodec;
import org.onosproject.net.DefaultLink;
import org.onosproject.net.Link;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.device.DeviceServiceAdapter;
/**
* Unit test for LinkCodec.
*/
public class LinkCodecTest {
private final Link link = new DefaultLink(JsonCodecUtils.PID,
JsonCodecUtils.CP1,
JsonCodecUtils.CP2,
Link.Type.DIRECT,
Link.State.ACTIVE,
false,
JsonCodecUtils.A1);
@Test
public void linkCodecTest() {
final MockCodecContext context = new MockCodecContext();
context.registerService(DeviceService.class, new DeviceServiceAdapter());
final JsonCodec<Link> codec = context.codec(Link.class);
assertThat(codec, is(notNullValue()));
final Link pojoIn = link;
assertJsonEncodable(context, codec, pojoIn);
}
}
......@@ -15,6 +15,9 @@
*/
package org.onosproject.codec.impl;
import java.util.HashMap;
import java.util.Map;
import org.onosproject.codec.CodecContext;
import org.onosproject.codec.JsonCodec;
......@@ -25,8 +28,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
*/
public class MockCodecContext implements CodecContext {
private ObjectMapper mapper = new ObjectMapper();
private CodecManager manager = new CodecManager();
private final ObjectMapper mapper = new ObjectMapper();
private final CodecManager manager = new CodecManager();
private final Map<Class<? extends Object>, Object> services = new HashMap<>();
/**
* Constructs a new mock codec context.
......@@ -46,9 +50,15 @@ public class MockCodecContext implements CodecContext {
return manager.getCodec(entityClass);
}
@SuppressWarnings("unchecked")
@Override
public <T> T get(Class<T> serviceClass) {
return null;
return (T) services.get(serviceClass);
}
// for registering mock services
public <T> void registerService(Class<T> serviceClass, T impl) {
services.put(serviceClass, impl);
}
}
......
/*
* Copyright 2015 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.onosproject.codec.impl;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.onosproject.codec.impl.JsonCodecUtils.assertJsonEncodable;
import org.junit.Test;
import org.onosproject.codec.JsonCodec;
import org.onosproject.net.DefaultDevice;
import org.onosproject.net.DefaultPort;
import org.onosproject.net.Device;
import org.onosproject.net.Port;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.device.DeviceServiceAdapter;
/**
* Unit test for PortCodec.
*/
public class PortCodecTest {
private final Device device = new DefaultDevice(JsonCodecUtils.PID,
JsonCodecUtils.DID1,
Device.Type.SWITCH,
JsonCodecUtils.MFR,
JsonCodecUtils.HW,
JsonCodecUtils.SW1,
JsonCodecUtils.SN,
JsonCodecUtils.CID,
JsonCodecUtils.A1);
private final Port port = new DefaultPort(device,
JsonCodecUtils.P1,
true,
JsonCodecUtils.A1);
@Test
public void portCodecTest() {
final MockCodecContext context = new MockCodecContext();
context.registerService(DeviceService.class, new DeviceServiceAdapter());
final JsonCodec<Port> codec = context.codec(Port.class);
assertThat(codec, is(notNullValue()));
final Port pojoIn = port;
assertJsonEncodable(context, codec, pojoIn);
}
}
......@@ -343,7 +343,6 @@ public class DevicesResourceTest extends ResourceTest {
for (int portIndex = 0; portIndex < jsonPorts.size(); portIndex++) {
JsonObject jsonPort = jsonPorts.get(portIndex).asObject();
assertThat(jsonPort.size(), is(4));
assertThat(jsonPort.get("port").asString(),
is(Integer.toString(portIndex + 1)));
assertThat(jsonPort.get("isEnabled").asBoolean(),
......
......@@ -123,11 +123,6 @@ public class LinksResourceTest extends ResourceTest {
JsonObject jsonLink = json.get(jsonLinkIndex).asObject();
if (jsonLink.names().size() != expectedAttributes) {
reason = "Found a link with the wrong number of attributes";
return false;
}
if (matchesLink(link).matchesSafely(jsonLink)) {
return true;
}
......