Jump to: navigation, search

Controller Core Functionality Tutorials:Application Development Tutorial

Contents

Introduction

This tutorial was written for the ODL Design Summit 2015.

At various points when slides are referred to, those slides can be found attached here: File:Li Summit ODL Tutorial.pptx and uploaded online on Google Drive

Also mentioned is a "USB key" with VMs and other items. That USB Key Image can be found here

Finally, since the Tutoral at the ODL Design Summit 2015 was recorded, here are the YouTube recordings:

Setup Development Environment

You have been provided with a USB key with:

  1. VirtualBox Installer
  2. ODL.ova - A VM with a dev environment
  3. m2.zip - a zip of the m2 used in ODL.ova should you choose to use your own local dev env instead of the VM.
  4. #opendaylight-tutorial - IRC channel for this tutorial
  5. For Ubuntu Linux USB key fix: sudo apt-get install exfat-fuse exfat-utils
  6. For Fedora Linux USB key fix: sudo yum install fuse-exfat exfat-utils


Password on the VM is "ODL-Developer"

Exercise 1: Create a new project from the startup archetype

Create the hello project

OpenDaylight is built using the maven build system. Maven provides a facility called 'archetypes' to template out new projects.

We will use a maven archetype to create our HelloWorld App by running the following command in a terminal:

cd ~/git/
mvn archetype:generate -DarchetypeGroupId=org.opendaylight.controller \
-DarchetypeArtifactId=opendaylight-startup-archetype \
-DarchetypeVersion=1.3.0-SNAPSHOT \
-DarchetypeRepository=http://nexus.opendaylight.org/content/repositories/opendaylight.snapshot/ \
-DarchetypeCatalog=http://nexus.opendaylight.org/content/repositories/opendaylight.snapshot/archetype-catalog.xml

And responding to the prompts as follows (feel free to substitute your or your companies copyright in the example below):

Define value for property 'groupId': : org.opendaylight.hello
Define value for property 'artifactId': : hello
Define value for property 'package':  org.opendaylight.hello: : 
Define value for property 'classPrefix':  Hello: : 
Define value for property 'copyright': : Yoyodyne, Inc.

Where no new value is shown, you are hitting enter to accept the defaults. If you want to change any of the defaults, say 'N' at the last question; which will give you the opportunity to change them.

After it finishes running you should have a new top level directory:

hello/

cd into that directory:

cd hello/

and look around:

api
artifacts
features
impl
it
karaf
pom.xml


【Attention Please!】Edit by Mao Jianwei

If you get this Failure:

[WARNING] Error initializing: org.codehaus.plexus.velocity.DefaultVelocityComponent
java.lang.NoClassDefFoundError: org/apache/commons/lang/StringUtils

Add this dependency in the ~/.m2/repository/org/apache/maven/plugins/maven-archetype-plugin/{version}/maven-archetype-plugin-{version}.pom

<dependency>
    <groupId>commons-lang</groupId>
    <artifactId>commons-lang</artifactId>
    <version>2.6</version>
</dependency>

Build the hello project

mvn -nsu clean install

Running the hello project

cd karaf/target/assembly/bin
./karaf

This will present the karaf prompt:

opendaylight-user@root>

Verifying

At the karaf prompt type:

log:tail

and wait to see a message:

HelloProvider Session Initiated

to exit karaf:

shutdown -f

Intermission: Import hello into Eclipse

We will import hello into Eclipse together as a group.
You can follow along with the video here.

Understanding where the log line came from (the entry point)

The entry point is in the impl project:

impl/src/main/java/org/opendaylight/hello/impl/HelloProvider.java

In the HelloProvider.onSessionInitiate method:

@Override
    public void onSessionInitiated(ProviderContext session) {
        LOG.info("HelloProvider Session Initiated");
    }

This is the method you would add any new things you are doing in your implementation. It is analogous to an Activator.

Conceptual Intro: Yangtools

Slides gone through as a group.

Conceptual Intro: MD-SAL Broker

Slides gone through as a group

Conceptual Intro: RPCs

Slides gone through as a group

Exercise 2: Model a simple HelloWorld RPC

Edit

api/src/main/yang/hello.yang

Edit this file to look as below. You will see that we are adding the code in a YANG module to define the 'hello-world' RPC:

module hello {
    yang-version 1;
    namespace "urn:opendaylight:params:xml:ns:yang:hello";
    prefix "hello";

