Skip to content

REST API Programming Guide

Byungjoon Lee edited this page Apr 15, 2014 · 4 revisions

This document explains how to make a set of REST APIs that exports your module functionality to its users. In other words, you use these techniques to implement REST server-side functionality.

As explained in the Module Programming Guide, the key component that you should understand in our REST programming model is OFModel.

  • IRIS Model Programmers usually wraps their important data using OFModel.
  • By overriding the getAllRestApi method of OFModel, you can easily create REST APIs to manipulate your data.

The most basic implementation method

As you might already know, you should implement your REST API within the subclass code of OFModel. For example, OFMStateManager module holds a model class of State, which is a subclass of OFModel. A subclass of OFModel should return the list of REST APIs just like the following code.

private RESTApi[] apis = {
	new RESTApi(
		"/wm/core/controller/switches/json",
		new Restlet() {
			@Override
			public void handle(Request request, Response response) {
				StringWriter sWriter = new StringWriter();
				JsonFactory f = new JsonFactory();
				JsonGenerator g = null;
				try { 
					g = f.createJsonGenerator(sWriter);
				
					g.writeStartArray();
					for ( IOFSwitch sw : manager.getController().getSwitches() ) {
						g.writeStartObject();
						g.writeFieldName("dpid");
						g.writeString(HexString.toHexString(sw.getId()));
						g.writeFieldName("inetAddress");
						g.writeString(sw.getConnection().getClient().getRemoteAddress().toString());
						g.writeFieldName("connectedSince");
						g.writeNumber(sw.getConnectedSince().getTime());
						g.writeEndObject();
					}
					g.writeEndArray();
					g.close();
					
				} catch (IOException e) {
					e.printStackTrace();
				}
				
				String r = sWriter.toString();
				response.setEntity(r, MediaType.APPLICATION_JSON);
			}
		}
	),
        // ...
};

@Override
public RESTApi[] getAllRestApi() {
	return this.apis;
}

When you create a RESTApi object, you should pass two arguments. First is a URI that the RESTApi object will be bound to, and the second is a reference to the Restlet object.

Further, within the module, you should return the all references to all model objects, just like the following (which is part of OFMStateManager implementation).

public class OFMStateManager extends OFModule {
	
	private State state;

	@Override
	protected void initialize() {
		state = new State(this);
	}

	// ...

	@Override
	public OFModel[] getModels() {
		return new OFModel[] { this.state };
	}

}

An implementation method that does not use JsonGenerator

If you don’t want to use JsonGenerator (because it’s complexity), you can instead use a Wrapper class. You can find an example for this method within the OFMLinkDiscovery module (Links class).

/** 
 * class to convert Link information into JSON format.
 *
 */
class RESTLink {
	@JsonProperty("src-switch")
	public String srcdpid;
	
	@JsonProperty("src-port")
	public short srcport;
	
	@JsonProperty("src-port-state")
	public int srcstatus;
	
	@JsonProperty("dst-switch")
	public String dstdpid;
	
	@JsonProperty("dst-port")
	public short dstport;
	
	@JsonProperty("dst-port-state")
	public int dststatus;
	
	public String type;
	
	public RESTLink (Link l) {
		byte[] bDPID = ByteBuffer.allocate(8).putLong(l.getSrc()).array();
		this.srcdpid = String.format("%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
				bDPID[0], bDPID[1], bDPID[2], bDPID[3], bDPID[4], bDPID[5], bDPID[6], bDPID[7]);
		
		bDPID = ByteBuffer.allocate(8).putLong(l.getDst()).array();
		this.dstdpid = String.format("%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
				bDPID[0], bDPID[1], bDPID[2], bDPID[3], bDPID[4], bDPID[5], bDPID[6], bDPID[7]);
		
		this.srcport = l.getSrcPort();
		this.dstport = l.getDstPort();
		
		LinkInfo linkInfo = links.get( l );
		this.srcstatus = linkInfo.getSrcPortState();
		this.dststatus = linkInfo.getDstPortState();
		this.type = linkInfo.getLinkType().toString();
	}
}

