当前位置: 首页 > ONOS > 正文

ONOS Application tutorial

开发基于ONOS的SDN应用程序

本教程由http://sdnhub.cn根据https://wiki.onosproject.org/display/ONOS/Application+tutorial翻译和整理而成。转载请注明出处。

基于ONOS最新的1.1.0发行版。

1.概述

一个ONOS应用程序本质上是一个使用Maven工程编译的OSGi软件集。因此,一个ONOS应用程序可被看做是一些Java类和POM文件(Maven Program Object Manager files,XML格式)的集合。本教程讨论如何手工的从零开始编写一个ONOS应用程序。本示例应用程序是基于intent的反应式转发应用(reactive forwarding application)。

完成本教程后的收获,你讲理解如下内容:

  • 一个ONOS应用程序的组织和架构
  • 如何注册你的应用程序与各种服务
  • 使用ONOS北向(Northbound)API的基础
  • 如何运行一个ONOS应用程序

约定(Conventions):

${ONOS_ROOT} 指向ONOS项目的根目录。例如:如果ONOS项目的根目录是 ~/onos,则执行cd ${ONOS_ROOT} 等价于 cd ~/onos

最后,假定你已经 check out 了ONOS的源代码(参考https://wiki.onosproject.org/display/ONOS/Getting+ONOS),设置了相关环境,并且已经将源码导入到了IDE。本教程中的练习可以使用任何文本编辑器,但是使用IDE完成将会更容易点 :)。

2.项目骨架设置

作为参考,应用程序的目录结构总结如下:

${ONOS_ROOT}/apps/pom.xml (apps parent POM file)
              | 
              /myifwd/pom.xml (application POM file)
                   |
                   /src/main/java/org/onosproject/myifwd/IntentReactiveForwarding.java (the application)
                       |                              |
                       |                              /package-info.java (optional package-wide documentation/annotations)
                       |
                       /test/java/org/onosproject/myifwd/ (Unit tests go here)

注意:组件模版指南(Component Template Tutorial)演示了如何生成一个应用程序模版。https://wiki.onosproject.org/display/ONOS/Component+Template+Tutorial。如果使用模版的话,则项目骨架设置这一节内容就不是必须的。

2.1 设置目录结构

创建一个ONOS应用程序的第一步是创建如下的目录结构。这个目录结构遵守Maven惯例。

应用程序的根目录放在ONOS项目根目录下的 apps/ 子目录下:

$ mkdir -p ${ONOS_ROOT}/apps/myifwd

应用程序的源代码在应用程序根目录下的 src/main/java/… 中:

$ mkdir -p ${ONOS_ROOT}/apps/myifwd/src/main/java/org/onosproject/myifwd

类似的,单元测试存放目录为应用程序根目录下的 src/test/java/… :

$ mkdir -p ${ONOS_ROOT}/apps/myifwd/src/test/java/org/onosproject/myifwd

2.2添加和编辑POM文件

Maven使用POM文件来编译应用程序源代码。在应用程序根目录下,创建文件用于描述项目的 pom.xml 文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!--
  ~ Copyright 2014 Open Networking Laboratory
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->
<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.onosproject</groupId>
        <artifactId>onos-apps</artifactId>
        <version>1.1.0</version>
        <relativePath>../pom.xml</relativePath>
    </parent>

    <artifactId>onos-app-myifwd</artifactId>
    <packaging>bundle</packaging>

    <description>ONOS simple reactive forwarding app that uses intent service</description>

</project>

在本例中,最终的Karaf的软件集(bundle)名称为onos-app-myifwd。这个名称将在Karaf的CLI中的诸如 feature:install 等命令中使用。注意 1.1.0 中的版本号要跟你安装的ONOS版本号对应。

下一步,编辑文件 apps/pom.xmlmodules 部分,包含 myifwd注意:不是软件集名称,而是应用程序的根目录),内容如下:

<modules>
    <module>tvue</module>
    <module>fwd</module>
    <module>ifwd</module>        
    <module>myifwd</module>        <---这是添加的内容
    <module>foo</module>
    <module>mobility</module>
    <module>proxyarp</module>
    <module>config</module>
    <module>sdnip</module>
    <module>calendar</module>
    <module>optical</module>
    <module>metrics</module>
    <module>oecfg</module>
    <module>demo</module>