    revision "2015-01-05" {
        description "Initial revision of hello model";
    }
    rpc hello-world {
        input {
            leaf name {
                type string;
            }
        }
        output {
            leaf greeting {
                type string;
            }
        }
    }
}

Return to the hello/api directory and build your api:

mvn clean install

Intermission: Look at mapping of yang to Java for RPCs

We will do this together as a group.

Exercise 3: Implement your HelloWorld RPC

Create a new file:

impl/src/main/java/org/opendaylight/hello/impl/HelloWorldImpl.java
/*
 * Copyright (c) Yoyodyne, Inc. and others.  All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */
package org.opendaylight.hello.impl;

import java.util.concurrent.Future;

import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.HelloService;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.HelloWorldInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.HelloWorldOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.HelloWorldOutputBuilder;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorldImpl implements HelloService {
    private static final Logger LOG = LoggerFactory.getLogger(HelloWorldImpl.class);

    @Override
    public Future<RpcResult<HelloWorldOutput>> helloWorld(HelloWorldInput input) {
        HelloWorldOutput output = new HelloWorldOutputBuilder()
            .setGreeting("Hello " + input.getName())
            .build();
        return RpcResultBuilder.success(output).buildFuture();
    }
}

And edit the existing HelloProvider.java to match:

/*
 * Copyright (c) Yoyodyne, Inc. and others.  All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */
package org.opendaylight.hello.impl;

import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ProviderContext;
import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.RpcRegistration;
import org.opendaylight.controller.sal.binding.api.BindingAwareProvider;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.HelloService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloProvider implements BindingAwareProvider, AutoCloseable {

    private static final Logger LOG = LoggerFactory.getLogger(HelloProvider.class);
    private RpcRegistration<HelloService> helloService;

    @Override
    public void onSessionInitiated(ProviderContext session) {
        LOG.info("HelloProvider Session Initiated");
        helloService = session.addRpcImplementation(HelloService.class, new HelloWorldImpl());
    }

    @Override
    public void close() throws Exception {
        LOG.info("HelloProvider Closed");
        if (helloService != null) {
            helloService.close();
        }
    }

}

NOTE: If you're working with Boron or later version, refer to https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL:Startup_Project_Archetype for further handling with impl-blueprint.xml.

Then rebuild from the hello/ directory and rerun karaf.

Intermission: Introduction to Yangui and Postman

We will go through these together as a group.

To access yangui: http://localhost:8080/index.html#/yangui/index

Exercise 4: Manually test your HelloWorld RPC

Go back and use your skills with Yangui and Postman to try out your HelloWorld app manually.

Intermission: Debugging your HelloWorld RPC in Karaf

To run karaf in 'debug' mode run it with

./karaf debug

We will go through this togther as a group using the Eclipse debugger to attach to it on port 5005.

Exercise 5: Integration test your HelloWorld RPC

Edit HelloIT.java and add methods:

    @Override
    public Option getLoggingOption() {
        Option option = editConfigurationFilePut(ORG_OPS4J_PAX_LOGGING_CFG,
                logConfiguration(HelloIT.class),
                LogLevel.INFO.name());
        option = composite(option, super.getLoggingOption());
        return option;
    }

    @Test
    public void testRPC() throws InterruptedException, ExecutionException {
        String name = "Ed Warnicke";
        HelloService service = getSession().getRpcService(HelloService.class);

        HelloWorldInput input = new HelloWorldInputBuilder()
                .setName(name)
                .build();
        Future<RpcResult<HelloWorldOutput>> outputFuture = service.helloWorld(input);
        RpcResult<HelloWorldOutput> outputResult = outputFuture.get();
        Assert.assertTrue("RPC was unsuccessful", outputResult.isSuccessful());
        Assert.assertEquals("Did not receive the expected response to helloWorld RPC", "Hello " + name,
                outputResult.getResult().getGreeting());
    }