private RESTApi[] apis = {
	new RESTApi(
		"/wm/topology/links/json",
		new Restlet() {
			@Override
			public void handle(Request request, Response response) {
				// create an object mapper.
				ObjectMapper om = new ObjectMapper();
					// retrieve all link information as JSON.
				List<RESTLink> list = new LinkedList<RESTLink>();
				for ( Link l : links.keySet() ){
					list.add( new RESTLink (l) );
				}
				try {
					String r = om.writeValueAsString(list);
					response.setEntity(r, MediaType.APPLICATION_JSON);
				} catch (Exception e) {
					e.printStackTrace();
					return;
				}
			}
		}
	),
       // ...
};

The annotation @JsonProperty is to change the name of the key when you export the content within the public member variable to JSON.

Custom Serializer-based method

If you don’t want to create serialization-only classes, you can instead use Custom Serializers.

final class OFFeaturesReplySerializer extends JsonSerializer<OFFeaturesReply> {
	@Override
	public void serialize(OFFeaturesReply reply, JsonGenerator jgen, SerializerProvider provider) 
	throws IOException, JsonProcessingException {
		
		jgen.writeStartObject();
		jgen.writeStringField("datapathId", HexString.toHexString(reply.getDatapathId()));
		jgen.writeNumberField("actions", reply.getActions());
		jgen.writeNumberField("buffers", reply.getBuffers());
		jgen.writeNumberField("capabilities", reply.getCapabilities());
		jgen.writeNumberField("length", reply.getLength());
		jgen.writeNumberField("tables", reply.getTables());
                jgen.writeStringField("type", reply.getType().toString());
                jgen.writeNumberField("version", reply.getVersion());
                jgen.writeNumberField("xid", reply.getXid());
		provider.defaultSerializeField("ports", reply.getPorts(), jgen);
                jgen.writeEndObject();
	}
}

final class OFPhysicalPortSerializer extends JsonSerializer<OFPhysicalPort> {
	
	@Override
	public void serialize(OFPhysicalPort port, JsonGenerator jgen, SerializerProvider provider) 
	throws IOException, JsonProcessingException {
		
		jgen.writeStartObject();
		jgen.writeNumberField("portNumber", port.getPortNumber());
		jgen.writeStringField("hardwareAddress", HexString.toHexString(port.getHardwareAddress()));
		jgen.writeStringField("name", port.getName());
		jgen.writeNumberField("config", port.getConfig());
		jgen.writeNumberField("state", port.getState());
		jgen.writeNumberField("currentFeatures", port.getCurrentFeatures());
		jgen.writeNumberField("advertisedFeatures", port.getAdvertisedFeatures());
		jgen.writeNumberField("supportedFeatures", port.getSupportedFeatures());
		jgen.writeNumberField("peerFeatures", port.getPeerFeatures());
		jgen.writeEndObject();
	}
}

final class OFFeaturesReplyModule extends SimpleModule {

	public OFFeaturesReplyModule() {
		super("OFFeaturesReplyModule", new Version(1, 0, 0, "OFFeaturesReplyModule"));
		
		addSerializer(OFFeaturesReply.class, new OFFeaturesReplySerializer());
		addSerializer(OFPhysicalPort.class, new OFPhysicalPortSerializer());
	}
	
}

The above is the code of Custom Serializers for OFFeatuersReply class and OFPhysicalPort class. OFFeaturesReplyModule is a custom serializer module that aggregates both class. To use it, we create the object just like the following.

private OFFeaturesReplyModule features_reply_module = new OFFeaturesReplyModule();

After that, you use the object when you create RESTApi objects. Just make sure you do not forget to call registerModule to om which is an object of type ObjectMapper.

...
new RESTApi(
	"/wm/core/switch/{switchid}/features/json",
	new Restlet() {
		@Override
		public void handle(Request request, Response response) {
			
			String switchIdStr = (String) request.getAttributes().get("switchid");
			Long switchId = HexString.toLong(switchIdStr);
			IOFSwitch sw = manager.getController().getSwitch(switchId);
			if ( sw == null ) {
				return;		// switch is not completely set up.
			}
					
			OFFeaturesReply reply = sw.getFeaturesReply();
				
			HashMap<String, OFFeaturesReply> result = new HashMap<String, OFFeaturesReply>();
			result.put( switchIdStr, reply );
					
			// create an object mapper.
			ObjectMapper om = new ObjectMapper();
			om.registerModule(features_reply_module);
				
			try {
				String r = om.writeValueAsString(result);
				response.setEntity(r, MediaType.APPLICATION_JSON);
			} catch (Exception e) {
				e.printStackTrace();
				return;
			}
		}
	}
),
...

Clone this wiki locally