</modules>

2.3在Karaf中注册应用程序

Karaf运行时需要一个被称为 feature 的应用程序描述,以便部署此模块为一个OSGi软件集(bundle)。更详细的信息请参看Karaf的文档:http://karaf.apache.org/manual/latest/users-guide/provisioning.html

编辑文件 ${ONOS_ROOT}/features/features.xml ,添加下面的代码片段(描述了我们的应用程序的feature):

<feature name="onos-app-myifwd" version="1.1.0"
         description="My ONOS sample forwarding application using intents">
    <feature>onos-api</feature>
    <bundle>mvn:org.onosproject/onos-app-myifwd/1.1.0</bundle>
</feature>

本教程由http://sdnhub.cn根据https://wiki.onosproject.org/display/ONOS/Application+tutorial翻译和整理而成。转载请注明出处。基于ONOS最新的1.1.0发新版。

3.编写应用程序代码

有了应用程序的骨架后,接下来就可以开始编写代码了。这个 forwarding app 的核心java程序是 IntentReactiveForwarding.java ,它的保存目录为: ${ONOS_ROOT}/apps/src/main/java/org/onosproject/myifwd/

Tips:为了文档化或者包注释的目的,可以在应用程序中添加文件package-info.java(IDE通常会提供一个选项来生成该文件),内容如下:

/**
 * Sample reactive forwarding application using intent framework.
 */
package org.onosproject.myifwd;

本教程下面的内容,介绍如何构建 IntentReactiveForwarding Java类。

3.1 Register with Karaf to load automatically

Karaf的模块加载机制能够识别几个注释(它允许类去注册到Karaf),下面给出几个Karaf能够识别有用的注释:

  • @Component(immediate = true) – 声明一个类作为一个组件来激活,并且强制立即激活。
  • @Activate – 将方法标记为,组件启动(startup routine)时自动调用的方法。
  • @Deactivate – 将方法标记为,组件shutdown时自动调用的方法。
  • @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) – 将服务标记为应用程序所依赖的服务。应用程序激活前,保证必须有一个服务的实例被加载。

首先,将我们的应用程序连到Karaf,代码如下:

    /*
     * Copyright 2014 Open Networking Laboratory
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    package org.onosproject.myifwd;

    import org.apache.felix.scr.annotations.Activate;
    import org.apache.felix.scr.annotations.Component;
    import org.apache.felix.scr.annotations.Deactivate;
    import org.apache.felix.scr.annotations.Reference;
    import org.apache.felix.scr.annotations.ReferenceCardinality;

    import org.onosproject.core.ApplicationId;
    import org.onosproject.core.CoreService;
    import org.onosproject.net.Host;
    import org.onosproject.net.HostId;
    import org.onosproject.net.PortNumber;
    import org.onosproject.net.flow.DefaultTrafficSelector;
    import org.onosproject.net.flow.DefaultTrafficTreatment;
    import org.onosproject.net.flow.TrafficSelector;
    import org.onosproject.net.flow.TrafficTreatment;
    import org.onosproject.net.host.HostService;
    import org.onosproject.net.intent.HostToHostIntent;
    import org.onosproject.net.intent.IntentService;
    import org.onosproject.net.packet.DefaultOutboundPacket;
    import org.onosproject.net.packet.InboundPacket;
    import org.onosproject.net.packet.OutboundPacket;
    import org.onosproject.net.packet.PacketContext;
    import org.onosproject.net.packet.PacketProcessor;
    import org.onosproject.net.packet.PacketService;
    import org.onosproject.net.topology.TopologyService;
    import org.onlab.packet.Ethernet;
    import org.slf4j.Logger;

    import static org.slf4j.LoggerFactory.getLogger

    /**
     * Sample reactive forwarding application.
     */
    @Component(immediate = true)
    public class IntentReactiveForwarding {

        // a list of our dependencies :
        // to register with ONOS as an application - described next
        @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
        protected CoreService coreService;

        // topology information
        @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
        protected TopologyService topologyService;

        // to receive Packet-in events that we'll respond to
        @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
        protected PacketService packetService;

        // to submit/withdraw intents for traffic manipulation
        @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
        protected IntentService intentService;

        // end host information
        @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
        protected HostService hostService;

        // method called at startup
        @Activate
        public void activate() {
        }

        // method called at shutdown
        @Deactivate
        public void deactivate() {
        }
    }

