Opendaylight中开发模块Ping

2014-08-06 by muzi

前言

在安装和运行opendaylight之后,我们需要了解opendaylight各个目录的作用,运行机制。在此基础之上,我们需要动手进行第一个ODL模块的开发,在开发中找到ODL的重要所在。接下来的教程是在完成官网的一篇教程的基础上的总结和介绍,希望读者能有所收获。

摘要

本篇教程主要介绍如何在ODL上使用MD-SAL开发一个简单的TCP ping插件。这个插件实现了向指定地址发送ICMP,探测IP地址可达性的功能。首先我们需要使用YANG定义一个ping model,然后我们需要实现一个ping plugin,实现ping功能,再然后创建ping service 用于提供ping服务,最后我们通过REST API来实现北向接口使用ping service。

其中每一个功能,我们将其创建为maven工程,并作为osgi的bundle。所以我们总共需要创建4个OSGI 的bundle:

  • Northbound API / Implementation [ping-northbound]

    The northbound API defines the interface for interacting with the given service. For example, a REST, or JSON interface for invoking the ping service.

  • Service Bundle [ping-service]

    The service bundle links the northbound implementation with the south bound (MD-SAL) definitions.

  • South Bound API (MD-SAL definition) [model-ping]

    This bundle defines a simple yang file which results in auto-generated data-models ( i.e. auto generated java interfaces)

  • Specific Implementation of South Bound Interface

    Defines the specific of the south bound interface, generally protocol specific etc.

第一步:下载控制器源码,编译

  • 使用一下的命令下载controller的最新代码,注意,若使用以前旧版本的控制器,在教程中会遇到一些问题,所以推荐重新下载新文件。

    git clone https://git.opendaylight.org/gerrit/p/controller.git
    
  • 检查YANG tools的版本>=0.5.8-SNAPSHOT即可。

    vi controller/opendaylight/commons/opendaylight/pom.xml
    ...
    <yangtools.version>0.5.8-SNAPSHOT</yangtools.version>
    ...
    
  • 编译控制器

    cd controller/opendaylight/distribution/opendaylight
    mvn clean install
    

第二步:使用YANG定义model-ping

在ODL中使用YANG来描述底层的数据结构,所以我们需要首先要建立一个YANG model文件,目的是为了生成JAVA API。YANG文件定义的数据结构可以一一对应生成java 接口等API,更详细的资料请看:

https://wiki.opendaylight.org/view/YANG_Tools:YANG_to_Java_Mapping

最简单的方式是在md-sal/model中建立model,这样可以和使用其他模块的pom.xml。在完成第一步的基础上,按照以下命令,创建YANG工程

cd ../../md-sal/model/
mkdir model-ping
cd model-ping/
mkdir -p src/main/yang

然后在src/main/yang目录创建文件

vi src/main/yang/ping.yang

这个ping model的yang文件首先需要定义一个简单的RPC(Remote Procedure Call Protocol)来完成ping request的初始化,这个过程需要IPV4的地址作为参数,所以我们需要import “ietf-inet-types” model。同时,我们也要为这个调用设置返回值。具体如下:(复制粘贴时请将中文注释删除)

module ping {        #模块名称
  namespace "urn:opendaylight:ping";
  prefix ping;
  import ietf-inet-types {prefix inet;}#import
  revision "2013-09-11" {
    description "TCP ping module";    #对模块/模型描述
  }
  rpc send-echo {          #定义rpc 
    description "Send TCP ECHO request";
    input {    #输入
      leaf destination {  
        type inet:ipv4-address;   #参数为ipv4地址
      }
    }
    output {   #输出
      leaf echo-result {
        type enumeration {
          enum "reachable" {    #返回类型
            value 0;
            description "Received reply";
          }
          enum "unreachable" {
            value 1;
            description "No reply during timeout";
          }
          enum "error" {
            value 2;
            description "Error happened";
          }
        }
        description "Result types";
      }
    }
  }
}

完成之后,我们需要为maven项目model-ping构建pom.xml文件。

vi pom.xml

在pom.xml中使用model-parent 会使整个pom.xml变得很简单。

