開發OpenDaylight組件的完整流程

在前面介紹學習了OpenDaylight的幾個重要模塊後,這裏再來介紹下完整開發一個模塊的過程。html

 

OSGI的bundles提供被其餘OSGI組件調用的服務。這個教程中展現的是Data Packet Service去解析數據包(其接口爲 IDataPacketService)。java

這個小組件不能提供函數供其餘bundles調用,可是可讓咱們很好的理解爲了接收到一個
Packet-in消息事件,必需要實現 IListenDataPacket 接口。當Packet-in消息到達控制器,SAL模塊就會通知實現這些接口的組件。node

詳細過程請見   http://www.frank-durr.de/?p=84   實驗室的婷婷學姐翻譯了此篇文章,就直接貼過來了,供你們學習git

譯文程序員

在這個教程裏,我會詳細解釋如何爲opendaylight開發一個OSGI組件來實現常規的網絡控制邏輯。與REST 接口不一樣,當一個packet到達並在交換設備流表中失配的時候,將會觸發一個packet-in事件,OSGI組件接收packet-in事件。所以,爲了實現流響應式編程,OSGI組件是研究OpenDaylight一個很好的切入口。apache

 

即便是對於有經驗的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。app

    bundles在OSGI框架中運行(Opendaylight中的Equinox)。OSGI最有趣的地方在於bundles能夠在運行時安裝和卸載,於是咱們無需停下SDN控制器來增長或修改控制邏輯

opendaylight-osgi

 

咱們能夠看到,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倉庫中得到,並本身編譯

 

  1. user@host:$ git clone https://git.opendaylight.org/gerrit/p/controller.git  
  2. user@host:$ cd ./controller/opendaylight/distribution/opendaylight/  
  3. user@host:$ 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來表明該組件的根目錄。

  1. myctrlapp  
  2.     |--src  
  3.           |--main  
  4.                    |--java  
  5.                              |--de  
  6.                                   |--frank_durr  
  7.                                              |--myctrlapp  


    顯然,Java實如今src/main/java中,咱們使用de.frank_durr.myctrlapp來實現咱們的控制組件。

 

    Maven中有個重要文件叫POM(project object model)咱們在~/myctrlapp文件夾中建立pom.xml文件。內容以下:這裏建議參考源教程,拷貝過來以後縮進有些問題。

[html] view plain copy

  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <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">  
  3.    
  4.     <modelVersion>4.0.0</modelVersion>  
  5.    
  6.     <groupId>de.frank_durr</groupId>  
  7.     <artifactId>myctrlapp</artifactId>  
  8.     <version>0.1</version>  
  9.     <packaging>bundle</packaging>  
  10.    
  11.     <build>  
  12.         <plugins>  
  13.             <plugin>  
  14.                 <groupId>org.apache.felix</groupId>  
  15.                 <artifactId>maven-bundle-plugin</artifactId>  
  16.                 <version>2.3.7</version>  
  17.                 <extensions>true</extensions>  
  18.                 <configuration>  
  19.                     <instructions>  
  20.                         <Import-Package>  
  21.                             *  
  22.                         </Import-Package>  
  23.                         <Export-Package>  
  24.                             de.frank_durr.myctrlapp  
  25.                         </Export-Package>  
  26.                         <Bundle-Activator>  
  27.                             de.frank_durr.myctrlapp.Activator  
  28.                         </Bundle-Activator>  
  29.                     </instructions>  
  30.                     <manifestLocation>${project.basedir}/META-INF</manifestLocation>  
  31.                 </configuration>  
  32.             </plugin>  
  33.         </plugins>  
  34.     </build>  
  35.    
  36.     <dependencies>  
  37.         <dependency>  
  38.             <groupId>org.opendaylight.controller</groupId>  
  39.             <artifactId>sal</artifactId>  
  40.             <version>0.7.0</version>  
  41.         </dependency>  
  42.     </dependencies>  
  43.    
  44.     <repositories>  
  45.         <!-- OpenDaylight releases -->  
  46.         <repository>  
  47.             <id>opendaylight-mirror</id>  
  48.             <name>opendaylight-mirror</name>  
  49.             <url>http://nexus.opendaylight.org/content/groups/public/</url>  
  50.             <snapshots>  
  51.                 <enabled>false</enabled>  
  52.             </snapshots>  
  53.             <releases>  
  54.                 <enabled>true</enabled>  
  55.                 <updatePolicy>never</updatePolicy>  
  56.             </releases>  
  57.         </repository>  
  58.         <!-- OpenDaylight snapshots -->  
  59.         <repository>  
  60.             <id>opendaylight-snapshot</id>  
  61.             <name>opendaylight-snapshot</name>  
  62.             <url>http://nexus.opendaylight.org/content/repositories/opendaylight.snapshot/</url>  
  63.             <snapshots>  
  64.                 <enabled>true</enabled>  
  65.             </snapshots>  
  66.             <releases>  
  67.                 <enabled>false</enabled>  
  68.             </releases>  
  69.         </repository>  
  70.     </repositories>  
  71. </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工程