关于注释的更详细信息参看 Felix documentation.

3.2 注册服务

接下来,我们的应用程序必须向 CoreService 注册,在应用程序使用 ONOS 的各种服务前,以获取一个唯一的应用程序ID号。然后,向 PacketService 注册,以实现监听网络流量事件(packet-ins)和发出packets(packet-outs)。 In specific, 需要给 PacketService 服务提供一个事件句柄(event handler,例如,一个实现了 PacketProcessor 接口的Java类)

注意:每个服务需要不同的句柄(handler),可以查看对应的 Javadoc 获得更信息的信息。

我们通过定义方法 activate() 和方法 deactivate() 实现向这两个服务注册和取消注册句柄。我们也定义了一个内部类来实现 PacketProcessor 接口,代码如下:

public class IntentReactiveForwarding {
    // for verbose output
    private final Logger log = getLogger(getClass());

    // ... <services go here> ...

    // our application-specific event handler
    private ReactivePacketProcessor processor = new ReactivePacketProcessor();

    // our unique identifier
    private ApplicationId appId;

    // method called at startup
    @Activate
    public void activate() {
        // "org.onlab.onos.myifwd" is the FQDN of our app
        appId = coreService.registerApplication("org.onosproject.myifwd");
        // register our event handler
        packetService.addProcessor(processor, PacketProcessor.ADVISOR_MAX + 2);

        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
        selector.matchEthType(Ethernet.TYPE_IPV4);
        packetService.requestPackets(selector.build(), PacketPriority.REACTIVE, appId);

        log.info("Started");
    }

    @Deactivate
    public void deactivate() {
        // deregister and null our handler
        packetService.removeProcessor(processor);
        processor = null;
        log.info("Stopped");
    }

    // our handler defined as a private inner class
    /**
     * Packet processor responsible for forwarding packets along their paths.
     */
    private class ReactivePacketProcessor implements PacketProcessor {
        @Override
        public void process(PacketContext context) {
        }
    }
}

3.3 添加包处理代码

每当收到一个网络包时 ReactivePacketProcessor 类的 process() 方法就会被调用,所以,我们可以在此方法内定义我们的包转发行为,代码如下:

// our handler defined as a private inner class
/**
 * Packet processor responsible for forwarding packets along their paths.
 */
private class ReactivePacketProcessor implements PacketProcessor {
    @Override
    public void process(PacketContext context) {
        // Stop processing if the packet has been handled, since we
        // can't do any more to it.
        if (context.isHandled()) {
            return;
        }

        // Extract the original Ethernet frame from the packet information
        InboundPacket pkt = context.inPacket();
        Ethernet ethPkt = pkt.parsed();

        // Find and source and destination hosts
        HostId srcId = HostId.hostId(ethPkt.getSourceMAC());
        HostId dstId = HostId.hostId(ethPkt.getDestinationMAC());

        // Do we know who this is for? If not, flood and bail.
        Host dst = hostService.getHost(dstId);
        if (dst == null) {
            flood(context);
            return;
        }

        // Otherwise forward and be done with it.
        setUpConnectivity(context, srcId, dstId);
        forwardPacketToDst(context, dst);
    }
} 

我们在 IntentReactiveForwarding 类中定义了几个在 process() 方法中调用的辅助方法,代码如下所示:

// Floods the specified packet if permissible.
private void flood(PacketContext context) {
    if (topologyService.isBroadcastPoint(topologyService.currentTopology(),
                                         context.inPacket().receivedFrom())) {
        packetOut(context, PortNumber.FLOOD);
    } else {
        context.block();
    }
}

// Sends a packet out the specified port.
private void packetOut(PacketContext context, PortNumber portNumber) {
    context.treatmentBuilder().setOutput(portNumber);
    context.send();
}

private void forwardPacketToDst(PacketContext context, Host dst) {
    TrafficTreatment treatment = DefaultTrafficTreatment.builder().setOutput(dst.location().port()).build();
    OutboundPacket packet = new DefaultOutboundPacket(dst.location().deviceId(),
                                                      treatment, context.inPacket().unparsed());
    packetService.emit(packet);
    log.info("sending packet: {}", packet);
}