(Hint: these are the imports you are looking for:

import static org.ops4j.pax.exam.CoreOptions.composite;
import static org.ops4j.pax.exam.CoreOptions.maven;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.editConfigurationFilePut;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.opendaylight.controller.mdsal.it.base.AbstractMdsalTestBase;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.HelloService;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.HelloWorldInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.HelloWorldInputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.HelloWorldOutput;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.exam.karaf.options.LogLevelOption.LogLevel;
import org.ops4j.pax.exam.options.MavenUrlReference;
import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
import org.ops4j.pax.exam.spi.reactors.PerClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Rebuild hello/it

Now edit "Hello" in the last assert to be "Hellow" and rebuild hello/it and see it fail.

Exercise 6: Debugging your integration test

To debug your integration test:

cd it/
mvn clean install -Dkaraf.debug

Which will launch the IT test karaf container listening on port 5005.

Place a debug point in the testRPC test and walk through your code.

= Conceptual Intro: Datatree

Slides gone through as a group.

Conceptual Intro: Datastore

There will be slides.

Exercise 7: Model a simple HelloWorld GreetingRegistry

Edit

api/src/main/yang/hello.yang

and add:

    container greeting-registry {
        list greeting-registry-entry {
            key "name";
            leaf name {
                type string;
            }
            leaf greeting {
                type string;
            }
        }
    }

Rebuild api/

Intermission: Look at mapping of yang to Java for Datatree

We will walk through this together.

Exercise 8: Implement RPC recording who it greeted to the GreetingRegistry

We want an invocation of the HelloWorld RPC to cause an entry to be made to the GreetingRegistry for the greeting made to a particular person. This will properly be stored in the operational store.

Make DataBroker available to HelloWorldImpl.java

To accomplish this, first the HelloWorldImpl.java has to have access to the Databroker. To achieve that, add to HelloWorldImpl.java:

    private DataBroker db;

    public HelloWorldImpl(DataBroker db) {
        this.db = db;
    }

and alter HelloProvider.java to pass pass the DataBroker to the HelloWorldImpl(...) contstructor:

        DataBroker db = session.getSALService(DataBroker.class);
        helloService = session.addRpcImplementation(HelloService.class, new HelloWorldImpl(db));

Initialize the GreetingRegistry

As part of initialization of HelloWorldImpl.java, we need to make sure that we have a GreetingRegistry (abiet with no GreetingRegistryEntries) created. To do this, add to HelloWorldImpl.java:

    private void initializeDataTree(DataBroker db) {
        LOG.info("Preparing to initialize the greeting registry");
        WriteTransaction transaction = db.newWriteOnlyTransaction();
        InstanceIdentifier<GreetingRegistry> iid = InstanceIdentifier.create(GreetingRegistry.class);
        GreetingRegistry greetingRegistry = new GreetingRegistryBuilder()
                .build();
        transaction.put(LogicalDatastoreType.OPERATIONAL, iid, greetingRegistry);
        CheckedFuture<Void, TransactionCommitFailedException> future = transaction.submit();
        Futures.addCallback(future, new LoggingFuturesCallBack<>("Failed to create greeting registry", LOG));
    }

and call that method from the constructor.

Write to GreetingRegistry whenever the HelloWorld RPC is called

To accomplish this add to HelloWorldImpl.java the following methods:

    private void writeToGreetingRegistry(HelloWorldInput input, HelloWorldOutput output) {
        WriteTransaction transaction = db.newWriteOnlyTransaction();
        InstanceIdentifier<GreetingRegistryEntry> iid = toInstanceIdentifier(input);
        GreetingRegistryEntry greeting = new GreetingRegistryEntryBuilder()
                .setGreeting(output.getGreeting())
                .setName(input.getName())
                .build();
        transaction.put(LogicalDatastoreType.OPERATIONAL, iid, greeting);
        CheckedFuture<Void, TransactionCommitFailedException> future = transaction.submit();
        Futures.addCallback(future, new LoggingFuturesCallBack<Void>("Failed to write greeting to greeting registry", LOG));
    }

    private InstanceIdentifier<GreetingRegistryEntry> toInstanceIdentifier(HelloWorldInput input) {
        InstanceIdentifier<GreetingRegistryEntry> iid = InstanceIdentifier.create(GreetingRegistry.class)
            .child(GreetingRegistryEntry.class, new GreetingRegistryEntryKey(input.getName()));
        return iid;
    }

and change the HelloWorldImpl.helloWorld(...) method to call writeToGreetingRegistry(...) before it returns.

Note: You will also need this LoggingFuturesCallBack.java to log results of your Datatree read (in case of error):

package org.opendaylight.hello.impl;

import org.slf4j.Logger;

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

public class LoggingFuturesCallBack<V> implements FutureCallback<V> {

    private Logger LOG;
    private String message;

    public LoggingFuturesCallBack(String message,Logger LOG) {
        this.message = message;
        this.LOG = LOG;
    }

    @Override
    public void onFailure(Throwable e) {
        LOG.warn(message,e);

    }

    @Override
    public void onSuccess(V arg0) {
        LOG.info("Success! {} ", arg0);

    }

}

As a hint... the imports for HelloWorldImpl.java are:

import java.util.concurrent.Future;

import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.HelloService;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.HelloWorldInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.HelloWorldOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.HelloWorldOutputBuilder;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

Exercise 9: Manually testing GreetingRegistry

Use yangui to test GreetingRegistry.

Exercise 10: Integration testing GreetingRegistry

Add the following method to HelloIT.java:

    private void validateGreetingRegistry(String name) {
        InstanceIdentifier<GreetingRegistryEntry> iid = InstanceIdentifier.create(GreetingRegistry.class)
                .child(GreetingRegistryEntry.class, new GreetingRegistryEntryKey(name));
        DataBroker db = getSession().getSALService(DataBroker.class);
        ReadOnlyTransaction transaction = db.newReadOnlyTransaction();
        CheckedFuture<Optional<GreetingRegistryEntry>, ReadFailedException> future =
                transaction.read(LogicalDatastoreType.OPERATIONAL, iid);
        Optional<GreetingRegistryEntry> optional = Optional.absent();
        try {
            optional = future.checkedGet();
        } catch (ReadFailedException e) {
            LOG.warn("Reading greeting failed:",e);
        }
        Assert.assertTrue(name + " not recorded in greeting registry",optional.isPresent());
    }

and add an invocation of it to the end of HelloIT.testRPC().

Run your IT tests again and see them succeed.

Change your HelloWorldImpl.java or HelloIT.java slightly to cause your IT tests to fail.

Exercise 11: Extending GreetingRegistry for custom greetings (synchronous)

Next we'd like to use the CONFIGURATION side of the GreetingRegistry to allow programmability of the Greeting a particular user gets in response to the RPC.

Initialize CONFIGURATION GreetingRegistry

First we need to initialize the CONFIGURATION GreetingRegistry... you can do this by adding to HelloWorldImpl.initializeDataTree the line:

        transaction.put(LogicalDatastoreType.CONFIGURATION, iid, greetingRegistry);

Have HelloWorldImpl consult the CONFIGURATION GreetingRegistry when responding

Add the following method to HelloWorldImpl.java:

    private String readFromGreetingRegistry(HelloWorldInput input) {
        String result = "Hello " + input.getName();
        ReadOnlyTransaction transaction = db.newReadOnlyTransaction();
        InstanceIdentifier<GreetingRegistryEntry> iid = toInstanceIdentifier(input);
        CheckedFuture<Optional<GreetingRegistryEntry>, ReadFailedException> future =
                transaction.read(LogicalDatastoreType.CONFIGURATION, iid);
        Optional<GreetingRegistryEntry> optional = Optional.absent();
        try {
            optional = future.checkedGet();
        } catch (ReadFailedException e) {
            LOG.warn("Reading greeting failed:",e);
        }
        if(optional.isPresent()) {
            result = optional.get().getGreeting();
        }
        return result;
    }

and invoke that method in the HelloWorldImpl.helloWorld(...) rather than hardcoding the response.

Exercise 12: Manually testing custom greetings

Use yangui to setup and test some custom greetings.

Exercise 13: Integration testing custom greetings

In order to test programmable responses to the HelloWorld RPC, we are going to want to have a new test:

testProgrammableRPC()

but it is almost identical to testRPC... except that it stops to program an alternate response and it checks for the response it programmed.

Extract out testRPC logic

Because we don't want to repeat the logic in testRPC, we are going to extract it. Extract a method out of HelloIT.testRPC:

private void validateRPCResponse(String name,String response) throws InterruptedException, ExecutionException {
        HelloService service = getSession().getRpcService(HelloService.class);

        HelloWorldInput input = new HelloWorldInputBuilder()
                .setName(name)
                .build();
        Future<RpcResult<HelloWorldOutput>> outputFuture = service.helloWorld(input);
        RpcResult<HelloWorldOutput> outputResult = outputFuture.get();
        Assert.assertTrue("RPC was unsuccessful", outputResult.isSuccessful());
        Assert.assertEquals("Did not receive the expected response to helloWorld RPC", response,
                outputResult.getResult().getGreeting());
    }

and call that from testRPC. Run your IT tests again at this point to make sure they still work.

Write a method to program a response to a greeting

In HelloIT write a new method:

    private void programResponse(String name, String response) throws TransactionCommitFailedException {
        DataBroker db = getSession().getSALService(DataBroker.class);
        WriteTransaction transaction = db.newWriteOnlyTransaction();
        InstanceIdentifier<GreetingRegistryEntry> iid = InstanceIdentifier.create(GreetingRegistry.class)
                .child(GreetingRegistryEntry.class, new GreetingRegistryEntryKey(name));
        GreetingRegistryEntry entry = new GreetingRegistryEntryBuilder()
                .setName(name)
                .setGreeting(response)
                .build();
        transaction.put(LogicalDatastoreType.CONFIGURATION, iid, entry);
        CheckedFuture<Void, TransactionCommitFailedException> future = transaction.submit();
        future.checkedGet();
    }

Write a testProgrammableRPC method

    @Test
    public void testProgrammableRPC() throws InterruptedException, ExecutionException, TransactionCommitFailedException {
        String name = "Colin Dixon";
        String response = "Hola " + name;
        programResponse(name,response);
        validateRPCResponse(name,response);
        validateGreetingRegistry(name);
    }

Rerun your tests.

Conceptual Intro: DataChangeListeners

Slides will be presented.

Exercise 14: Rework Greeting Registry Integration Tests with DataChangeListeners

At this point, if you are unlucky, you will have discovered that we have a race condition in our IT tests.

Because the way we implemented the HelloWorld RPC was intentionally asynchronous with regards to writing to the datastore, it can happen that when your HelloIT goes to look for a registry entry for a given RPC call, it gets there *before* the write has concluded.

We can deal with this one of two ways. Either we can decide that we really *do* want the HelloWorld RPC to complete its write before returning, or we can fix our HelloIT tests to deal with this correctly via DataChangeListeners. We are going to do the latter.

Writing GreetingRegistryDataChangeListenerFuture

We are going to write a simple Future that implements the DataChangeListener contract: It will live in the path "impl/src/main/java/org/opendaylight/hello/GreetingRegistryDataChangeListenerFuture.java"

package org.opendaylight.hello;

import org.opendaylight.controller.md.sal.binding.api.DataBroker;
import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.GreetingRegistry;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.greeting.registry.GreetingRegistryEntry;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.greeting.registry.GreetingRegistryEntryKey;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;

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

public class GreetingRegistryDataChangeListenerFuture extends AbstractFuture<GreetingRegistryEntry> implements DataChangeListener, AutoCloseable {

    private String name;
    private ListenerRegistration<DataChangeListener> registration;

    public GreetingRegistryDataChangeListenerFuture(DataBroker db,String name) {
        super();
        this.name = name;
        InstanceIdentifier<GreetingRegistryEntry> iid =
                InstanceIdentifier.create(GreetingRegistry.class)
                    .child(GreetingRegistryEntry.class);
        this.registration = db.registerDataChangeListener(LogicalDatastoreType.OPERATIONAL,
                iid, this, DataChangeScope.BASE);
    }

    @Override
    public void onDataChanged(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> event) {
        InstanceIdentifier<GreetingRegistryEntry> iid =
                InstanceIdentifier.create(GreetingRegistry.class)
                .child(GreetingRegistryEntry.class,new GreetingRegistryEntryKey(this.name));
        if(event.getCreatedData().containsKey(iid) ) {
            if(event.getCreatedData().get(iid) instanceof GreetingRegistryEntry) {
                this.set((GreetingRegistryEntry) event.getCreatedData().get(iid));
            }
            quietClose();
        } else if (event.getUpdatedData().containsKey(iid)) {
            if(event.getUpdatedData().get(iid) instanceof GreetingRegistryEntry) {
                this.set((GreetingRegistryEntry) event.getUpdatedData().get(iid));
            }
            quietClose();
        }
    }

    private void quietClose() {
        try {
            this.close();
        } catch (Exception e) {
            throw new IllegalStateException("Unable to close registration",e);
        }
    }

    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
        quietClose();
        return super.cancel(mayInterruptIfRunning);
    }

    @Override
    public void close() throws Exception {
        if(registration != null) {
            registration.close();
        }
    }

}