<?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/xsd/maven-4.0.0.xsd">
  <parent>
    <artifactId>model-parent</artifactId>
    <groupId>org.opendaylight.controller.model</groupId>
    <version>1.1-SNAPSHOT</version>
    <relativePath></relativePath>
  </parent>

  <modelVersion>4.0.0</modelVersion>
  <artifactId>model-ping</artifactId>
  <packaging>bundle</packaging>
</project>

完成以上的步骤之后,就可以使用maven编译生成API和bundle了。

mvn clean install

若想运行ODL的时候运行bundle,则需要将生成的jar包复制到plugins目录。

cp ./target/model-ping-1.1-SNAPSHOT.jar ../../../distribution/opendaylight/target/\
distribution.opendaylight-osgipackage/opendaylight/plugins/\
org.opendaylight.controller.model.model-ping-1.1-SNAPSHOT.jar

第三步:ping plugin

上一步我们创建了一个model,定义了数据结构,这一步我们需要创建一个bundle来实现这个ping的插件。从而提供ping的服务。

在controller/opendaylight/ping目录创建工程,命令如下:

cd ../../../
mkdir -p ping/plugin/src/main/java/org/opendaylight/controller/ping/plugin/internal
cd ping/plugin

这依然是一个maven工程,所以我们需要创建他的pom.xml文件,用于描述工程。

vi pom.xml

在pom.xml文件中,我们需要为上一步定义的model-ping描述依赖关系。并需要引入上一步编译生成的包:org.opendaylight.yang.gen.v1.urn.opendaylight.ping.rev130911。 同时,为了定义ipv4,我们还需要引入依赖包:org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924

<?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/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <parent>
   <groupId>org.opendaylight.controller</groupId>
   <artifactId>commons.opendaylight</artifactId>
   <version>1.4.2-SNAPSHOT</version>
   <relativePath>../../commons/opendaylight</relativePath>
 </parent>
 <artifactId>ping.plugin</artifactId>
 <version>0.4.0-SNAPSHOT</version>
 <packaging>bundle</packaging>
 <build>  #构建工程
   <plugins>
     <plugin>
       <groupId>org.apache.felix</groupId>
       <artifactId>maven-bundle-plugin</artifactId>
       <version>${bundle.plugin.version}</version>
       <extensions>true</extensions>
       <configuration>
         <instructions>
           <Import-Package>
             org.opendaylight.yang.gen.v1.urn.opendaylight.ping.rev130911,
             org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924,
             org.opendaylight.yangtools.yang.common,
             org.opendaylight.yangtools.yang.binding,
             org.opendaylight.controller.sal.binding.api,
             org.opendaylight.controller.sal.common.util,
             com.google.common.util.concurrent,
             org.osgi.framework
           </Import-Package>
           <Export-Package>
             org.opendaylight.controller.ping.plugin.internal#生成包,java文件所在目录
           </Export-Package>
           <Bundle-Activator>
             org.opendaylight.controller.ping.plugin.internal.PingProvider
           </Bundle-Activator>
         </instructions>
         <manifestLocation>${project.basedir}/META-INF</manifestLocation>
       </configuration>
     </plugin>
   </plugins>
 </build>
 <dependencies>#依赖关系
    <dependency>
      <groupId>org.osgi</groupId>
      <artifactId>org.osgi.core</artifactId>
    </dependency>
   <dependency>
     <groupId>org.opendaylight.controller.model</groupId>
     <artifactId>model-ping</artifactId>
     <version>1.1-SNAPSHOT</version>
   </dependency>
   <dependency>
     <groupId>org.opendaylight.controller</groupId>
     <artifactId>sal-binding-api</artifactId>
     <version>1.1-SNAPSHOT</version>
   </dependency>
   <dependency>
     <groupId>org.opendaylight.controller</groupId>
     <artifactId>sal-common-util</artifactId>
     <version>1.1-SNAPSHOT</version>
   </dependency>
   <dependency>
     <groupId>org.opendaylight.yangtools</groupId>
     <artifactId>yang-common</artifactId>
     <version>${yangtools.version}</version>
   </dependency>
   <dependency>
     <groupId>org.opendaylight.yangtools</groupId>
     <artifactId>yang-binding</artifactId>
     <version>${yangtools.version}</version>
   </dependency>
 </dependencies>
