Jump to: navigation, search

Ping

Overview

In this example we use MD-SAL to implement a simple TCP ping plugin that can be used to check reachability of IP addresses. First we will define the ping model with Yang, then implement the simple plugin and service for ping and finally define REST API interface to initiate the ping through northbound interface.


This example will create four OSGI bundles.

  1. Service Bundle [ping-service]
    • The service bundle links the northbound implementation with the south bound (MD-SAL) definitions.
  2. 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)
  3. Specific Implementation of South Bound Interface
    • Defines the specific of the south bound interface, generally protocol specific etc.
  4. Northbound API / Implementation [ping-northbound] (OPTIONAL)
    • The fourth bundle created is optional and just illustrates how you can create your own REST northbound interface. However by doing so you loose the benefits of a consistent auto-generated RESTful interface tied to your yang model.
    • The northbound API defines the interface for interacting with the given service. For example, a REST, or JSON interface for invoking the ping service.


Shortcut

A patch file which contains the changes mentioned in this page can be obtained via this link.

Compile ODL controller

Following commands are used to clone ODL controller Git repository and compile clean copy of ODL controller.


git clone https://git.opendaylight.org/gerrit/p/controller.git

Check that the used Yang tools version is >= 0.5.8-SNAPSHOT.

vi controller/opendaylight/commons/opendaylight/pom.xml
...
<yangtools.version>0.5.8-SNAPSHOT</yangtools.version>
...

Compile the ODL controller.

cd controller/opendaylight/distribution/opendaylight
mvn clean install

Yang definition for ping

First we need to create the Yang project that can generate Java APIs from Yang model files. Easiest way to do this is to add the new Yang model together with existing Yang models in 'yang-prototype/sal/model'. This way we can reuse the pom.xml from other models. To create the ping Yang project run the following commands after you have the ODL controller code checked out and compiled.

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

We create the 'ping.yang' into the correct directory.

vi src/main/yang/ping.yang

In the ping Yang module we define a single RPC call to initiate the ping request. This call takes an IPv4 address as an input variable. For IPv4 address we have imported the 'ietf-inet-types' module. We also define simple output for the RPC call that is returned to the caller.

module ping {
  namespace "urn:opendaylight:ping";
  prefix ping;
  import ietf-inet-types {prefix inet;}
  revision "2013-09-11" {
    description "TCP ping module";
  }
  rpc send-echo {
    description "Send TCP ECHO request";
    input {
      leaf destination {
        type inet:ipv4-address;
      }
    }
    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";
      }
    }
  }
}

We need to create the pom.xml file for the project.

vi pom.xml

When using the 'model-parent' the pom.xml can be really simple. If the build fails because parent pom cannot be found, you may need to update the <version> tag of the parent to match that used in other poms in the md-sal/model directory


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

Now compile and generate the APIs and the bundle.

mvn clean install

Copy the bundle into the plugins directory of compiled ODL controller in order to run it.

NOTE: This is no longer the supported distribution method. We should be using karaf features etc here.TODO: Provide updated instruction on how to push in new bundles etc
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

Next we define a project that provides the Ping plugin service. We create this project into 'controller/opendaylight/md-sal/samples' directory.

cd ../samples
mkdir ping
cd ping

To create this service we will be using the configuration subsystem to wire in our dependencies (in this simple case, just the RPC-registry). The configuration for the dependencies that are needed are also defined in yang. So we will create a yang file which represents the required services. To do this, make the src/main/yang directory structure and vi a new .yang file.

mkdir -p src/main/yang/
vi src/main/yang/ping-provider-impl.yang

Paste in the following contents:

module ping-provider-impl {

    yang-version 1;
    namespace "urn:opendaylight:params:xml:ns:yang:controller:config:ping-provider:impl";
    prefix "ping-provider-impl";

    import config { prefix config; revision-date 2013-04-05; }
    import opendaylight-md-sal-binding { prefix mdsal; revision-date 2013-10-28; }

    description
        "This module contains the base YANG definitions for
        ping-provider impl implementation.";

    revision "2014-05-23" {
        description
            "Initial revision.";
    }

    // This is the definition of the service implementation as a module identity.
    identity ping-provider-impl {
            base config:module-type;

            // Specifies the prefix for generated java classes.
            config:java-name-prefix PingProvider;
    }