Rewriting the validateGreetingRegistry method

    private void validateGreetingRegistry(String name,GreetingRegistryDataChangeListenerFuture future) throws InterruptedException, TimeoutException, ExecutionException {
        future.get(100, TimeUnit.MILLISECONDS);
        Assert.assertTrue(name + " not recorded in greeting registry", future.isDone());
    }

Using the Future

In both tests, use:

        DataBroker db = getSession().getSALService(DataBroker.class);
        GreetingRegistryDataChangeListenerFuture future =
                new GreetingRegistryDataChangeListenerFuture(db, "ed");

before

validateRPCResponse(...)

and use your new validateGreetingRegistry() method.

Run your tests. Do something to break them and see them fail.

Exercise 15: Using Transaction Chains for GreetingRegistry

In order to protect the integrity of the Datastore, if the portion of the data tree to which a transaction applies changes between when it is created and when it is submitted, the transaction will fail.

One way around this is to use TransactionChains.

When you use a TransactionChain, your transactions are applied in the order they are submitted *to the chain*.

Creating a TransactionChain for HelloWorldImpl

Make HelloWorldImpl a TransactionChainListener by having it implement TransactionChainListener.

In HelloWorldImpl's constructor:

this.tc = db.createTransactionChain(this);

(you will have to create the field).

