How to Add Services to a Module

This tutorial is for Floodlight v1.0 and later. If you need to use Floodlight v0.91 or earlier, you can find the v0.91 and v0.90 documentation here

Table of Contents


Introduction

The controller architecture consists of a core module, that's responsible for listening to the openflow sockets and dispatching events, and a number of secondary modules that can register with the core module to handle those events. When you first start up the controller with debug logging enabled, you'll see a report of all the handlers as they register themselves:

17:29:23.231 [main] DEBUG n.f.core.internal.Controller - OFListeners for PACKET_IN: devicemanager,
17:29:23.231 [main] DEBUG n.f.core.internal.Controller - OFListeners for PORT_STATUS: devicemanager,
17:29:23.237 [main] DEBUG n.f.c.module.FloodlightModuleLoader - Starting net.floodlightcontroller.restserver.RestApiServer
17:29:23.237 [main] DEBUG n.f.c.module.FloodlightModuleLoader - Starting net.floodlightcontroller.forwarding.Forwarding
17:29:23.237 [main] DEBUG n.f.forwarding.Forwarding - Starting net.floodlightcontroller.forwarding.Forwarding
17:29:23.237 [main] DEBUG n.f.core.internal.Controller - OFListeners for PACKET_IN: devicemanager,forwarding,
17:29:23.237 [main] DEBUG n.f.c.module.FloodlightModuleLoader - Starting net.floodlightcontroller.storage.memory.MemoryStorageSource
17:29:23.240 [main] DEBUG n.f.restserver.RestApiServer - Adding REST API routable net.floodlightcontroller.storage.web.StorageWebRoutable
17:29:23.242 [main] DEBUG n.f.c.module.FloodlightModuleLoader - Starting net.floodlightcontroller.core.OFMessageFilterManager
17:29:23.242 [main] DEBUG n.f.core.internal.Controller - OFListeners for PACKET_IN: devicemanager,forwarding,messageFilterManager,
17:29:23.242 [main] DEBUG n.f.core.internal.Controller - OFListeners for PACKET_OUT: messageFilterManager,
17:29:23.242 [main] DEBUG n.f.core.internal.Controller - OFListeners for FLOW_MOD: messageFilterManager,
17:29:23.242 [main] DEBUG n.f.c.module.FloodlightModuleLoader - Starting net.floodlightcontroller.routing.dijkstra.RoutingImpl
17:29:23.247 [main] DEBUG n.f.c.module.FloodlightModuleLoader - Starting net.floodlightcontroller.core.CoreModule
17:29:23.248 [main] DEBUG n.f.core.internal.Controller - Doing controller internal setup
17:29:23.251 [main] INFO  n.f.core.internal.Controller - Connected to storage source
17:29:23.252 [main] DEBUG n.f.restserver.RestApiServer - Adding REST API routable net.floodlightcontroller.core.web.CoreWebRoutable
17:29:23.252 [main] DEBUG n.f.c.module.FloodlightModuleLoader - Starting net.floodlightcontroller.topology.internal.TopologyImpl
17:29:23.254 [main] DEBUG n.f.core.internal.Controller - OFListeners for PACKET_IN: topology,devicemanager,forwarding,messageFilterManager,
17:29:23.254 [main] DEBUG n.f.core.internal.Controller - OFListeners for PORT_STATUS: devicemanager,topology,

There are a number of different types of OpenFlow messages for which events are generated, but most of the action happens in the PacketIn handlers. A PacketIn message is the OpenFlow message that is sent from the switch to the controller if the switch does not have a flow table rule that matches the packet. The controller is expected to handle the packet and to install any necessary flows table entries (using a set of FlowMod messages). In this tutorial, we'll be adding a new PacketIn listener will store the PacketIn messages. We  will then make these messages available via the REST API.

Creating the Class

Add class in Eclipse

  1. Expand the "Floodlight" item in the Package Explorer and find the "src/main/java" folder.
  2. Right-click on the "src/main/java" folder and choose "New/Class."
  3. Enter "net.floodlightcontroller.pktinhistory" in the "Package" box.
  4. Enter "PktInHistory" in the "name" box.
  5. Next to the "Interfaces" box, choose "Add..."
  6. Type "IFloodlightModule" into the search box, and choose "OK."
  7. Repeat steps above for "IOFMessageListener"
  8. Click "Finish" in the dialog.

You should end up with something like this:

package net.floodlightcontroller.pktinhistory;

import java.util.Collection;
import java.util.Map;

import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFType;

