http://www.frank-durr.de/?p=84
學弟推薦的一個非常詳細的opendaylight開發入門教程,這裡盡力翻譯一下,為了自己更好的了解。
在這個教程裡,我會詳細解釋如何為opendaylight開發一個OSGI元件來實作正常的網絡控制邏輯。與REST 接口不同,當一個packet到達并在交換裝置流表中失配的時候,将會觸發一個packet-in事件,OSGI元件接收packet-in事件。是以,為了實作流響應式變成,OSGI元件是研究OpenDaylight一個很好的切入口。
即使是對于有經驗的java程式員,開發OSGI元件的學習曲線也是相當陡峭的,OpenDaylight使用十分強大的開發工具,例如Maven和OSGI、這些程式架構都十分的複雜,衆多的java類也是一個巨大的挑戰。然而,正如你将在本教程裡看到的這樣,由于maven提供的幫助,開發過程是很簡單明了的。
我将會一步一步展示一個簡單的OSGI元件的開發過程。這個元件并不做什麼貢獻,隻是簡單的呈現一個收到的ipv4資料包的目的位址。資料路徑id(data path id),以及ingress port。然而,你可以學習到許多能夠幫助你今後開發自己的控制元件的知識,例如
- 如何搭建一個opendaylight maven 工程?
- 如何 在opendaylight運作時刻安裝(install),解除安裝(uninstall),開啟(start),結束(stop)一個OSGI bundle?
- 如何管理OSGI元件依賴和生命周期?
- 如何通過data packet listenners接收packet-in事件?
- 如何使用Opendaylight Data Packet Service解析資料包?
這裡應該提一下,我使用opendaylight的API驅動的服務抽象層(API-Driven Service Abstraction Layer SAL),OpenDaylight 還實作了一個模型驅動的SAL。在後面的教程中将會涉及。
the big picture
下圖展示了我們的系統架構。它由一系列将java類,資源,配置檔案捆綁在一起的bundles組成。其中,MyControlApp是我們在這個教程中将要開發的。其他的bundles來自OpenDaylight工程,例如SAL bundles。
bundles在OSGI架構中運作(Opendaylight中的Equinox)。OSGI最有趣的地方在于bundles可以在運作時安裝和解除安裝,因而我們無需停下SDN控制器來增加或修改控制邏輯
。