</project>

完成pom.xml之后,我们需要在正确的目录添加接口实现文件

vi src/main/java/org/opendaylight/controller/ping/plugin/internal/PingImpl.java

在第一步完成之后,我们已经由YANG文件生成了PingService的接口。PingImpl将实现PingService接口。在PingService中有sendEcho 的RPC方法声明,所以PingImpl也有这一方法。这个RPC方法将IPV4数据 “SendEchoInput”作为参数传入,并返回枚举类型"SendEchoOutput"。返回值是由YANG定义的EchoResult.Reachable或者EchoResult.Unreachable,错误还会返回EchoResult.Error。类中用InetAddress.isReachable(timeout)函数去检测IPV4地址是否可达。

package org.opendaylight.controller.ping.plugin.internal;

import java.io.IOException;
import java.net.InetAddress;
import java.util.Collections;
import java.util.concurrent.Future;

import org.opendaylight.controller.sal.common.util.Rpcs;
import org.opendaylight.yang.gen.v1.urn.opendaylight.ping.rev130911.PingService;
import org.opendaylight.yang.gen.v1.urn.opendaylight.ping.rev130911.SendEchoInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.ping.rev130911.SendEchoOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.ping.rev130911.SendEchoOutput.EchoResult;
import org.opendaylight.yang.gen.v1.urn.opendaylight.ping.rev130911.SendEchoOutputBuilder;
import org.opendaylight.yangtools.yang.common.RpcError;
import org.opendaylight.yangtools.yang.common.RpcResult;

import com.google.common.util.concurrent.Futures;

public class PingImpl implements PingService {

    private EchoResult pingHost(InetAddress destination) throws IOException {
        if (destination.isReachable(5000)) {
            return EchoResult.Reachable;
        } else {
            return EchoResult.Unreachable;
        }
    }//EchoResult是在YANG文件中定义的输出数据格式,编译之后生成的JAVA类型。

    @Override
    public Future<RpcResult<SendEchoOutput>> sendEcho(SendEchoInput destination) {
        try {
            InetAddress dst = InetAddress.getByName(destination
                    .getDestination().getValue());
            EchoResult result = this.pingHost(dst);

            /* Build the result and return it. */
            SendEchoOutputBuilder ob = new SendEchoOutputBuilder();
            ob.setEchoResult(result);
            RpcResult<SendEchoOutput> rpcResult =
                    Rpcs.<SendEchoOutput> getRpcResult(true, ob.build(),
                            Collections.<RpcError> emptySet());

            return Futures.immediateFuture(rpcResult);
        } catch (Exception e) {

            /* Return error result. */
            SendEchoOutputBuilder ob = new SendEchoOutputBuilder();
            ob.setEchoResult(EchoResult.Error);
            RpcResult<SendEchoOutput> rpcResult =
                    Rpcs.<SendEchoOutput> getRpcResult(true, ob.build(),
                            Collections.<RpcError> emptySet());
            return Futures.immediateFuture(rpcResult);
        }
    }

}

Futures:represents the result of an asynchronous computation.

more:http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html

完成PingImpl.java之后我们还需要一个java文件,作用在于在osgi上注册plugin service,是bundle的activator。

vi src/main/java/org/opendaylight/controller/ping/plugin/internal/PingProvider.java

这个类需要继承AbstractBindingAwareProvider 接口(这个接口非常重要,后续的教程教详细介绍相关内容)。在我们的例子这个接口中的onSessionInitiated 是最重要的方法。我们使用这个方法,将pingservice的接口和实现作为参数参入,实现服务在OSGI框架注册,从而让其他bundle感知到。

package org.opendaylight.controller.ping.plugin.internal;

import java.util.Collection;

