添加模块服务
简介
控制器由一个负责监听openflowsocket并派发时间的核心模块,以及一些向核心模块注册用于处理响应事件的二级模块构成。当控制器启动时,可启用debuglog,进而看的这些二级模块的注册过程。
以下创建一个类利用一个buffer来存储近期收到的of消息,并提供rest API进行查询。
创建类
在Eclipse中添加类:
在floodlight项目中找到”src/main/java”文件
在 “src/main/java”文件下选择”New/Class.”
在packet中输入”net.floodlightcontroller.pktinhistory”
在name中输入”PktInHistory”。
在”Interfaces”中选择choose”Add…”,单击“chooseinterface”增加”IFloodlightListener”和”IFloodlightModule”,然后单击“OK”
最后单击“finish”。
得到程序框架。
设置模块依赖关系
模块需要监听openflow消息,因此需要向FloodlightProviderprotected注册,需要增加依赖关系,创建成员变量如下:
protected IFloodlightProviderService floodlightProvider;
然后将新增模块与模块加载相关联,通过完善getModuleDependencies()告知模块加载器在floodlight启动时自己加载。
@Override
public Collection<Class<? extends IFloodlightService>>getModuleDependencies() {
Collection<Class<?extends IFloodlightService>> l = new ArrayList<Class<?extends IFloodlightService>>();
l.add(IFloodlightProviderService.class);
return l;
}
初始化内部变量
@Override
public void init(FloodlightModuleContext context) throwsFloodlightModuleException {
floodlightProvider= context.getServiceImpl(IFloodlightProviderService.class);
}
处理OpenFlow消息
本部分实现对ofpacket_in消息的处理,利用一个buffer来存储近期收到的of消息,以备查询。
在startup()中注册监听器,告诉provider我们希望处理OF的PacketIn消息。
@Override
public void startUp(FloodlightModuleContext context) {
floodlightProvider.addOFMessageListener(OFType.PACKET_IN,this);
}
为OFMessage监听器提供id信息,需调用getName()。
@Override
public String getName() {
return "PktInHistory";
}
对CallbackOrderingPrereq()和isCallbackOrderingPostreq()的调用,只需让它们返回false,packetin消息处理链的执行顺序并不重要。
作为类内部变量,创建circularbuffer(import相关包),存储packetin消息。
protected ConcurrentCircularBuffer<SwitchMessagePair> buffer;
在初始化过程中初始化该变量。
@Override
public void init(FloodlightModuleContext context) throwsFloodlightModuleException {
floodlightProvider= context.getServiceImpl(IFloodlightProviderService.class);
buffer= newConcurrentCircularBuffer<SwitchMessagePair>(SwitchMessagePair.class,100);
}
最后实现模块接收到packetin消息时的处理动作。
@Override
public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContextcntx) {
switch(msg.getType()){
case PACKET_IN:
buffer.add(new SwitchMessagePair(sw, msg));
break;
default:
break;
}
return Command.CONTINUE;
}
每次packetin发生,其相应消息都会增加相关的交换机消息。该方法返回Command.CONTINUE 以告知IFloodlightProvider能将packetin发
给下一模块,若返回Command.STOP,则指消息就停在该模块不继续被继续处理。
添加restAPI
在实现了一个完整的模块之后,我们可以实现一个restapi,来获取该模块的相关信息。需要完成两件事情:利用创建的模块导出一个服务,并把该服务绑到RESTAPI模块。
具体说来,注册一个新的Restlet,包括
1.在net.floodlightcontroller.controller.internal.Controller中注册一个restlet。
2.实现一个*WebRoutable类。该类实现了RestletRoutable,并提供了getRestlet()和basePath()函数。
3.实现一个*Resource类,该类扩展了ServerResource(),并实现了@Get或@Put函数。
下面具体来看该如何实现。
创建并绑定接口IPktInHistoryService
首先在pktinhistory包中创建一个从IFloodlightService扩展出来的接口IPktInHistoryService(IPktInHistoryService.java),该服务拥有一个方法getBuffer(),来读取circularbuffer中的信息。
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();
}
现在回到原先创建的PktInHistory.java。相应类定义修订如下,让它具体实现IpktInHistoryService接口,
public class PktInHistory implements IFloodlightModule,IPktinHistoryService, IOFMessageListener {
并实现服务的getBuffer()方法。
@Override
public ConcurrentCircularBuffer<SwitchMessagePair> getBuffer() {
return buffer;
}
通过修改PktInHistory模块中getModuleServices()和getServiceImpls()方法通知模块系统,我们提供了IPktInHistoryService。
@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 = newHashMap<Class<? extends IFloodlightService>,IFloodlightService>();
m.put(IPktinHistoryService.class,this);
return m;
}
getServiceImpls()会告诉模块系统,本类(PktInHistory)是提供服务的类。
添加变量引用RESTAPI服务
之后,需要添加RESTAPI服务的引用(需要import相关包)。
protected IRestApiService restApi;
并添加IRestApiService作为依赖,这需要修改init()和getModuleDependencies()。
@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) throwsFloodlightModuleException {
floodlightProvider= context.getServiceImpl(IFloodlightProviderService.class);
restApi= context.getServiceImpl(IRestApiService.class);
buffer= new ConcurrentCircularBuffer<SwitchMessagePair>(SwitchMessagePair.class,100);
}
创建RESTAPI相关的类PktInHistoryResource和PktInHistoryWebRoutable
现在创建用在RESTAPI中的类,包括两部分,创建处理url call的类和注册到RESTAPI的类。
首先创建处理RESTAPI请求的类PktInHistoryResource(PktInHistoryResource.java)。当请求到达时,该类将返回circularbuffer中的内容。
packagenet.floodlightcontroller.pktinhistory;
importjava.util.ArrayList;
importjava.util.List;
importnet.floodlightcontroller.core.types.SwitchMessagePair;
importorg.restlet.resource.Get;
importorg.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;
}
}
现在创建PktInHistoryWebRoutable类(PktInHistoryWebRoutable.java),负责告诉RESTAPI我们注册了API并将它的URL绑定到指定的资源上。
packagenet.floodlightcontroller.pktinhistory;
importorg.restlet.Context;
importorg.restlet.Restlet;
importorg.restlet.routing.Router;
importnet.floodlightcontroller.restserver.RestletRoutable;
public class PktInHistoryWebRoutable implements RestletRoutable {
@Override
public Restlet getRestlet(Context context) {
Routerrouter = new Router(context);
router.attach("/history/json",PktInHistoryResource.class);
return router;
}
@Override
public String basePath() {
return"/wm/pktinhistory";
}
}
并将RestletPktInHistoryWebRoutable注册到RESTAPI服务,这通过修改PktInHistory类中的startUp()方法来完成。
@Override
public void startUp(FloodlightModuleContext context) {
floodlightProvider.addOFMessageListener(OFType.PACKET_IN,this);
restApi.addRestletRoutable(new PktInHistoryWebRoutable());
}
自定义序列化类
数据会被Jackson序列化为REST格式。如果需要指定部分序列化,需要自己实现序列化类OFSwitchImplJSONSerializer(OFSwitchImplJSONSerializer.java,位于net.floodlightcontroller.web.serialzers包中),并添加到net.floodlightcontroller.web.serialzers包。
packagenet.floodlightcontroller.core.web.serializers;
importjava.io.IOException;
importnet.floodlightcontroller.core.internal.OFSwitchImpl;
importorg.codehaus.jackson.JsonGenerator;
importorg.codehaus.jackson.JsonProcessingException;
importorg.codehaus.jackson.map.JsonSerializer;
importorg.codehaus.jackson.map.SerializerProvider;
importorg.openflow.util.HexString;
public class OFSwitchImplJSONSerializer extendsJsonSerializer<OFSwitchImpl> {
/**
*Handles serialization for OFSwitchImpl
*/
@Override
public void serialize(OFSwitchImpl switchImpl, JsonGenerator jGen,
SerializerProviderarg2) 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;
}
}
现在需要告诉Jackson使用我们的序列器。打开OFSwitchImpl.java(位于net.floodlightcontroller.core.internal包),修改如下(需要import我们创建的OFSwitchImplJSONSerializer包)
@JsonSerialize(using=OFSwitchImplJSONSerializer.class)
publicclass OFSwitchImpl implements IOFSwitch {
至此,新建模块基本完成,还需告诉loader我们的模块存在,添加模块名字到src/main/resources/META-INF/services/net.floodlight.core.module.IfloodlightModule。
net.floodlightcontroller.pktinhistory.PktInHistory
然后告知模块需要被加载。修改模块配置文件src/main/resources/floodlightdefault.properties中的floodlight.modules变量。
floodlight.modules= net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher,\
net.floodlightcontroller.forwarding.Forwarding,\
net.floodlightcontroller.pktinhistory.PktInHistory
启动mininet:
mn--controller=remote --ip=[Your IP Address] --mac --topo=tree,2
***Adding controller
***Creating network
***Adding hosts:
h1h2 h3 h4
***Adding switches:
s5s6 s7
***Adding links:
(h1,s6) (h2, s6) (h3, s7) (h4, s7) (s5, s6) (s5, s7)
***Configuring hosts
h1h2 h3 h4
***Starting controller
***Starting 3 switches
s5s6 s7
***Starting CLI:
启动后,运行:
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)
利用REST URL拿到结果:
$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....
附录:完整代码
package net.floodlightcontroller.pktinhistory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
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;
import net.floodlightcontroller.core.types.SwitchMessagePair;
import net.floodlightcontroller.core.IFloodlightProviderService;
import net.floodlightcontroller.restserver.IRestApiService;
import java.util.List;
import org.restlet.resource.Get;
import org.restlet.resource.ServerResource;
import org.restlet.Context;
import org.restlet.Restlet;
import org.restlet.routing.Router;
import net.floodlightcontroller.restserver.RestletRoutable;
public class PktInHistory implements IFloodlightModule, IOFMessageListener, IPktinHistoryService {
protected IFloodlightProviderService floodlightProvider;
protected ConcurrentCircularBuffer<SwitchMessagePair> buffer;
public ConcurrentCircularBuffer<SwitchMessagePair> getBuffer() {
return buffer;
}
protected IRestApiService restApi;
@Override
public String getName() {
return "PktInHistory";
}
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;
}
}
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";
}
}
@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) {
switch(msg.getType()) {
case PACKET_IN:
buffer.add(new SwitchMessagePair(sw, msg));
break;
default:
break;
}
return Command.CONTINUE;
}
@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;
}
@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);
}
@Override
public void startUp(FloodlightModuleContext context) {
floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
restApi.addRestletRoutable(new PktInHistoryWebRoutable());
}
}
ConcurrentCircularBuffer:
package net.floodlightcontroller.pktinhistory;
import java.util.concurrent.atomic.AtomicInteger;
import java.lang.reflect.Array;
public class ConcurrentCircularBuffer <T> {
private final AtomicInteger cursor = new AtomicInteger();
private final Object[] buffer;
private final Class<T> type;
public ConcurrentCircularBuffer (final Class <T> type,
final int bufferSize)
{
if (bufferSize < 1) {
throw new IllegalArgumentException(
"Buffer size must be a positive value"
);
}
this.type = type;
this.buffer = new Object [ bufferSize ];
}
public void add (T sample) {
buffer[ cursor.getAndIncrement() % buffer.length ] = sample;
}
@SuppressWarnings("unchecked")
public T[] snapshot () {
Object[] snapshots = new Object [ buffer.length ];
/* Identify the start-position of the buffer. */
int before = cursor.get();
/* Terminate early for an empty buffer. */
if (before == 0) {
return (T[]) Array.newInstance(type, 0);
}
System.arraycopy(buffer, 0, snapshots, 0, buffer.length);
int after = cursor.get();
int size = buffer.length - (after - before);
int snapshotCursor = before - 1;
/* The entire buffer was replaced during the copy. */
if (size <= 0) {
return (T[]) Array.newInstance(type, 0);
}
int start = snapshotCursor - (size - 1);
int end = snapshotCursor;
if (snapshotCursor < snapshots.length) {
size = snapshotCursor + 1;
start = 0;
}
/* Copy the sample snapshot to a new array the size of our stable
* snapshot area.
*/
T[] result = (T[]) Array.newInstance(type, size);
int startOfCopy = start % snapshots.length;
int endOfCopy = end % snapshots.length;
/* If the buffer space wraps the physical end of the array, use two
* copies to construct the new array.
*/
if (startOfCopy > endOfCopy) {
System.arraycopy(snapshots, startOfCopy,
result, 0,
snapshots.length - startOfCopy);
System.arraycopy(snapshots, 0,
result, (snapshots.length - startOfCopy),
endOfCopy + 1);
}
else {
/* Otherwise it's a single continuous segment, copy the whole thing
* into the result.
*/
System.arraycopy(snapshots, startOfCopy, result, 0, size);
}
return (T[]) result;
}
}