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:
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.
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. |
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 |
<?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> |
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> |
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:
An IDE will often provide an option to generate this file. |
The rest of the tutorial describes how to build out the Intent
ReactiveForwarding
class.
Karaf's module loading mechanism recognizes several annotations that allow a class to register with it. In particular:
@Component(immediate = true)
- declares a class as a component to activate, and forces immediate activation. @Activate
- marks a method as the method to call during the component startup routine.@Deactivate
- marks a method as the method to call during component shutdown. @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
- Marks a service as an application's dependency, and requires one instance of such a service to be loaded before this application's activation.
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.
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) { } } } |
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.
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.
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 |
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.
Return To : Tutorials and Walkthroughs