    // Augments the 'configuration' choice node under modules/module.  
    // We consume the three main services, RPCs, DataStore, and Notifications 
    augment "/config:modules/config:module/config:configuration" {
        case ping-provider-impl {
            when "/config:modules/config:module/config:type = 'ping-provider-impl'";
    
            container rpc-registry {
                uses config:service-ref {
                    refine type {
                        mandatory true;
                        config:required-identity mdsal:binding-rpc-registry;
                    }
                }
            }
        }
    }
}

Now that we have the contents of the wiring, we need to create a pom that will auto-generate some code for us.

vi pom.xml

In the pom.xml we have defined dependency to the Yang model previously created and imported 'org.opendaylight.yang.gen.v1.urn.opendaylight.ping.rev130911' package. Also 'org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924' is needed for IPv4 address definition.

<?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/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.opendaylight.controller.samples</groupId>
    <artifactId>sal-samples</artifactId>
    <version>1.2.0-SNAPSHOT</version>
  </parent>
  <artifactId>sample-ping-provider</artifactId>
  <packaging>bundle</packaging>

  <properties>
    <sal-binding-api.version>1.1-SNAPSHOT</sal-binding-api.version>
  </properties>

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

    <!-- dependencies to use AbstractDataBrokerTest -->
    <dependency>
      <groupId>org.opendaylight.controller</groupId>
      <artifactId>sal-binding-broker-impl</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.opendaylight.controller</groupId>
      <artifactId>sal-binding-broker-impl</artifactId>
      <type>test-jar</type>
      <scope>test</scope>
    </dependency>
    <dependency>
        <artifactId>junit</artifactId>
        <groupId>junit</groupId>
        <scope>test</scope>
    </dependency>
    <!-- used to mock up classes -->
     <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-all</artifactId>
      <scope>test</scope>
    </dependency>

  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <configuration>
          <instructions>
            <Export-Package>org.opendaylight.controller.config.yang.ping_provider,</Export-Package>
            <Import-Package>*</Import-Package>
          </instructions>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.opendaylight.yangtools</groupId>
        <artifactId>yang-maven-plugin</artifactId>
        <executions>
          <execution>
            <id>config</id>
            <goals>
              <goal>generate-sources</goal>
            </goals>
            <configuration>
              <codeGenerators>
                <generator>
                  <codeGeneratorClass>org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator</codeGeneratorClass>
                  <outputBaseDir>${jmxGeneratorPath}</outputBaseDir>
                  <additionalConfiguration>
                    <namespaceToPackage1>urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang</namespaceToPackage1>
                  </additionalConfiguration>
                </generator>
                <generator>
                  <codeGeneratorClass>org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl</codeGeneratorClass>
                  <outputBaseDir>${salGeneratorPath}</outputBaseDir>
                </generator>
              </codeGenerators>
              <inspectDependencies>true</inspectDependencies>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  <scm>
    <connection>scm:git:ssh://git.opendaylight.org:29418/controller.git</connection>
    <developerConnection>scm:git:ssh://git.opendaylight.org:29418/controller.git</developerConnection>
    <tag>HEAD</tag>
    <url>https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL</url>
  </scm>
</project>

Now run a build.

mvn clean install

Your code should compile and you should see some auto-generated files under src/main/yang-gen-sal and yang-gen-config. You will also see some code under src/main/java - these files (under java) should be checked into source as they will not be generated again. Now we need to create the implementation of our ping service. Create the implementation of the Ping into correct directory.

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

The ping implementation uses 'InetAddress.isReachable(timeout)' to check if given address is reachable or not. The listed class implements the 'PingService' interface from Yang model. This adds the defined RPC method sendEcho into the class. The RPC method takes 'SendEchoInput' (IPv4 address) in as a parameter and returns 'SendEchoOutput' (result enumeration). Result is returned in output format of Yang model 'EchoResult.Reachable' or 'EchoResult.Unreachable'. If an error happens 'EchoResult.Error' is returned.

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

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

Now we have to instantiate our ping service when MD-SAL tries to invoke our bundle. We do this by wiring our service into the auto generated PingProviderModule.java class. We add to the existing file a logger, and modify the createInstance method to create our PingImpl and wire it to the MD-SAL.

package org.opendaylight.controller.config.yang.config.ping_provider.impl;