import net.floodlightcontroller.core.FloodlightContext;
import net.floodlightcontroller.core.IOFMessageListener;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.module.FloodlightModuleContext;
import net.floodlightcontroller.core.module.FloodlightModuleException;
import net.floodlightcontroller.core.module.IFloodlightModule;
import net.floodlightcontroller.core.module.IFloodlightService;

public class PktInHistory implements IFloodlightModule, IOFMessageListener {

	@Override
	public String getName() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean isCallbackOrderingPrereq(OFType type, String name) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean isCallbackOrderingPostreq(OFType type, String name) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public net.floodlightcontroller.core.IListener.Command receive(
			IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Collection<Class<? extends IFloodlightService>> getModuleServices() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void init(FloodlightModuleContext context)
			throws FloodlightModuleException {
		// TODO Auto-generated method stub
	}

	@Override
	public void startUp(FloodlightModuleContext context) {
		// TODO Auto-generated method stub
	}
}

Setting up Module Dependencies

Now that we have our skeleton class, we have to implement the correct functions to make the module loadable. Since we are listening to OpenFlow messages we need to register with the FloodlightProvider (IFloodlightProviderService class). First we need to add it as a dependency. We create a member variable as so.

protected IFloodlightProviderService floodlightProvider;

Now we need to wire it up from the module loading system. First we have to tell the module loader we depend upon on. For that we'll modify the getModuleDependencies() function.

@Override
public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
    Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>();
    l.add(IFloodlightProviderService.class);
    return l;
}

Now that we've told the module loader we depend upon the IFloodlightProviderService we have to get the reference to it. We also create our internal datastructure (the Circular Buffer, shown later) here. This is done in the init method.

@Override
public void init(FloodlightModuleContext context) throws FloodlightModuleException {
    floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
}

Handling OpenFlow messages

Now that's we got a reference to the Floodlight provider we need to tell it we want to handle OpenFlow's PacketIn messages. We do this in the startUp() method.

@Override
public void startUp(FloodlightModuleContext context) {
    floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
}

We also have to put in our ID for the OFMessage listener. This is done in the getName() call.

@Override
public String getName() {
    return "PktInHistory";
}

For isCallbackOrderingPrereq() and isCallbackOrderingPostreq() calls we'll just leave them to return false. This is saying we do not care what order are in for the PacketIn processing chain.
We create a circular buffer to handle store the Packet In messages. Define the buffer as a member variable.

protected ConcurrentCircularBuffer<SwitchMessagePair> buffer;

Instantiate it in the init method that will now look like this.

@Override
public void init(FloodlightModuleContext context) throws FloodlightModuleException {
    floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
    buffer = new ConcurrentCircularBuffer<SwitchMessagePair>(SwitchMessagePair.class, 100);
}

(warning)

You'll need to import the ConcurrentCircularBuffer into the PktInHistory package. It is available here. If your Floodlight version does not include this class, you can add the ConcurrentCircularBuffer.java file within the net.floodlightcontroller.util package of your controller.

Now we have to define the behavior for what to do when the module receives a PacketIn message. This is done in the receive() function.

@Override
public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
    switch(msg.getType()) {
         case PACKET_IN:
             buffer.add(new SwitchMessagePair(sw, msg));
             break;
        default:
            break;
     }
     return Command.CONTINUE;
}

For each PacketIn we will add the message along with the switch it came in on. Returning Command.CONTINUE tells the IFloodlightProvider to pass the PacketIn to the next module in the system. Returning Command.STOP will tell it to stop processing here.

Adding a REST API

You can find a more in-depth REST API tutorial here if you are curious about the 'how' and 'why' behind some things you're required to do below.


We now have a complete module implementation, but no way to get information out of it! For this we'll need to do two things. Have our module export a service then tie it into the REST API Module.

In a nutshell, the steps to add a new Restlet include:

  • Registering a Restlet in net.floodlightcontroller.controller.internal.Controller. 
  • Implement a *WebRoutable class that implements RestletRoutable and provides getRestlet() and basePath() functions.
  • Implement a *Resource class that extends ServerResource() and implements a @Get or @Put function.

We'll create an interface called IPktInHistoryService and have it extend IFloodlightService. We use the naming convention of appending Service to any interface that extends IFloodlightService. The service will have one method, getBuffer() to retrieve the circular buffer we use. The interface will look like this.

package net.floodlightcontroller.pktinhistory;

import net.floodlightcontroller.core.module.IFloodlightService;
import net.floodlightcontroller.core.types.SwitchMessagePair;

public interface IPktinHistoryService extends IFloodlightService {
    public ConcurrentCircularBuffer<SwitchMessagePair> getBuffer();
}

Going back to our PktInHistory module we now need to do a couple things. One is make it implement the IPktInHistoryService. Our class definition will now look like this.