import org.opendaylight.controller.sal.binding.api.AbstractBindingAwareProvider;
// import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ConsumerContext;
import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ProviderContext;
import org.opendaylight.yang.gen.v1.urn.opendaylight.ping.rev130911.PingService;
import org.opendaylight.yangtools.yang.binding.RpcService;
import org.osgi.framework.BundleContext;

public class PingProvider extends AbstractBindingAwareProvider {

    PingImpl pingImpl;

    public PingProvider() {
        pingImpl = new PingImpl();//构造实例
    }

    @Override
    public Collection<? extends RpcService> getImplementations() {
        return null;
    }

    @Override
    public Collection<? extends ProviderFunctionality> getFunctionality() {
        return null;
    }

    @Override
    public void onSessionInitiated(ProviderContext session) {
        session.addRpcImplementation(PingService.class, pingImpl);//注册服务
    }

    @Override
    protected void startImpl(BundleContext context) {
    }

}

这一步我们完成了activator和implementation。完成了服务的实现和注册。然后编译工程:

mvn clean install

将生成的jar文件复制到plugins目录

cp target/ping.plugin-0.4.0-SNAPSHOT.jar ../../distribution/opendaylight/target/distribution.opendaylight-osgipackage/opendaylight/plugins/org.opendaylight.controller.ping.plugin-0.4.0-SNAPSHOT.jar

测试

以上的步骤已经完成了YANG定义model,以及服务生产者的实现。我们可以通过自动生成的RESTAPI去调用send-echo RPC。可使用curl进行测试,命令如下:

curl -v -H "Content-Type:application/json" -X POST -u admin:admin -d "{input:{destination:127.0.0.1}}" http://localhost:8080/restconf/operations/ping:send-echo

其中destination的值可换成任意你想要ping的地址。

如果ping成功将会返回

{"output":{"echo-result":"reachable"}}

否则返回其他。

第三步 通过REST API使用Ping RPC

接下来的部门将介绍如何为ping model构造消费者(以上的provider是生产者),然后为这个消费者实现一个REST API.

Ping service

为了让北向接口和YANG model解耦,我们使用Ping service来连接ping plugin和Ping northbound。所以我们还需要构建一个作为osgi bundle的maven工程,构建目录在controller/opendaylight/ping

cd ..
mkdir -p service/src/main/java/org/opendaylight/controller/ping/service/api
mkdir -p service/src/main/java/org/opendaylight/controller/ping/service/impl
cd service

每一个maven项目,我们都需要有一个pom.xml文件。

vi pom.xml

在pom.xml中,我们需要指明这个bundle的activator(osgi的每一个bundle都有activator,用于启动。详情请google),同时,还需要暴露出plugin service 接口,以便其他的bundle使用,比如Ping northbound。

<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/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.opendaylight.controller</groupId>
    <artifactId>commons.opendaylight</artifactId>
    <version>1.4.2-SNAPSHOT</version>
    <relativePath>../../commons/opendaylight</relativePath>
  </parent>
  <artifactId>ping.service</artifactId>
  <packaging>bundle</packaging>
  <version>1.1-SNAPSHOT</version>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <version>${bundle.plugin.version}</version>
        <extensions>true</extensions>
        <configuration>
          <instructions>
            <Import-Package>
              org.opendaylight.yang.gen.v1.urn.opendaylight.ping.rev130911,
              org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924,
              org.opendaylight.yangtools.yang.common,
              org.opendaylight.yangtools.yang.binding,
              org.opendaylight.controller.sal.binding.api,
              org.osgi.framework
            </Import-Package>
            <Export-Package>org.opendaylight.controller.ping.service.api</Export-Package>#导出api包
            <Bundle-Activator>org.opendaylight.controller.ping.service.impl.PingServiceImpl</Bundle-Activator>  #activator的指明
          </instructions>
          <manifestLocation>${project.basedir}/META-INF</manifestLocation>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>org.osgi</groupId>
      <artifactId>org.osgi.core</artifactId>
    </dependency>
    <dependency>
      <groupId>org.opendaylight.controller.model</groupId>
      <artifactId>model-ping</artifactId>
      <version>1.1-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>org.opendaylight.controller</groupId>
      <artifactId>sal-binding-api</artifactId>
      <version>1.1-SNAPSHOT</version>
    </dependency>
    <dependency>
     <groupId>org.opendaylight.yangtools</groupId>
     <artifactId>yang-common</artifactId>
     <version>${yangtools.version}</version>
    </dependency>
    <dependency>
     <groupId>org.opendaylight.yangtools</groupId>
     <artifactId>yang-binding</artifactId>
     <version>${yangtools.version}</version>
    </dependency>
  </dependencies>