import org.opendaylight.controller.ping.plugin.internal.PingImpl;
import org.opendaylight.controller.sal.binding.api.BindingAwareBroker;
import org.opendaylight.yang.gen.v1.urn.opendaylight.ping.rev130911.PingService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PingProviderModule extends org.opendaylight.controller.config.yang.config.ping_provider.impl.AbstractPingProviderModule {

    private static final Logger log = LoggerFactory.getLogger(PingProviderModule.class);

    public PingProviderModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
        super(identifier, dependencyResolver);
    }

    public PingProviderModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, org.opendaylight.controller.config.yang.config.ping_provider.impl.PingProviderModule oldModule, java.lang.AutoCloseable oldInstance) {
        super(identifier, dependencyResolver, oldModule, oldInstance);
    }

    @Override
    public void customValidation() {
        // add custom validation form module attributes here.
    }

    @Override
    public java.lang.AutoCloseable createInstance() {
        final PingImpl opendaylightPing = new PingImpl();

        // Register to md-sal
        final BindingAwareBroker.RpcRegistration<PingService> rpcRegistration = getRpcRegistryDependency()
                .addRpcImplementation(PingService.class, opendaylightPing);

        // Wrap toaster as AutoCloseable and close registrations to md-sal at
        // close()
        final class AutoCloseableToaster implements AutoCloseable {

            @Override
            public void close() throws Exception {
                rpcRegistration.close();
                log.info("Ping provider (instance {}) torn down.", this);
            }

            private void closeQuietly(final AutoCloseable resource) {
                try {
                    resource.close();
                } catch (final Exception e) {
                    log.debug("Ignoring exception while closing {}", resource, e);
                }
            }
        }

        AutoCloseable ret = new AutoCloseableToaster();
        log.info("Ping provider (instance {}) initialized.", ret);
        return ret;
    }
}


TODO - manual install bundles / config file

TODO: These steps do not work. I am not able to deploy these bundles and a manual configuration file and have it start up in karaf. We need to document how to do this quickly

Now, start the karaf controller, by going to the distribution built under controller/opendaylight/distributions/opendaylight-karaf/target/distribution.opendaylight-karaf-1.5.0-SNAPSHOT. Unzip it, and then run it via bin/karaf.

cd  controller/opendaylight/distributions/opendaylight-karaf/target/
unzip distribution.opendaylight-karaf-1.5.0-SNAPSHOT.zip
cd distribution.opendaylight-karaf-1.5.0-SNAPSHOT
bin/karaf

When the karaf user interface starts up, install the odl-restconf feature.

feature:install odl-restconf

Shutdown the controller.

Copy the two jars (model and provider) to the deploy directory on karaf (note: the deploy directory deploys the bundle and makes it available in OSGi. It can be used for quick development testing, but should not be used in production).

cp controller/opendaylight/md-sal/models/model-ping/target/model-ping-1.2.0-SNAPSHOT.jar controller/opendaylight/distributions/opendaylight-karaf/target/distribution.opendaylight-karaf-1.5.0-SNAPSHOT/deploy
cp controller/opendaylight/md-sal/samples/ping/target/sample-ping-provider-1.2.0-SNAPSHOT.jar controller/opendaylight/distributions/opendaylight-karaf/target/distribution.opendaylight-karaf-1.5.0-SNAPSHOT/deploy

Now go to <karaf-home>/etc/opendaylight/karaf and create a new configuration file there. Lets call it 03-ping.xml. In that file, add the following contents:

<snapshot>
    <configuration>
        <data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
            <modules xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
                <module>
                    <type xmlns:ping="urn:opendaylight:params:xml:ns:yang:controller:config:ping-provider:impl">
                        ping:ping-provider-impl
                    </type>
                    <name>ping-provider-impl</name>
                    
                    <rpc-registry>
                        <type xmlns:binding="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">binding:binding-rpc-registry</type>
                        <name>binding-rpc-broker</name>
                    </rpc-registry>

                </module>
            </modules>
        </data>
        
    </configuration>
    
    <required-capabilities>
        <capability>urn:opendaylight:params:xml:ns:yang:controller:config:ping-provider:impl?module=ping-provider-impl&revision=2014-05-23</capability>
    </required-capabilities>
    
</snapshot>

Restart the controller (NOTE: unknown how to get this configuration to be used). You are all set! Now move onto the next step and issue your ping via restconf.

Accessing Ping RPC via REST API

Now that we have created a yang model and implemented a provider for this, we can access the send-echo RPC through the RESTCONF API that is automatically generated for us. This can be done either using CURL or the POSTMAN plugin for Chrome browser or any other similar tool. To send the command perform a POST operation to the following URL:

http://localhost:8080/restconf/operations/ping:send-echo
With the following headers:
Content-Type: application/yang.operation+json
Cache-Control: no-cache
and the data set to raw Json encoded data (for POSTMAN at least) 
{ "input" : { "destination" : "192.168.56.102" } } Note: Replace the IP address with the IP address that you want to ping.