public class PktInHistory implements IFloodlightModule, IPktinHistoryService, IOFMessageListener {

We also have to implement the service's getBuffer() method.

@Override
public ConcurrentCircularBuffer<SwitchMessagePair> getBuffer() {
    return buffer;
}

We tell the module system that we provide the IPktInHistoryService. We modify the getModuleServices() and getServiceImpls() methods.

@Override
public Collection<Class<? extends IFloodlightService>> getModuleServices() {
    Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>();
    l.add(IPktinHistoryService.class);
    return l;
}

@Override
public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
    Map<Class<? extends IFloodlightService>, IFloodlightService> m = new HashMap<Class<? extends IFloodlightService>, IFloodlightService>();
    m.put(IPktinHistoryService.class, this);
    return m;
}

The getServiceImpls() call tells the module system that we are the class that provides the service.

We also need to get add a reference to the REST API service.

protected IRestApiService restApi;

We'll have to add the IRestApiService as a dependency and wire it up the same way we did with the IFloodlightProviderService. The init() and  getModuleDependencies() methods will now look like this.

@Override
public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
    Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>();
    l.add(IFloodlightProviderService.class);
    l.add(IRestApiService.class);
    return l;
}

@Override
public void init(FloodlightModuleContext context) throws FloodlightModuleException {
    floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
    restApi = context.getServiceImpl(IRestApiService.class);
    buffer = new ConcurrentCircularBuffer<SwitchMessagePair>(SwitchMessagePair.class, 100);
}

Now we create the classes that are used in the REST API. There are two parts. The first is a class that handles a specific URL call and another class that registers this with the REST API.

First we'll create the class that handles the REST API request. When a request comes in it will just return a snapshot of the circular buffer.

package net.floodlightcontroller.pktinhistory;

import java.util.ArrayList;
import java.util.List;

import net.floodlightcontroller.core.types.SwitchMessagePair;

import org.restlet.resource.Get;
import org.restlet.resource.ServerResource;

public class PktInHistoryResource extends ServerResource {
    @Get("json")
    public List<SwitchMessagePair> retrieve() {
        IPktinHistoryService pihr = (IPktinHistoryService)getContext().getAttributes().get(IPktinHistoryService.class.getCanonicalName());
        List<SwitchMessagePair> l = new ArrayList<SwitchMessagePair>();
        l.addAll(java.util.Arrays.asList(pihr.getBuffer().snapshot()));
        return l;
    }
}

Now we have to create the RestletRoutable. This tells the REST API we are registering this API and bind's it's URLs to a specific resource. The class looks like this.

package net.floodlightcontroller.pktinhistory;

import org.restlet.Context;
import org.restlet.Restlet;
import org.restlet.routing.Router;

import net.floodlightcontroller.restserver.RestletRoutable;

public class PktInHistoryWebRoutable implements RestletRoutable {
    @Override
    public Restlet getRestlet(Context context) {
        Router router = new Router(context);
        router.attach("/history/json", PktInHistoryResource.class);
        return router;
    }

    @Override
    public String basePath() {
        return "/wm/pktinhistory";
    }
}

Now we need to register our Restlet Routable with the REST API service. We'll modify our startUp() method to do that. It will now look like this.

@Override
public void startUp(FloodlightModuleContext context) {
    floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
    restApi.addRestletRoutable(new PktInHistoryWebRoutable());
}

The data is serialized into REST format using Jackson. Jackson will look at a class and try to serialize every field that has a getter. In this case we don't want everything to be serialized in IOFSwitch or OFMessage, so we'll need to write a custom serializer. We will add it to the net.floodlightcontroller.web.serialzers package.

package net.floodlightcontroller.core.web.serializers;

import java.io.IOException;

import org.projectfloodlight.openflow.protocol.OFPacketIn;
import org.projectfloodlight.openflow.protocol.OFPacketInReason;
import org.projectfloodlight.openflow.protocol.OFType;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import net.floodlightcontroller.core.types.SwitchMessagePair;
import net.floodlightcontroller.util.OFMessageUtils;

public class SwitchMessagePairSerializer extends JsonSerializer<SwitchMessagePair> {