</project>

完成pom.xml之后,我们需要开始定义ping service的接口和实现。

vi src/main/java/org/opendaylight/controller/ping/service/api/PingServiceAPI.java

ping service的接口非常简单。参数为传入的目标地址,返回会boolean类型,表明是否可达。具体如下:

package org.opendaylight.controller.ping.service.api;

public interface PingServiceAPI {

    /**
     * pingDestination
     *
     * @param address An IPv4 address to be pinged
     * @return True if address is reachable,
     * false if address is unreachable or error occurs.
     */
    boolean pingDestination(String address);
}

完成接口定义之后,我们还需要实现接口。

vi src/main/java/org/opendaylight/controller/ping/service/impl/PingServiceImpl.java

这里的PingServiceImpl类继承了AbstractBindingAwareConsumer类。在AbstractBindingAwareConsumer类中提供了回调函数,以osgi启动bundle。

(这个重要的类所属的目录为:controller/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/sal/binding/api。着这个目录中有非常重要的类如BindingAwareBroker.java。在后续的教程中将重点分析次目录。)

在onSessionInitialized方法中,'ConsumerContext'被存储等待后续使用。而startImpl方法则是调用了register函数,将bundle注册到了osgi平台。pingDestination方法首先寻找是否存在ping service 的RPC调用。如存在则继续使用ping.recv130911b包内的SendEchoInput类构建input,然后调用ping plugin中的sendEcho,返回的SendEchoOutput字符串映射成boolean值,返回调用者。

package org.opendaylight.controller.ping.service.impl;

import java.util.concurrent.ExecutionException;

import org.opendaylight.controller.ping.service.api.PingServiceAPI;
import org.opendaylight.controller.sal.binding.api.AbstractBindingAwareConsumer;
import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ConsumerContext;
import org.opendaylight.controller.sal.binding.api.BindingAwareConsumer;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Address;
import org.opendaylight.yang.gen.v1.urn.opendaylight.ping.rev130911.PingService;
import org.opendaylight.yang.gen.v1.urn.opendaylight.ping.rev130911.SendEchoInputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.ping.rev130911.SendEchoOutput;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class PingServiceImpl extends AbstractBindingAwareConsumer implements
        BundleActivator, BindingAwareConsumer, PingServiceAPI {

    private PingService ping;
    private ConsumerContext session;

    @Override
    public void onSessionInitialized(ConsumerContext session) {
        this.session = session;
    }

    @Override
    protected void startImpl(BundleContext context) {
        context.registerService(PingServiceAPI.class, this, null);
    }

    @Override
    public boolean pingDestination(String address) {

        if (ping == null) {
            ping = this.session.getRpcService(PingService.class);
            if (ping == null) {

                /* No ping service found. */
                return false;
            }
        }

        Ipv4Address destination = new Ipv4Address(address);

        SendEchoInputBuilder ib = new SendEchoInputBuilder();
        ib.setDestination(destination);
        try {
            RpcResult<SendEchoOutput> result = ping.sendEcho(ib.build()).get();
            switch (result.getResult().getEchoResult()) {
            case Reachable:
                return true;
            case Unreachable:
            case Error:
            default:
                return false;
            }
        } catch (InterruptedException ie) {
        } catch (ExecutionException ee) {
        }

        return false;
    }

}

完成以上内容之后,对bundle使用mvn进行编译,并将生成的jar包复制到指定目录。

mvn clean install
cp target/ping.service-1.1-SNAPSHOT.jar ../../distribution/opendaylight/target/distribution.opendaylight-osgipackage/opendaylight/plugins/org.opendaylight.controller.ping.service-1.1-SNAPSHOT.jar