Then where ever you are creating transactions using db, replace it with tc, and everything should work as before.

Exercise 16: Reworking HelloWorldImpl customized greetings to be Async

The way we originally wrote customized greetings, we waited synchronously for the response to our read. Now, we rewrite it to respond asynchronously.

To do so, create a new HelloWorldFuture:

package org.opendaylight.hello.impl;

import org.opendaylight.controller.md.sal.binding.api.BindingTransactionChain;
import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.GreetingRegistry;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.HelloWorldInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.HelloWorldOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.HelloWorldOutputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.greeting.registry.GreetingRegistryEntry;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.greeting.registry.GreetingRegistryEntryBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hello.rev150105.greeting.registry.GreetingRegistryEntryKey;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Optional;
import com.google.common.util.concurrent.AbstractFuture;
import com.google.common.util.concurrent.CheckedFuture;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;

public class HelloWorldFuture extends AbstractFuture<RpcResult<HelloWorldOutput>> implements FutureCallback<Optional<GreetingRegistryEntry>> {
    private static final Logger LOG = LoggerFactory.getLogger(HelloWorldFuture.class);
    private HelloWorldInput input;
    private BindingTransactionChain tc;

    public HelloWorldFuture(BindingTransactionChain tc,HelloWorldInput input) {
        this.tc = tc;
        this.input = input;
        readFromGreetingRegistry(input);
    }

