Overview

An ONOS application is essentially an OSGi bundle built as a Maven project. Therefore, an ONOS application can be boiled down to a collection of Java classes and POM files (Maven Program Object Manager files, written in XML.) This tutorial discusses how to manually roll out an application from scratch, using the intent-based reactive forwarding application as an example. 

By completing this tutorial, you will understand:

Conventions

Before we begin, we note that we make use of collapsible code blocks so that the page doesn't get too long and cluttered. A collapsed block has an Expand Source link that displays the contents when selected:

${ONOS_ROOT} refers to the project root directory for ONOS. For example, if the project directory is ~/onos,  cd ${ONOS_ROOT} is equivalent to cd ~/onos .

Finally, we assume that you have checked out the ONOS source code as per Getting ONOS, set up your environment as per Environment Setup, and have imported it to some IDE, as per IDE Setup. This exercise can be done using any text editor, but an IDE makes life much easier. 

Project skeleton setup

As a reference, the structure of our application can be summarized by the following directory structure:

${ONOS_ROOT}/apps/pom.xml (apps parent POM file)
              |  
              /ifwd/pom.xml (application POM file)
                   |
                   /src/main/java/org/onosproject/ifwd/IntentReactiveForwarding.java (the application)
                       |                              |
                       |                              /package-info.java (optional package-wide documentation/annotations)
                       |
                       /test/java/org/onosproject/ifwd/ (Unit tests go here)
The Component Template Tutorial demonstrates how to generate a template for an application. Templates make the steps in this section for creating a project skeleton unnecessary.

1. Set up a directory layout

The first step to creating a new application is to build the following directory structure. This structure follows Maven's conventions.

The application root directory is placed under apps/ of the ONOS project root:

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

The application source class definitions are placed under src/main/java/... of the application root:

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

Similarly, unit tests are placed under src/test/java/... of the application root:

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

2. Add and edit POM files

The POM files are used by Maven to build the application. In the application root directory, add a pom.xml file describing the project:

 

<?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.0.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>

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

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

</project>

In this case, the final bundle will be known as 'onos-app-ifwd'. This would be the name used with commands such as feature:install from the Karaf/ONOS CLI.

Next, edit apps/pom.xml to include ifwd (note, not the bundle name, but the application root directory) as one of its child projects:

    <modules>
        <module>tvue</module>
        <module>fwd</module>
        <module>ifwd</module>        <---added here
        <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>
 

3. Register the Application with Karaf

Karaf runtime requires an application description called a feature to deploy the module as an OSGi bundle (see the Karaf documentation for more detail). Edit ${ONOS_ROOT}/features/features.xml to include the following snippet, which describes the feature declaration for our application.

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

Writing the application

Once we have a project skeleton, we can begin to write our application. The core of the forwarding app is named IntentReactiveForwarding.java, and is defined in ${ONOS_ROOT}/apps/src/main/java/org/onosproject/ifwd/ . 

For the sake of documentation or package-wide annotations, a package-info.java file may be added along with the application, containing the following:

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

An IDE will often provide an option to generate this file.

The rest of the tutorial describes how to build out the IntentReactiveForwarding class.

1. Register with Karaf to load automatically

Karaf's module loading mechanism recognizes several annotations that allow a class to register with it. In particular:

We first start by "wiring up" our application to 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.ifwd;
 
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() {
    }
}

More information about these and other annotations are available on the Felix documentation.

2. Register for Services.

Next, our application must register with CoreService to acquire an unique application ID before it can make use of ONOS's various services. It may then register with the PacketService to listen in on network traffic events (packet-ins) and to send out packets (packet-outs). In specific, the PacketService needs to be provided with an event handler (i.e., a class implementing PacketProcessor). 

Each service expects a different type of handler. Consult its Javadocs for more information.

Our modified activate() and deactivate() methods now register and deregister from these two services, and we also define a PacketProcessor implementation as an inner class:

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;
 
    @Activate
    public void activate() {
        // "org.onlab.onos.ifwd" is the FQDN of our app
        appId = coreService.registerApplication("org.onlab.onos.ifwd");
        // register our event handler
        packetService.addProcessor(processor, PacketProcessor.ADVISOR_MAX + 2);
        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. Add packet handling code.

The method process() of our handler is called by the PacketService each time it receives a network packet. This means that we can define our packet forwarding behavior in this method:

    // 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);
        }
    }

We define the helper functions used by process() in IntentReactiveForwarding (the outer class).

    // 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);
    }

Note that, even though we are controlling OpenFlow switches with our application, we do not speak in terms of OpenFlow messages. The intent framework abstracts the protocol-specific mechanisms away. 

4. Build the application.

Since the application is a project of its own, it can be built independently of the rest of ONOS by running Maven from the project root directory:

$ cd ${ONOS_ROOT}/apps/ifwd && maven clean install
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] Building onos-app-ifwd 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------

... <more output>

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6.258s
[INFO] Finished at: Mon Dec 01 23:09:21 PST 2014
[INFO] Final Memory: 30M/316M
[INFO] ------------------------------------------------------------------------
$

Our application may now be loaded and used.

Starting the application

Dynamically (at runtime)

The application can be enabled both from the ONOS CLI and the Karaf Web Console. In the first case, just type the following command in the ONOS CLI:

onos> feature:install onos-app-ifwd

Statically (at startup)

The application can be loaded by configuring org.apache.karaf.features.cfg, in the /etc folder of the Karaf installation. This configuration file contains a list of features that Karaf loads when is started (featuresBoot). We can simply add the feature onos-app-ifwd in this list:

 

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-ifwd

ONOS must be restarted in this case.

What next?

 


Return To : Tutorials and Walkthroughs