Ping Northbound

在完成了ping service之后,我们最后还需要构建一个北向API,调用ping service。

在controller/opendaylight/ping目录在创建northbound目录。

cd ..
mkdir -p northbound/src/main/java/org/opendaylight/controller/ping/northbound
mkdir -p northbound/src/main/resources/META-INF
mkdir -p northbound/src/main/resources/WEB-INF
cd northbound

同样的为工程添加pom.xml

vi pom.xml

在pom.xml文件中我们需要import Ping service包,以便使用Ping Service。同时,我们也要需要确定许多WEB服务的信息。例如ContextPath,我们定义ContextPath为'/controller/nb/v2',所以我们可以通过HTTP PUT的方式向http://localhost:8080/controller/nb/v2/ping/{ipAddress}发送send ping请求。

<?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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.opendaylight.controller</groupId>
    <artifactId>commons.opendaylight</artifactId>
    <version>1.4.2-SNAPSHOT</version>
    <relativePath>../../commons/opendaylight</relativePath>
  </parent>
  <artifactId>ping.northbound</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>bundle</packaging>

  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.enunciate</groupId>
        <artifactId>maven-enunciate-plugin</artifactId>
        <version>${enunciate.version}</version>
        <dependencies>
          <dependency>
            <groupId>org.opendaylight.controller</groupId>
            <artifactId>sal</artifactId>
            <version>0.7.1-SNAPSHOT</version>
          </dependency>
        </dependencies>
      </plugin>
      <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <version>${bundle.plugin.version}</version>
        <extensions>true</extensions>
        <configuration>
          <instructions>
            <Import-Package>
              org.opendaylight.controller.ping.service.api,
              org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924,
              org.apache.commons.logging,
              com.sun.jersey.spi.container.servlet,
              org.opendaylight.controller.northbound.commons,
              org.opendaylight.controller.northbound.commons.exception,
              org.opendaylight.controller.northbound.commons.utils,
              org.opendaylight.controller.sal.utils,
              org.opendaylight.controller.sal.authorization,
              org.opendaylight.controller.sal.packet.address,
              javax.ws.rs,
              javax.ws.rs.core,
              javax.xml.bind.annotation,
              javax.xml.bind,
              org.slf4j,
              org.apache.catalina.filters,
              com.fasterxml.jackson.jaxrs.base,
              com.fasterxml.jackson.jaxrs.json,
              !org.codehaus.enunciate.jaxrs
            </Import-Package>
            <Web-ContextPath>/controller/nb/v2</Web-ContextPath>
          </instructions>
          <manifestLocation>${project.basedir}/src/main/resources/META-INF</manifestLocation>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>org.opendaylight.controller.thirdparty</groupId>
      <artifactId>com.sun.jersey.jersey-servlet</artifactId>
      <version>1.17</version>
    </dependency>
    <dependency>
      <groupId>org.opendaylight.controller</groupId>
      <artifactId>commons.northbound</artifactId>
      <version>0.4.2-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>org.codehaus.enunciate</groupId>
      <artifactId>enunciate-core-annotations</artifactId>
      <version>${enunciate.version}</version>
    </dependency>
    <dependency>
      <groupId>org.opendaylight.controller.thirdparty</groupId>
      <artifactId>org.apache.catalina.filters.CorsFilter</artifactId>
      <version>7.0.42</version>
    </dependency>
    <dependency>
      <groupId>org.opendaylight.controller</groupId>
      <artifactId>ping.service</artifactId>
      <version>1.1-SNAPSHOT</version>
    </dependency>
  </dependencies>
</project>

接下来我们创建enunciate.xml。什么是enunciate? http://enueciate.codehaus.org

<?xml version="1.0"?>
<enunciate label="full" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://enunciate.codehaus.org/schemas/enunciate-1.26.xsd">

  <services>
    <rest defaultRestSubcontext="/controller/nb/v2/ping"/>
  </services>

  <modules>
    <docs docsDir="rest" title="Ping REST API" includeExampleXml="true" includeExampleJson="true"/>
  </modules>