Once you submit the request you should get back the response. For example:

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

The sections below show how to implement a consumer for the ping module and then implement a REST API for the consumer. An alternative to manually creating the REST API for the ping service consumer is to make it a provider as well and create a yang model for the API.

Defining a Custom Northbound REST API (Optional)

To define a custom rest interface follow these steps. Note, it is not recommended to define a custom REST API that provides the same functionality as a restconf API as restconf provides a uniformity of API (since they are auto generated) for users among other benefits.

Ping service

In this example we use Ping service in between the Ping northbound and Ping plugin. This way the northbound doesn't need to be dependent on the Yang model. We create the Ping service project in to the 'controller/opendaylight/ping' directory.

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

Once again we need a pom.xml for the new project.

vi pom.xml

In the pom.xml file we define the bundle activator and export the service interface so other bundles can use it (e.g. 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>
            <Bundle-Activator>org.opendaylight.controller.ping.service.impl.PingServiceImpl</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.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>

We the define the Ping service interface.

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

The Ping service interface is a simple interface that takes the destination address as String and returns boolean based on whether the address is reachable or not.

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

We then implement the Ping service.

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

This class extends 'AbstractBindingAwareConsumer' this provides callbacks from OSGi framework when bundle is started. In 'onSessionInitialized' method the 'ConsumerContext' is stored for later use and in 'startImpl' the service interface provided by this class is registered. The implemented interface method 'pingDestination' takes in the address as a String. It will first look for the Ping RPC service defined in the Yang. If found the method will create the Ipv4Address build the 'SendEchoInput' and call 'sendEcho' in the Ping plugin. The resulting 'SendEchoOutput' is mapped to boolean value for caller.

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

}

Compile the Ping service.

mvn clean install

Copy the resulting bundle to ODL plugin directory.

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

To use the Ping service and plugin we implement northbound REST API. This project is created into 'controller/opendaylight/ping' directory.

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

Create the pom.xml for this project.

vi pom.xml

We need to import the Ping service package (exported by Ping service project). In addition we need to specify Web service dependenty things e.g. 'Web-ContextPath'. In this example we want to use HTTP PUT method to send ping request through URI http://localhost:8080/controller/nb/v2/ping/{ipAddress} therefore the root is defined as '/controller/nb/v2'.

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

Create enunciate.xml file.

vi enunciate.xml

Copied from other northbound project and edited for Ping northbound.

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

Then we create web.xml.

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

The web.xml (copied from other northbound project) define a servlet 'JAXRSPing' that is mapped to 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>

Create the source files.

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

This file adds the 'PingNorthbound' as a Web service application. To it is called when REST calls to a specific URI are coming in.

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

Create the northbound implementation.

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

Ping northbound defines on method 'ping' that is called when the specified URI path (@Path("/ping/{ipAddress}")) within ODL controller is called with HTTP PUT. The method the looks up the Ping service interface and calls 'pingDestination' method. It constructs the HTTP response based on the return value.

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

Compile the Ping northbound.

mvn clean install

Copy Ping northbound bundle to ODL controller 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

Testing

Run the ODL controller with the Ping bundles.

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

Use 'curl' to send HTTP ping request from command line.

$ 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

Also RESTClient in Firefox can be used to test the Ping.

Compile together with main project

All Ping projects can be compiled and installed as part of the main project by adding them as modules to existing pom.xml files.

Yang modules are compiled with pom.xml in 'sal/yang-prototype/sal/modules'.

vi controller/opendaylight/sal/yang-prototype/sal/model/pom.xml

Add:

 <module>model-ping</module>

Into <modules> section:

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

Rest of the projects are compiled from pom.xml in 'distribution/opendaylight'

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

Add Ping projects:

 <!-- Ping -->
 <module>../../ping/service</module>
 <module>../../ping/plugin</module>
 <module>../../ping/northbound</module>

Into the <modules> section:

 ...
    <!-- Samples -->
    <module>../../samples/simpleforwarding</module>
    <module>../../samples/loadbalancer</module>
    <module>../../samples/northbound/loadbalancer</module>

    <!-- Ping -->
    <module>../../ping/service</module>
    <module>../../ping/plugin</module>
    <module>../../ping/northbound</module>

    <!-- Parents -->
    <module>../../commons/concepts</module>
    <module>../../commons/integrationtest</module>
    <module>../../commons/checkstyle</module>
    <module>../../commons/opendaylight</module>
    <module>../../commons/parent</module>
  </modules>
  ...