執行一下命令:

  1. user@host:$ cd ~/myctrlapp  
  2. user@host:$ mvn eclipse:eclipse 

當你改動了pom文件的時候,必定要從新執行以上指令。接下來,你能夠將工程導入到Eclipse當中了。

 

  1. Menu Import / General / Existing projects into workspace  
  2. 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文件。(代碼參見原教程)

[java] view plain copy

  1. package de.frank_durr.myctrlapp;  
  2.   
  3. import java.util.Dictionary;  
  4. import java.util.Hashtable;  
  5.   
  6. import org.apache.felix.dm.*;  
  7. import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;  
  8. import org.opendaylight.controller.sal.packet.*;  
  9. import org.slf4j.Logger;  
  10. import org.slf4j.LoggerFactory;  
  11.   
  12.   
  13.   
  14. public class Activator extends ComponentActivatorAbstractBase{  
  15.       
  16.     private static final Logger log = LoggerFactory.getLogger(PacketHandler.class);  
  17.     public Object[] getImplementations(){  
  18.         log.trace("getting implementations");  
  19.         Object[] res ={PacketHandler.class};  
  20.         return res;  
  21.     }  
  22.       
  23.     public void configureInstance(Component c,Object imp,String containerName){  
  24.         log.trace("Configuring instance");  
  25.         if(imp.equals(PacketHandler.class)){  
  26.             Dictionary<String,Object> props=new Hashtable<String,Object>();  
  27.             props.put("salListenerName","mypackethandler");  
  28.             c.setInterface(new String[] {IListenDataPacket.class.getName()},props);  
  29.             c.add(createContainerServiceDependency(containerName).setService(IDataPacketService.class).setCallbacks("setDataPacketService","unsetDataPacketService").setRequired(true));  
  30.               
  31.         }  
  32.     }  
  33. }  

 

 

咱們擴展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.

代碼以下:

 

[java] view plain copy

  1. <pre name="code" class="java">package de.frank_durr.myctrlapp;  
  2.    
  3. import java.net.InetAddress;  
  4. import java.net.UnknownHostException;  
  5.    
  6. import org.opendaylight.controller.sal.core.Node;  
  7. import org.opendaylight.controller.sal.core.NodeConnector;  
  8. import org.opendaylight.controller.sal.packet.Ethernet;  
  9. import org.opendaylight.controller.sal.packet.IDataPacketService;  
  10. import org.opendaylight.controller.sal.packet.IListenDataPacket;  
  11. import org.opendaylight.controller.sal.packet.IPv4;  
  12. import org.opendaylight.controller.sal.packet.Packet;  
  13. import org.opendaylight.controller.sal.packet.PacketResult;  
  14. import org.opendaylight.controller.sal.packet.RawPacket;  
  15. import org.slf4j.Logger;  
  16. import org.slf4j.LoggerFactory;  
  17.    
  18. public class PacketHandler implements IListenDataPacket {  
  19.    
  20.     private static final Logger log = LoggerFactory.getLogger(PacketHandler.class);  
  21.     private IDataPacketService dataPacketService;  
  22.    
  23.     static private InetAddress intToInetAddress(int i) {  
  24.         byte b[] = new byte[] { (byte) ((i>>24)&0xff), (byte) ((i>>16)&0xff), (byte) ((i>>8)&0xff), (byte) (i&0xff) };  
  25.         InetAddress addr;  
  26.         try {  
  27.             addr = InetAddress.getByAddress(b);  
  28.         } catch (UnknownHostException e) {  
  29.             return null;  
  30.         }  
  31.    
  32.         return addr;  
  33.     }  
  34.    
  35.     /* 
  36.      * Sets a reference to the requested DataPacketService 
  37.      * See Activator.configureInstance(...): 
  38.      * c.add(createContainerServiceDependency(containerName).setService( 
  39.      * IDataPacketService.class).setCallbacks( 
  40.      * "setDataPacketService", "unsetDataPacketService") 
  41.      * .setRequired(true)); 
  42.      */  
  43.     void setDataPacketService(IDataPacketService s) {  
  44.         log.trace("Set DataPacketService.");  
  45.    
  46.         dataPacketService = s;  
  47.     }  
  48.    
  49.     /* 
  50.      * Unsets DataPacketService 
  51.      * See Activator.configureInstance(...): 
  52.      * c.add(createContainerServiceDependency(containerName).setService( 
  53.      * IDataPacketService.class).setCallbacks( 
  54.      * "setDataPacketService", "unsetDataPacketService") 
  55.      * .setRequired(true)); 
  56.      */  
  57.     void unsetDataPacketService(IDataPacketService s) {  
  58.         log.trace("Removed DataPacketService.");  
  59.    
  60.         if (dataPacketService == s) {  
  61.             dataPacketService = null;  
  62.         }  
  63.     }  
  64.    
  65.     @Override  
  66.     public PacketResult receiveDataPacket(RawPacket inPkt) {  
  67.         //System.out.println("Received data packet.");  
  68.    
  69.         // The connector, the packet came from ("port")  
  70.         NodeConnector ingressConnector = inPkt.getIncomingNodeConnector();  
  71.         // The node that received the packet ("switch")  
  72.         Node node = ingressConnector.getNode();  
  73.    
  74.         // Use DataPacketService to decode the packet.  
  75.         Packet l2pkt = dataPacketService.decodeDataPacket(inPkt);  
  76.    
  77.         if (l2pkt instanceof Ethernet) {  
  78.             Object l3Pkt = l2pkt.getPayload();  
  79.             if (l3Pkt instanceof IPv4) {  
  80.                 IPv4 ipv4Pkt = (IPv4) l3Pkt;  
  81.                 int dstAddr = ipv4Pkt.getDestinationAddress();  
  82.                 InetAddress addr = intToInetAddress(dstAddr);  
  83.                 System.out.println("Pkt. to " + addr.toString() + " received by node " + node.getNodeIDString() + " on connector " + ingressConnector.getNodeConnectorIDString());  
  84.                 return PacketResult.KEEP_PROCESSING;  
  85.             }  
  86.         }  
  87.         // We did not process the packet -> let someone else do the job.  
  88.         return PacketResult.IGNORED;  
  89.     }  
  90. }  