// Install a rule forwarding the packet to the specified port.
private void setUpConnectivity(PacketContext context, HostId srcId, HostId dstId) {
    TrafficSelector selector = DefaultTrafficSelector.builder().build();
    TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();

    HostToHostIntent intent = new HostToHostIntent(appId, srcId, dstId,
                                                   selector, treatment);
    intentService.submit(intent);
}

注意: 尽管我们的应用程序控制了 OpenFlow 交换机,但是并没有使用具体的 OpenFlow 协议报文, intent 对特定的协议机制进行了抽象,以方便开发应用程序。

3.4 给出应用程序的完整代码:

/*
 * Copyright 2014 Open Networking Laboratory
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.onosproject.myifwd;

import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;

import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.Host;
import org.onosproject.net.HostId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.host.HostService;
import org.onosproject.net.intent.HostToHostIntent;
import org.onosproject.net.intent.IntentService;
import org.onosproject.net.packet.DefaultOutboundPacket;
import org.onosproject.net.packet.InboundPacket;
import org.onosproject.net.packet.OutboundPacket;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.packet.PacketPriority;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.onosproject.net.topology.TopologyService;
import org.onlab.packet.Ethernet;
import org.slf4j.Logger;

import static org.slf4j.LoggerFactory.getLogger;

/**
 * Sample reactive forwarding application.
 */
@Component(immediate = true)
public class IntentReactiveForwarding {

    // for verbose output
    private final Logger log = getLogger(getClass());

    // a list of our dependencies :
    // to register with ONOS as an application - described next
    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
    protected CoreService coreService;

    // topology information
    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
    protected TopologyService topologyService;

    // to receive Packet-in events that we'll respond to
    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
    protected PacketService packetService;

    // to submit/withdraw intents for traffic manipulation
    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
    protected IntentService intentService;

    // end host information
    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
    protected HostService hostService;

    // our application-specific event handler
    private ReactivePacketProcessor processor = new ReactivePacketProcessor();

    // our unique identifier
    private ApplicationId appId;

    // method called at startup
    @Activate
    public void activate() {
        // "org.onlab.onos.ifwd" is the FQDN of our app
        appId = coreService.registerApplication("org.onosproject.myifwd");
        // register our event handler
        packetService.addProcessor(processor, PacketProcessor.ADVISOR_MAX + 2);

        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
        selector.matchEthType(Ethernet.TYPE_IPV4);
        packetService.requestPackets(selector.build(), PacketPriority.REACTIVE, appId);

        log.info("Started");
    }

    // method called at shutdown
    @Deactivate
    public void deactivate() {
        // deregister and null our handler
        packetService.removeProcessor(processor);
        processor = null;
        log.info("Stopped");
    }

    // our handler defined as a private inner class
    /**
     * Packet processor responsible for forwarding packets along their paths.
     */
    private class ReactivePacketProcessor implements PacketProcessor {
        @Override
        public void process(PacketContext context) {
            // Stop processing if the packet has been handled, since we
            // can't do any more to it.
            if (context.isHandled()) {
                return;
            }

            // Extract the original Ethernet frame from the packet information
            InboundPacket pkt = context.inPacket();
            Ethernet ethPkt = pkt.parsed();

            // Find and source and destination hosts
            HostId srcId = HostId.hostId(ethPkt.getSourceMAC());
            HostId dstId = HostId.hostId(ethPkt.getDestinationMAC());

            // Do we know who this is for? If not, flood and bail.
            Host dst = hostService.getHost(dstId);
            if (dst == null) {
                flood(context);
                return;
            }

            // Otherwise forward and be done with it.
            setUpConnectivity(context, srcId, dstId);
            forwardPacketToDst(context, dst);
        }
    }

    // Floods the specified packet if permissible.
    private void flood(PacketContext context) {
        if (topologyService.isBroadcastPoint(topologyService.currentTopology(),
                                         context.inPacket().receivedFrom())) {
            packetOut(context, PortNumber.FLOOD);
        } else {
            context.block();
        }
    }

    // Sends a packet out the specified port.
    private void packetOut(PacketContext context, PortNumber portNumber) {
        context.treatmentBuilder().setOutput(portNumber);
        context.send();
    }