</enunciate>

最后我们还需要配置web.xml

vi src/main/resources/WEB-INF/web.xml

web.xml可以从其他类似的北向的项目中复制,然后修改。在servlet标签中声明:JAXRSPing,对应到param-value中的PingNorthboundRSApplication。我们将马上实现这个文件。

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
 http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
 version="3.0">
  <servlet>
    <servlet-name>JAXRSPing</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>javax.ws.rs.Application</param-name>
      <param-value>org.opendaylight.controller.ping.northbound.PingNorthboundRSApplication</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>JAXRSPing</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

        <filter>
          <filter-name>CorsFilter</filter-name>
          <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
          <init-param>
            <param-name>cors.allowed.origins</param-name>
            <param-value>*</param-value>
          </init-param>
          <init-param>
            <param-name>cors.allowed.methods</param-name>
            <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
          </init-param>
          <init-param>
            <param-name>cors.allowed.headers</param-name>
            <param-value>Content-Type,X-Requested-With,accept,authorization, origin,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
          </init-param>
          <init-param>
            <param-name>cors.exposed.headers</param-name>
            <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
          </init-param>
          <init-param>
            <param-name>cors.support.credentials</param-name>
            <param-value>true</param-value>
          </init-param>
          <init-param>
            <param-name>cors.preflight.maxage</param-name>
            <param-value>10</param-value>
          </init-param>
        </filter>
        <filter-mapping>
          <filter-name>CorsFilter</filter-name>
          <url-pattern>/*</url-pattern>
        </filter-mapping>

        <security-constraint>
          <web-resource-collection>
            <web-resource-name>NB api</web-resource-name>
            <url-pattern>/*</url-pattern>
            <http-method>POST</http-method>
            <http-method>GET</http-method>
            <http-method>PUT</http-method>
            <http-method>PATCH</http-method>
            <http-method>DELETE</http-method>
            <http-method>HEAD</http-method>
          </web-resource-collection>
          <auth-constraint>
            <role-name>System-Admin</role-name>
            <role-name>Network-Admin</role-name>
            <role-name>Network-Operator</role-name>
            <role-name>Container-User</role-name>
          </auth-constraint>
        </security-constraint>

        <security-role>
                <role-name>System-Admin</role-name>
        </security-role>
        <security-role>
                <role-name>Network-Admin</role-name>
        </security-role>
        <security-role>
                <role-name>Network-Operator</role-name>
        </security-role>
        <security-role>
                <role-name>Container-User</role-name>
        </security-role>

        <login-config>
                <auth-method>BASIC</auth-method>
                <realm-name>opendaylight</realm-name>
        </login-config>
</web-app>

完成xml文件之后,我们首先需要完成源文件PingNorthboundRSApplication.java

vi src/main/java/org/opendaylight/controller/ping/northbound/PingNorthboundRSApplication.java

这个文件将PingNorthbound作为一个web service app。当REST 调用对应URI的资源时将被调用。

package org.opendaylight.controller.ping.northbound;

import java.util.HashSet;
import java.util.Set;

import javax.ws.rs.core.Application;

public class PingNorthboundRSApplication extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> classes = new HashSet<Class<?>>();
        classes.add(PingNorthbound.class);
        return classes;
    }
}

在PingNorthbound文件中应用的实例定义为PingNorthbound,所以我们需要实现这个类。

vi src/main/java/org/opendaylight/controller/ping/northbound/PingNorthbound.java

PingNorthnound定义了ping方法,用于相应特定URI路径的资源相应。ping方法会查找ping service的接口和pingDestination的方法,并根据返回值构建HTTP相应。

package org.opendaylight.controller.ping.northbound;

import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;

import org.codehaus.enunciate.jaxrs.ResponseCode;
import org.codehaus.enunciate.jaxrs.StatusCodes;
import org.opendaylight.controller.ping.service.api.PingServiceAPI;
import org.opendaylight.controller.sal.utils.ServiceHelper;

@Path("/")
public class PingNorthbound {
    /**
     * Ping test
     */
    @Path("/ping/{ipAddress}")
    @PUT
    @StatusCodes({
        @ResponseCode(code = 200, condition = "Destination reachable"),
        @ResponseCode(code = 503, condition = "Internal error"),
        @ResponseCode(code = 503, condition = "Destination unreachable") })
    public Response ping(@PathParam(value = "ipAddress") String ipAddress) {
        PingServiceAPI ping = (PingServiceAPI) ServiceHelper.getGlobalInstance(
                PingServiceAPI.class, this);
        if (ping == null) {

            /* Ping service not found. */
            return Response.ok(new String("No ping service")).status(500)
                    .build();
        }
        if (ping.pingDestination(ipAddress))
            return Response.ok(new String(ipAddress + " - reachable")).build();

        return Response.ok(new String(ipAddress + " - unreachable")).status(503)
                .build();
    }
}