Compile everything from command line.

cd controller/opendaylight/distribution/opendaylight
mvn clean install

Or use the 'opendaylight-asembleit' in eclipse.

Comments

Problem #1: After defining ping.yang and the pom.xml, mvn clean install gave the below error:

~/controller/opendaylight/md-sal/model/model-ping:$ mvn clean install
[INFO] Scanning for projects...
[ERROR] The build could not read 1 project -> [Help 1]
[ERROR]
[ERROR] The project org.opendaylight.controller.model:model-ping:1.1-SNAPSHOT (/home/ubuntu/controller/opendaylight/md-sal/model/model-ping/pom.xml) has 1 error
[ERROR] Non-resolvable parent POM for org.opendaylight.controller.model:model-parent:1.1-SNAPSHOT: Could not find artifact org.opendaylight.controller:sal-parent:pom:1.1-SNAPSHOT @ org.opendaylight.controller.model:model-parent:1.1-SNAPSHOT, /home/ubuntu/.m2/repository/org/opendaylight/controller/model/model-parent/1.1-SNAPSHOT/model-parent-1.1-SNAPSHOT.pom, line 3, column 13 -> [Help 2]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException
[ERROR] [Help 2] http://cwiki.apache.org/confluence/display/MAVEN/UnresolvableModelException

Solution: Remove the <relativePath></relativePath> entry from the pom.xml. The pom.xml should be:

<?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>
  </parent>
   
  <modelVersion>4.0.0</modelVersion>
  <artifactId>model-ping</artifactId>
  <packaging>bundle</packaging>
</project>

Problem #2: While testing the rest api on section 'Accessing Ping RPC via REST API' you may see the following exceptions in stderr:

...
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123) [bundlefile:na]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:581) [bundlefile:na]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168) [bundlefile:na]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99) [bundlefile:na]
	at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:929) [bundlefile:na]
	at org.apache.catalina.authenticator.SingleSignOn.invoke(SingleSignOn.java:336) [bundlefile:na]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) [bundlefile:na]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407) [bundlefile:na]
	at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1002) [bundlefile:na]
	at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:585) [bundlefile:na]
	at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312) [bundlefile:na]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_51]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_51]
	at java.lang.Thread.run(Thread.java:744) [na:1.7.0_51]
2014-05-10 16:21:48.964 EDT [http-bio-8080-exec-7] ERROR o.o.c.sal.restconf.impl.RestCodec - ClassCastException was thrown when codec is invoked with parameter Unreachable
java.lang.ClassCastException: org.opendaylight.yang.gen.v1.urn.opendaylight.ping.rev130911.SendEchoOutput$EchoResult cannot be cast to java.lang.String
	at org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec$EnumCodecStringImpl.serialize(TypeDefinitionAwareCodec.java:512) ~[bundlefile:na]
	at org.opendaylight.controller.sal.restconf.impl.RestCodec$ObjectCodec.serialize(RestCodec.java:141) ~[bundlefile:na]
	at org.opendaylight.controller.sal.rest.impl.JsonMapper.writeValueOfNodeByType(JsonMapper.java:240) [bundlefile:na]
	at org.opendaylight.controller.sal.rest.impl.JsonMapper.writeLeaf(JsonMapper.java:193) [bundlefile:na]
	at org.opendaylight.controller.sal.rest.impl.JsonMapper.writeChildrenOfParent(JsonMapper.java:118) [bundlefile:na]
	at org.opendaylight.controller.sal.rest.impl.JsonMapper.writeContainer(JsonMapper.java:154) [bundlefile:na]
	at org.opendaylight.controller.sal.rest.impl.JsonMapper.write(JsonMapper.java:70) [bundlefile:na]
	at org.opendaylight.controller.sal.rest.impl.StructuredDataToJsonProvider.writeTo(StructuredDataToJsonProvider.java:61) [bundlefile:na]
	at org.opendaylight.controller.sal.rest.impl.StructuredDataToJsonProvider.writeTo(StructuredDataToJsonProvider.java:33) [bundlefile:na]
	at com.sun.jersey.spi.container.ContainerResponse.write(ContainerResponse.java:306) [jersey-server-1.17.jar:1.17]
	at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1479) [jersey-server-1.17.jar:1.17]
	at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1391) [jersey-server-1.17.jar:1.17]
...

Solution:

Pick up fixes for bug-990:  https://bugs.opendaylight.org/show_bug.cgi?id=990