    private void forwardPacketToDst(PacketContext context, Host dst) {
        TrafficTreatment treatment = DefaultTrafficTreatment.builder().setOutput(dst.location().port()).build();
        OutboundPacket packet = new DefaultOutboundPacket(dst.location().deviceId(),
                                                      treatment, context.inPacket().unparsed());
        packetService.emit(packet);
        log.info("sending packet: {}", packet);
    }

    // Install a rule forwarding the packet to the specified port.
    private void setUpConnectivity(PacketContext context, HostId srcId, HostId dstId) {
        TrafficSelector selector = DefaultTrafficSelector.builder().build();
        TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();

        HostToHostIntent intent = new HostToHostIntent(appId, srcId, dstId,
                                                   selector, treatment);
        intentService.submit(intent);
    }
}

3.5 编译应用程序

由于应用程序是自己创建的一个工程,所以可以独立于其它的ONOS应用而单独构建,在应用程序的根目录下运行如下的maven命令:

root@mininet-vm:~/onos/apps/myifwd# mvn clean install
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building onos-app-myifwd 1.1.0
[INFO] ------------------------------------------------------------------------
[INFO] 

此处省略n多字 :) ---

[INFO] Installing org/onosproject/onos-app-myifwd/1.1.0/onos-app-myifwd-1.1.0.jar
[INFO] Writing OBR metadata
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 16.496 s
[INFO] Finished at: 2015-03-27T13:16:35-07:00
[INFO] Final Memory: 27M/71M
[INFO] ------------------------------------------------------------------------
root@mininet-vm:~/onos/apps/myifwd# 

4.启动应用程序

4.1 动态加载应用程序

应用程序可以在 ONOS CLI 中加载,可以在 Karaf Web Console 中加载。

使用如下命令启动ONOS:

# karaf clean

注意: 在ONOS的CLI中使用如下命令加载应用程序:

onos> feature:install onos-app-myifwd

如果提示找不到应用程序,则需要将如下的内容:

<feature name="onos-app-myifwd" version="1.1.0"
         description="My ONOS sample forwarding application using intents">
    <feature>onos-api</feature>
    <bundle>mvn:org.onosproject/onos-app-myifwd/1.1.0</bundle>
</feature>

加入到下面的这个xml文件中:

~/.m2/repository/org/onosproject/onos-features/1.1.0/onos-features-1.1.0-features.xml 

原因,在源码安装ONOS时,karaf启动时从上面的这个xml文件加载feature信息,但是,我们自己编译应用程序后此文件中的feature信息并没有自动更新(是否有方法自动更新呢?有待研究一下),所以手动加上对应的feature配置信息就OK。

4.2 静态加载应用程序

要想在 ONOS 启动时自动加载应用程序,需要修改 org.apache.karaf.features.cfg (在Karaf安装目录的etc子目录下)。修改 featuresBoot 部分,添加需要在启动时自动加载的应用onos-app-myifwd :

org.apache.karaf.features.cfg

featuresBoot=config,standard,region,package,kar,ssh,management,webconsole,onos-api,onos-core-trivial,onos-cli,onos-openflow,onos-gui,onos-rest,onos-app-mobility,onos-app-myifwd

ONOS必须重启才能使上面的配置生效。

5.下一步?

  • 尝试将应用程序扩展为一个服务,用一个命令扩展CLI,学习:CLI and Service Tutorial,Try extending this application into a service, and extend the CLI with a command that uses it, in the CLI and Service Tutorial.
  • 在组件模版教程中(Component Template Tutorial)学习如何创建应用程序模版。
  • See what is involved in extending the southbound in the Provider Tutorial.

本教程由http://sdnhub.cn根据https://wiki.onosproject.org/display/ONOS/Application+tutorial翻译和整理而成。转载请注明出处。

本文固定链接: http://sdnhub.cn/index.php/onos-application-tutorial/ | 软件定义网络SDN

该日志由 sdnhub 于2015年03月29日发表在 ONOS 分类下, 通告目前不可用,你可以至底部留下评论。
原创文章转载请注明: ONOS Application tutorial | 软件定义网络SDN
关键字: , ,

ONOS Application tutorial:等您坐沙发呢!

发表评论

*

快捷键:Ctrl+Enter