终于,我们完成了northbound的实现,别忘记对工程进行编译。

mvn clean install

还要把对应的jar包复制到plugins目录下

cp target/ping.northbound-1.0-SNAPSHOT.jar ../../distribution/opendaylight/target/distribution.opendaylight-osgipackage/opendaylight/plugins/org.opendaylight.controller.ping.northbound-1.0-SNAPSHOT.jar

测试

运行odl,并通过curl测试

cd ../../distribution/opendaylight/target/distribution.opendaylight-0.1.0-SNAPSHOT-osgipackage/opendaylight
./run.sh

此时可以输入命令

ss ping

启动关于ping内容的bundle,可以看到5个内容,其中4个是我们刚刚添加的bundle.再开终端使用curl测试,测试命令和结果如下:

$ curl --user "admin":"admin" -X PUT http://localhost:8080/controller/nb/v2/ping/127.0.0.1
127.0.0.1 - reachable
$ curl --user "admin":"admin" -X PUT http://localhost:8080/controller/nb/v2/ping/128.0.0.1
128.0.0.1 - unreachable

到目前位置,我们已经完成了绝大部分的工作!最后我们还需要将工程和整个工程一起编译。

最后一步 编译整个工程

全部的ping工程都需要作为控制器的一部分进行编译和安装,我们需要通过在pom.xml的修改达到这一目的。

官网上的教程说的是'sal/yang-prototype/sal/modules'目录,但是新版的控制器并没有这一目录,所以博主努力寻找了一下,在opendylight/md-sal/model下找到了类似的pom.xml。博主认为这就是正确的文件。

vi controller/opendaylight/md-sal/model/pom.xml

model-ping添加到一下部分中

 ...
 <modules>
        <module>model-inventory</module>
        <module>model-flow-base</module>
        <module>model-flow-service</module>
        <module>model-flow-statistics</module>
        <module>model-topology-bgp</module>
        <module>model-ping</module>
 </modules>
 ...

剩下的3个工程同样需要编译,将其加入到对应的pom.xml中。官网给的文件目录是对的,但是内容不对。

vi controller/opendaylight/distribution/opendaylight/pom.xml

添加内容如下

 ...
 <!-- Ping -->  
    <dependency>
      <groupId>org.opendaylight.controller</groupId>
      <artifactId>ping.northbound</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>   
    <dependency>    
      <groupId>org.opendaylight.controller</groupId>    
      <artifactId>ping.plugin</artifactId>
      <version>0.4.0-SNAPSHOT</version>
    </dependency>   
    <dependency>    
      <groupId>org.opendaylight.controller</groupId>    
      <artifactId>ping.service</artifactId> 
      <version>1.1-SNAPSHOT</version>
    </dependency>

 <!--Southbound bundles-->
 ...
 ...
    <dependency>    
      <groupId>org.opendaylight.controller.model</groupId>  
      <artifactId>model.ping</artifactId>   
      <version>1.1-SNAPSHOT</version>
    </dependency>

<!-- toaster example I'm pretty sure we should trim -->
...

两处修改大概在500+和1000+行处。

最后编译所有文件

cd controller/opendaylight/distribution/opendaylight
mvn clean install

运行odl,再次测试,无误!

官网对应教程:https://wiki.opendaylight.org/view/Ping


Comments