我們可以看到,OSGI bundles可以提供可供其他OSGI元件調用的服務。其中我們要使用的是Data Packet Service(interface IDataPacketService)來解析資料包。
盡管我們的簡單控制元件并沒有給其他bundle提供任何功能,但是我們必須知道,假如想要收到packet-in事件,我們必須實作IListenDataPacket接口。到一個OpenFlow packet-in消息到達控制器的時候,SAL将會激活實作了IListenDataPacket接口的元件。當然,其中就有我們的bundle。
Prerequisites
在我們開始開發我們的元件之前,我們應該拿到opendaylight可運作版本。http://www.opendaylight.org/software/downloads可以從以下位址中獲得,或者你也可以從opendaylight GIT倉庫中獲得,并自己編譯
[email protected]:$ git clone https://git.opendaylight.org/gerrit/p/controller.git
[email protected]:$ cd ./controller/opendaylight/distribution/opendaylight/
[email protected]:$ mvn clean install
其實要開發一個opendaylight OSGI元件,你并不需要拿到opendaylight源碼。我們下面将會介紹,我們隻需要将要用到的元件從opendaylight倉庫中以Jar包的形式導入就可以了。
在編譯的過程中,你将會看到Maven下載下傳很多java packages。如果你從來沒有用過Maven,可能會對此感到困惑,我們剛才難道沒有用Git下載下傳整個工程嗎?實際上,Maven可以從遠端倉庫自動的下載下傳工程依賴項(庫,plugins)并且将它們放進你的本地倉庫,通常會位于~/.m2。如果你在編譯完opendaylight之後檢視這個倉庫,你将會看到所有Maven下載下傳下來的庫檔案。
例如,你可能會看到Maven下載下傳了Apache Xerces XML解析器,我們将會在讨論工程依賴的時候解釋這些依賴項。
Creating the Maven Project
現在,我們開始開發我們的OSGI 元件。因為Opendaylight 是基于Maven的。是以我們最好也使用Maven 來開發自己的工程。是以我們首先為我們的OSGI元件建立一個Maven工程。首先我們建立如下目錄結構,我們用~/myctrlapp來代表該元件的根目錄。
myctrlapp
|--src
|--main
|--java
|--de
|--frank_durr
|--myctrlapp
顯然,Java實作在src/main/java中,我們使用de.frank_durr.myctrlapp來實作我們的控制元件。
Maven中有個重要檔案叫POM(project object model)我們在~/myctrlapp檔案夾中建立pom.xml檔案。内容如下:這裡建議參考源教程,拷貝過來之後縮進有些問題。
<pre name="code" class="html"><?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.frank_durr</groupId>
<artifactId>myctrlapp</artifactId>
<version>0.1</version>
<packaging>bundle</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.7</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>
*
</Import-Package>
<Export-Package>
de.frank_durr.myctrlapp
</Export-Package>
<Bundle-Activator>
de.frank_durr.myctrlapp.Activator
</Bundle-Activator>
</instructions>
<manifestLocation>${project.basedir}/META-INF</manifestLocation>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<artifactId>sal</artifactId>
<version>0.7.0</version>
</dependency>
</dependencies>
<repositories>
<!-- OpenDaylight releases -->
<repository>
<id>opendaylight-mirror</id>
<name>opendaylight-mirror</name>
<url>http://nexus.opendaylight.org/content/groups/public/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
</releases>
</repository>
<!-- OpenDaylight snapshots -->
<repository>
<id>opendaylight-snapshot</id>
<name>opendaylight-snapshot</name>
<url>http://nexus.opendaylight.org/content/repositories/opendaylight.snapshot/</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
</project>
首先,我們定義自己的group id(我們組織的唯一id),以及artifact id(我們工程的名字)以及一個 版本号version number。
在Maven建立的過程中,plugins被激活。其中一個Apache Felix 非常重要,它建立了我們的OSGI工程,它指明了每一個需要導入的包,通配符*導入了每一個在bundle中不存在,但是在bundle中有引用過的bundle。這比指定每一個imports更加合理,簡便。另外,我們導出我們package中的所有實作。
bundle activator在bundle生命周期中的每一次 start與stop時調用。下面,我會介紹如何使用activator來注冊我們的服務,如何向外提供我們元件的接口。
dependency元素指明了我們的元件的依賴項。還記得我說過Maven會自動下載下傳所有需要的庫(jars)到你的本地倉庫嗎~/m2?當然,隻有你告訴Maven你需要什麼,它才會這麼做。我們隻需要opendaylight的SAL。opendaylight 工程給我們的倉庫提供了一個已經編譯好的元件,是以,Maven會從遠端倉庫下載下傳JARs。是以,我們并不需要将所有的opendaylight工程源碼導入到Eclipse!~在我的例子中,我使用了0.7.0版本,你也可以将它改成0.7.0-SNAPSHOT來使用快照版本。
從POM檔案中,我們可以建立一個Eclipse工程
執行一下指令:
[email protected]:$ cd ~/myctrlapp
[email protected]:$ mvn eclipse:eclipse
當你改動了pom檔案的時候,一定要重新執行以上指令。接下來,你可以将工程導入到Eclipse當中了。
Menu Import / General / Existing projects into workspace
Select root folder ~/myctrlapp
Implementation of OSGi Component: The Activator
要實作我們的OSGI元件,我們還需要兩個類檔案:一個OSGI activator,來向OSGI架構注冊我們的元件,一個packet handler來實作控制邏輯以及當packet-in事件來到的時候執行相應的動作。
首先我們在~/myctrlapp/src/main/java/frank_durr/myctrlapp檔案夾下建立Activator.java檔案。(代碼參見原教程)
<pre name="code" class="java">package de.frank_durr.myctrlapp;
import java.util.Dictionary;
import java.util.Hashtable;
import org.apache.felix.dm.*;
import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
import org.opendaylight.controller.sal.packet.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Activator extends ComponentActivatorAbstractBase{
private static final Logger log = LoggerFactory.getLogger(PacketHandler.class);
public Object[] getImplementations(){
log.trace("getting implementations");
Object[] res ={PacketHandler.class};
return res;
}
public void configureInstance(Component c,Object imp,String containerName){
log.trace("Configuring instance");
if(imp.equals(PacketHandler.class)){
Dictionary<String,Object> props=new Hashtable<String,Object>();
props.put("salListenerName","mypackethandler");
c.setInterface(new String[] {IListenDataPacket.class.getName()},props);
c.add(createContainerServiceDependency(containerName).setService(IDataPacketService.class).setCallbacks("setDataPacketService","unsetDataPacketService").setRequired(true));
}
}
}
我們擴充opendaylight controller中的基本類 ComponentActivatorAbstractBase。已經熟悉OSGI的開發者知道當budle啟動或停止的時候,OSGI架構會相應調用兩個方法start()和stop()。這兩個方法在ComponentActivatorAbstractBase裡面被重寫以管理opendaylight元件的生命周期。getImplementations()和configureInstance()将在這過程中被調用。
getImplementations()方法傳回實作這個bundle元件的class。一個bundle可以實作超過1個元件,例如,一個bundle中可以包括一個ARP請求的packethandler元件以及一個IP資料包的packethandler元件。我們的bundle中隻實作了一個元件,該元件僅僅響應packet-in事件,由我們的PacketHandler類來實作,是以我們隻傳回了一個實作。
configureInstance()方法配置了元件,并且,聲明了導出的服務接口以及使用到的服務。因為一個OSGIbundle可以實作多于一個元件,是以在26行這裡最好檢查一下應該配置哪個元件。
接下來我們聲明我們的元件導出的服務。回憶一下,為了接收packet-in事件,元件必須要實作IListenDataPacket接口。因而,在34行,
我們明确的将我們的元件注冊為packet-in事件的監聽者。另外,在31行,我們使用salListenerName屬性為我們的listenner命名,如果你想要知道注冊時的細節,建議你可以檢視org.opendaylight.controller.sal.implementation.internal.DataPacketService類裡面的setListenDataPacket()方法,你将會看到,packet handler将會相繼的被調用,可能會有很多元件注冊了packet-in事件。你不能期望opendaylight 在其他元件的listenner收到事件通知前先調用你的listenner。listenners被調用的順序并沒有被指定。但是你可以利用salListennerDependency屬性來創造dependency list。另外,你可以使用屬性salListennerFilter。你可以給listener設定一個org.opendaylight.controller.sal.match.Match對象來按照包首部過濾packets,否則的話你将會收到所有的packet(如果在我們的handler被調用前沒有被其他的listenner消耗掉的話(consume))。
在configureInstance()方法中 除了導出我們的packet listenner實作,我們也可以指定我們使用到的其他的服務,這些依賴在37行聲明,在我們的例子當中,我們隻用到了實作了IDataPacketService接口的服務。你可能會問:”我如何得到提供該服務的對象來調用服務呢?“ 你可以定義兩個回調函數作為你的元件(PacketHandler)類的一部分。這裡稱為setDataPacketService()和unsetDataPacketService()這兩個函數在引用服務将被調用。(PacketHandler的實作在下面)
Implementation of OSGi Component: The Packet Handler
我們實作的第二個部分就是packetHandler。它接收packet-in事件,(這個類你已經在activator裡面配置過了)。我們在這個目錄下建立PacketHandler.java檔案:~/myctrlapp/src/main/java/de/frank_durr/myctrlapp.
代碼如下:
<pre name="code" class="java">package de.frank_durr.myctrlapp;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.opendaylight.controller.sal.core.Node;
import org.opendaylight.controller.sal.core.NodeConnector;
import org.opendaylight.controller.sal.packet.Ethernet;
import org.opendaylight.controller.sal.packet.IDataPacketService;
import org.opendaylight.controller.sal.packet.IListenDataPacket;
import org.opendaylight.controller.sal.packet.IPv4;
import org.opendaylight.controller.sal.packet.Packet;
import org.opendaylight.controller.sal.packet.PacketResult;
import org.opendaylight.controller.sal.packet.RawPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PacketHandler implements IListenDataPacket {
private static final Logger log = LoggerFactory.getLogger(PacketHandler.class);
private IDataPacketService dataPacketService;
static private InetAddress intToInetAddress(int i) {
byte b[] = new byte[] { (byte) ((i>>24)&0xff), (byte) ((i>>16)&0xff), (byte) ((i>>8)&0xff), (byte) (i&0xff) };
InetAddress addr;
try {
addr = InetAddress.getByAddress(b);
} catch (UnknownHostException e) {
return null;
}
return addr;
}
/*
* Sets a reference to the requested DataPacketService
* See Activator.configureInstance(...):
* c.add(createContainerServiceDependency(containerName).setService(
* IDataPacketService.class).setCallbacks(
* "setDataPacketService", "unsetDataPacketService")
* .setRequired(true));
*/
void setDataPacketService(IDataPacketService s) {
log.trace("Set DataPacketService.");
dataPacketService = s;
}
/*
* Unsets DataPacketService
* See Activator.configureInstance(...):
* c.add(createContainerServiceDependency(containerName).setService(
* IDataPacketService.class).setCallbacks(
* "setDataPacketService", "unsetDataPacketService")
* .setRequired(true));
*/
void unsetDataPacketService(IDataPacketService s) {
log.trace("Removed DataPacketService.");
if (dataPacketService == s) {
dataPacketService = null;
}
}
@Override
public PacketResult receiveDataPacket(RawPacket inPkt) {
//System.out.println("Received data packet.");
// The connector, the packet came from ("port")
NodeConnector ingressConnector = inPkt.getIncomingNodeConnector();
// The node that received the packet ("switch")
Node node = ingressConnector.getNode();
// Use DataPacketService to decode the packet.
Packet l2pkt = dataPacketService.decodeDataPacket(inPkt);
if (l2pkt instanceof Ethernet) {
Object l3Pkt = l2pkt.getPayload();
if (l3Pkt instanceof IPv4) {
IPv4 ipv4Pkt = (IPv4) l3Pkt;
int dstAddr = ipv4Pkt.getDestinationAddress();
InetAddress addr = intToInetAddress(dstAddr);
System.out.println("Pkt. to " + addr.toString() + " received by node " + node.getNodeIDString() + " on connector " + ingressConnector.getNodeConnectorIDString());
return PacketResult.KEEP_PROCESSING;
}
}
// We did not process the packet -> let someone else do the job.
return PacketResult.IGNORED;
}
}
我們可以看到,我們的handler實作了IListenDataPacket接口, 這個接口聲明了recieveDataPacket()函數,該函數在packet-in事件到達的時候被自動調用,參數為 raw packet。
要解析raw packet,我們需要使用OpenDaylight Data Packet Service.正如在Activator中描述的那樣,在元件的配置過程中,我們在handler實作中設定了兩個回調函數,setDataPacketService()以及unsetDataPacketService()。當我們需要data packet service的時候,setDataPacketservice将會被調用,用來解析raw packet。當我們收到raw packet “inPkt”的時候。我們會調用dataPacketService.decodeDataPacket(inPkt)來獲得一個L2資料幀。使用 instanceof 。我們可以檢查這個資料包的類型。如果是以太網資料幀,我們獲得這個資料幀中的payload(我的了解就是去掉二層的標頭和尾,得到L3資料包)。再一次檢查類型,如果是IPV4資料包,我們輸出目的IP。
另外,這個例子展示了如何獲得收到資料包的node(交換機節點)以及connector(端口号)
最後,我們決定這個資料包應該繼續被後續handler處理或者消耗掉這個packet并傳回一個相應的值。PacketResult.KEEP_PROCESSING說明我們的handler已經處理好了這個packet。接下來其他的handler也可以繼續處理。PacketResult.CONSUME表示,在我們處理完之後其他的handler不能再處理了。PacketResult.IGNORED說明。packet 處理流水線應當繼續進行,而且我們并沒有處理這個資料包。
Deploying the OSGI Bundle
我們已經實作了我們的元件,可以使用maven來編譯打包:
- [email protected]:$ cd ~/myctrlapp
- [email protected]:$ mvn package
如果我們的POM檔案和代碼都是正确的話,以上指令就會建立正确的JAR檔案:~/myctrlapp/target/myctrlapp-0.1.jar
這個bundle現在可以被安裝到OSGI架構當中(opendaylight中Equinox)首先我們啟動控制器。
[email protected]:$ cd ~/controller/opendaylight/distribution/opendaylight/target/distribution.opendaylight-osgipackage/opendaylight/
[email protected]:$ ./runs.sh
然後install我們的bundle
osgi> install file:/home/user/myctrlapp/target/myctrlapp-0.1.jar
Bundle id is 256
可以看到我們的編号是256
現在我們啟動budle
osgi> start 256
你可以檢查一下現在正在運作的OSGIbundle。使用指令:
osgi> ss
類似的你可以stop和uninstall這個bundle使用如下指令:
osgi> stop 256
osgi> uninstall 256
在我們開始測試這個bundle之前,我們stopOpendaylight的兩個服務。Simple Forwarding Service和LoadBalancing Service:
osgi> <span style="color:#CC0000;">ss | grep simple</span>
171 ACTIVE org.opendaylight.controller.samples.simpleforwarding_0.4.1.SNAPSHOT
true
osgi> stop 171
osgi> <span style="color:#FF0000;">osgi> ss | grep loadbalance</span>r
150 ACTIVE org.opendaylight.controller.samples.loadbalancer.northbound_0.4.1.SNAPSHOT
187 ACTIVE org.opendaylight.controller.samples.loadbalancer_0.5.1.SNAPSHOT
true
osgi> stop 187
為什麼要這麼做呢,因為這兩項服務也實作了packet listenner,為了測試,我們必須要確定這兩個服務沒有把packet consume掉,否則的話我們就不能取得資料包。
Testing
我們使用簡單的linear Mininet 拓撲隻有兩個交換機,兩個host。
[email protected]:$ sudo mn --controller=remote,ip=129.69.210.89 --topo linear,2
這個ip 填控制器的ip
現在我們用host1 ping host2 然後看osgi控制台的輸出。
mininet> h1 ping h2
osgi>
Pkt. to /10.0.0.2 received by node 00:00:00:00:00:00:00:01 on connector 1
Pkt. to /10.0.0.1 received by node 00:00:00:00:00:00:00:02 on connector 1
我們可以看到我們的handler從兩個交換機都接收到了資料包,data path id 為00:00:00:00:00:00:00:01和00:00:00:00:00:00:00:02以及,目的ip為10.0.0.2與10.0.0.1都是從port1接收到的。
至此!一個簡單的OSGI bundle就完成了!!