    private void writeToGreetingRegistry(HelloWorldInput input, HelloWorldOutput output) {
        WriteTransaction transaction = tc.newWriteOnlyTransaction();
        InstanceIdentifier<GreetingRegistryEntry> iid = toInstanceIdentifier(input);
        GreetingRegistryEntry greeting = new GreetingRegistryEntryBuilder()
                .setGreeting(output.getGreeting())
                .setName(input.getName())
                .build();
        transaction.put(LogicalDatastoreType.OPERATIONAL, iid, greeting);
        CheckedFuture<Void, TransactionCommitFailedException> future = transaction.submit();
        Futures.addCallback(future, new LoggingFuturesCallBack<Void>("Failed to write greeting to greeting registry", LOG));
    }

    private InstanceIdentifier<GreetingRegistryEntry> toInstanceIdentifier(HelloWorldInput input) {
        InstanceIdentifier<GreetingRegistryEntry> iid = InstanceIdentifier.create(GreetingRegistry.class)
            .child(GreetingRegistryEntry.class, new GreetingRegistryEntryKey(input.getName()));
        return iid;
    }

    private void readFromGreetingRegistry(HelloWorldInput input) {
        ReadOnlyTransaction transaction = tc.newReadOnlyTransaction();
        InstanceIdentifier<GreetingRegistryEntry> iid = toInstanceIdentifier(input);
        CheckedFuture<Optional<GreetingRegistryEntry>, ReadFailedException> future =
                transaction.read(LogicalDatastoreType.CONFIGURATION, iid);
        Futures.addCallback(future, this);
    }

    @Override
    public void onSuccess(Optional<GreetingRegistryEntry> result) {
        String greeting = "Hello " + this.input.getName();
        if(result.isPresent()) {
            greeting = result.get().getGreeting();
        }
        HelloWorldOutput output = new HelloWorldOutputBuilder()
                .setGreeting(greeting)
                .build();
        writeToGreetingRegistry(input, output);
        this.set(RpcResultBuilder.success(output).build());
    }

    @Override
    public void onFailure(Throwable t) {
        this.set(RpcResultBuilder.<HelloWorldOutput>failed().withError(ErrorType.RPC, "Failed", t).build());

    }
}

and change HelloWorldImpl.helloWorld(...) to just return:

 return new HelloWorldFuture(tc, input);

Bonus: Walkthrough of how the magic works

We will walk through some of how the magic in the projects generated by the archetype work to enable better understanding of the complexity that is being masked there.