Advanced Tutorial

Skip to end of metadata
Go to start of metadata
These documents are deprecated and exist for archival purposes only. The documents are very likely out-dated. Use at your own risk!

This document will walk you through the process of adding a new module to Floodlight. Along the way, hopefully you'll learn a thing or two about how the controller architecture works.

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.openflow.protocol.OFMessage;
import org.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 int getId() {
        // TODO Auto-generated method stub
        return 0;
    }

    @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 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) 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.class.getSimpleName();
}

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);
}
You'll need to import the ConcurrentCircularBuffer into the PktInHistory package. It is available here.

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

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.

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 OFSwitchImpl 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 net.floodlightcontroller.core.internal.OFSwitchImpl;

import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;
import org.openflow.util.HexString;

public class OFSwitchImplJSONSerializer extends JsonSerializer<OFSwitchImpl> {

    /**
     * Handles serialization for OFSwitchImpl
     */
    @Override
    public void serialize(OFSwitchImpl switchImpl, JsonGenerator jGen,
                          SerializerProvider arg2) throws IOException,
                                                  JsonProcessingException {
        jGen.writeStartObject();
        jGen.writeStringField("dpid", HexString.toHexString(switchImpl.getId()));
        jGen.writeEndObject();
    }

    /**
     * Tells SimpleModule that we are the serializer for OFSwitchImpl
     */
    @Override
    public Class<OFSwitchImpl> handledType() {
        return OFSwitchImpl.class;
    }

}

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 OFSwitchImpl and annotate the class as of so.

@JsonSerialize(using=OFSwitchImplJSONSerializer.class)
public class OFSwitchImpl implements IOFSwitch {

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 file 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,
            "length": 96,
            "lengthU": 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,
            "length": 96,
            "lengthU": 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....
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.