    @Override
    public void serialize(SwitchMessagePair m, JsonGenerator jGen, SerializerProvider arg2)
            throws IOException, JsonProcessingException {
        jGen.writeStartObject();

        jGen.writeFieldName("message");
        jGen.writeStartObject();
        if (m.getMessage().getType() == OFType.PACKET_IN) {
        	jGen.writeNumberField("bufferId", ((OFPacketIn)m.getMessage()).getBufferId().getInt());
        	jGen.writeNumberField("inPort", OFMessageUtils.getInPort((OFPacketIn)m.getMessage()).getPortNumber());
        	jGen.writeNumberField("packetDataLength", ((OFPacketIn)m.getMessage()).getData().length);
        	jGen.writeBinaryField("packetData", ((OFPacketIn)m.getMessage()).getData());
            jGen.writeStringField("reason", ((OFPacketIn)m.getMessage()).getReason().toString());
        	jGen.writeNumberField("totalLength", ((OFPacketIn)m.getMessage()).getTotalLen());
        }
        jGen.writeStringField("type", m.getMessage().getType().toString());
        jGen.writeStringField("version", m.getMessage().getVersion().toString());
        jGen.writeNumberField("xid", m.getMessage().getXid());
        jGen.writeEndObject();

        jGen.writeFieldName("switch");
        jGen.writeStartObject();
        jGen.writeStringField("dpid", m.getSwitch().getId().toString());
        jGen.writeEndObject();

        jGen.writeEndObject();
    }
}

Now we have to tell Jackson to use the serializer. We do this by annotating our class which will tell Jackson to use our serializer. Open up SwitchMessagePair and annotate the class as depicted below.

@JsonSerialize(using=SwitchMessagePairSerializer.class)
public class SwitchMessagePair {

Loading the module

We're almost done, we just need to tell Floodlight to load the module on startup. First we have to tell the loader that the module exists. This is done by adding the fully qualified module name on it's own line in src/main/resources/META-INF/services/net.floodlight.core.module.IFloodlightModule. We open that file and append this line.

net.floodlightcontroller.pktinhistory.PktInHistory

Then we tell the module to be loaded. We modify the Floodlight module configuration file. The default one is src/main/resources/floodlightdefault.properties. The key is floodlight.modules and the value is a comma separated list of fully qualified module names. After we add PktInHistory it will look something like this.

floodlight.modules = net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher,\
net.floodlightcontroller.forwarding.Forwarding,\
net.floodlightcontroller.pktinhistory.PktInHistory

This is just a sample excerpt from a configuration file. As Floodlight is under rapid development this file may not reflect the most current configuration required to run. Instead of using this configuration exactly, just append the PktInHistory module to the current list in the configuration file.

Testing with mininet

We can also try testing the whole controller by connecting mininet to it. Run the controller as before, then from inside a controller vm:

$ sudo mn --controller=remote --ip=[Your IP Address] --mac --topo=tree,2
*** Adding controller
*** Creating network
*** Adding hosts:
h1 h2 h3 h4
*** Adding switches:
s5 s6 s7
*** Adding links:
(h1, s6) (h2, s6) (h3, s7) (h4, s7) (s5, s6) (s5, s7)
*** Configuring hosts
h1 h2 h3 h4
*** Starting controller
*** Starting 3 switches
s5 s6 s7
*** Starting CLI:

At the mininet prompt, run:

mininet> pingall
*** Ping: testing ping reachability
h1 -> h2 h3 h4
h2 -> h1 h3 h4
h3 -> h1 h2 h4
h4 -> h1 h2 h3
*** Results: 0% dropped (0/12 lost)

Now we will hit the REST URL and get our results.

$ curl -s http://localhost:8080/wm/pktinhistory/history/json | python -mjson.tool
[
    {
        "message": {
            "bufferId": 256,
            "inPort": 2,
            "packetDataLength": 96,
            "packetData": "MzP/Uk+PLoqIUk+Pht1gAAAAABg6/wAAAAAAAAAAAAAAAAAAAAD/AgAAAAAAAAAAAAH/Uk+PhwAo2gAAAAD+gAAAAAAAACyKiP/+Uk+P",
            "reason": "NO_MATCH",
            "totalLength": 78,
            "type": "PACKET_IN",
            "version": 1,
            "xid": 0
        },
        "switch": {
            "dpid": "00:00:00:00:00:00:00:06"
        }
    },
    {
        "message": {
            "bufferId": 260,
            "inPort": 1,
            "packetDataLength": 96,
            "packetData": "MzP/Uk+PLoqIUk+Pht1gAAAAABg6/wAAAAAAAAAAAAAAAAAAAAD/AgAAAAAAAAAAAAH/Uk+PhwAo2gAAAAD+gAAAAAAAACyKiP/+Uk+P",
            "reason": "NO_MATCH",
            "totalLength": 78,
            "type": "PACKET_IN",
            "version": 1,
            "xid": 0
        },
        "switch": {
            "dpid": "00:00:00:00:00:00:00:05"
        }
    },

etc....etc....