咱們能夠看到,咱們的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來編譯打包:

 

 

<ol class="linenums" style="color: rgb(85, 85, 85); font-family: monospace; font-size: 15px; line-height: 35px; white-space: pre-wrap;"><li class="L0"><span class="pln">user@host</span><span class="pun">:</span><span class="pln">$ cd </span><span class="pun">~/</span><span class="pln">myctrlapp</span></li><li class="L1"><span class="pln">user@host</span><span class="pun">:</span><span class="pln">$ mvn </span><span class="kwd">package</span></li></ol>

若是咱們的POM文件和代碼都是正確的話,以上指令就會建立正確的JAR文件:~/myctrlapp/target/myctrlapp-0.1.jar

 

這個bundle如今能夠被安裝到OSGI框架當中(opendaylight中Equinox)首先咱們啓動控制器。

 

[html] view plaincopy

  1. user@host:$ cd ~/controller/opendaylight/distribution/opendaylight/target/distribution.opendaylight-osgipackage/opendaylight/  
  2. user@host:$ ./runs.sh  

而後install咱們的bundle

 

 

[html] view plaincopy

  1. osgi> install file:/home/user/myctrlapp/target/myctrlapp-0.1.jar  
  2. Bundle id is 256  

能夠看到咱們的編號是256

 

如今咱們啓動budle

 

[html] view plaincopy

  1. osgi> start 256  


你能夠檢查一下如今正在運行的OSGIbundle。使用命令:

 

 

[html] view plaincopy

  1. osgi> ss  


相似的你能夠stop和uninstall這個bundle使用以下命令:

 

 

[html] view plaincopy

  1. osgi> stop 256  
  2. osgi> uninstall 256  


在咱們開始測試這個bundle以前,咱們stopOpendaylight的兩個服務。Simple Forwarding Service和LoadBalancing Service:

 

 

 

[html] view plaincopy

  1. osgi> <span style="color:#CC0000;">ss | grep simple</span>  
  2. 171 ACTIVE org.opendaylight.controller.samples.simpleforwarding_0.4.1.SNAPSHOT  
  3. true  
  4. osgi> stop 171  
  5. osgi> <span style="color:#FF0000;">osgi> ss | grep loadbalance</span>r  
  6. 150 ACTIVE org.opendaylight.controller.samples.loadbalancer.northbound_0.4.1.SNAPSHOT  
  7. 187 ACTIVE org.opendaylight.controller.samples.loadbalancer_0.5.1.SNAPSHOT  
  8. true  
  9. osgi> stop 187  


    爲何要這麼作呢,由於這兩項服務也實現了packet listenner,爲了測試,咱們必需要確保這兩個服務沒有把packet consume掉,不然的話咱們就不能取得數據包。

 

 

 

Testing

  咱們使用簡單的linear Mininet 拓撲只有兩個交換機,兩個host。

 

 

[html] view plaincopy

  1. user@host:$ sudo mn --controller=remote,ip=129.69.210.89 --topo linear,2  


這個ip 填控制器的ip

 

如今咱們用host1 ping host2 而後看osgi控制檯的輸出。

 

[html] view plaincopy

  1. mininet> h1 ping h2  
  2.    
  3. osgi>  
  4. Pkt. to /10.0.0.2 received by node 00:00:00:00:00:00:00:01 on connector 1  
  5. 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就完成了!!

相關文章
相關標籤/搜索