Update 13/8/2019: lighty TransportPCE Controller is now available in the OpenDaylight upstream!

The migration of an OpenDaylight application to lighty.io is simple and straightforward. In this post, we are going to show you how to do this with help of the TransportPCE OpenDaylight project acting as an example.

Users can find many examples shipped along with the lighty.io package to help them jumpstart projects, but this post will detail the migration process in particular.

The major part of the migration is removing hard dependencies in OSGi / Karaf.

This exercise is good for the OpenDaylight project itself, as these dependencies should not exist in the first place. We recommend sending the resulting patch to the upstream track as a step towards cleaner code.

Since there are no OSGI dependencies in the TransportPCE project, we will focus on the rest of the work that needs to be done. To begin, you need to find out what other OpenDaylight modules (e.g. controller, NETCONF, RESTCONF) the given project uses (depends on).

TransportPCE’s dependencies are already migrated to lighty.io and therefore it is as easy as initialization of an object and calling a start function.

Initializing object and calling a start function:

        //1. initialize and start OpenDaylight controller (MD-SAL, Controller, YangTools, Akka)
        final LightyControllerBuilder lightyControllerBuilder = new LightyControllerBuilder();
        final LightyController lightyController = lightyControllerBuilder.from(controllerConfiguration).build();

The second step is to build the lighty.io TransportPCE object. To do this you have to go to the original project and find all the blueprint files.

These will specify the dependency references that are needed by your newly created object constructor and beans, which are the objects that you will create using those references.

These beans also have names of functions that you need for initializing and destroying of the object blueprint example:

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
  <reference id="dataBroker"
    odl:type="default" />
  <reference id="mountPointService"
    interface="org.opendaylight.controller.md.sal.binding.api.MountPointService" />
  <reference id="rpcProviderRegistry"
    interface="org.opendaylight.controller.sal.binding.api.RpcProviderRegistry" />
  <bean id="provider"
    init-method="init" destroy-method="close">
    <argument ref="dataBroker" />
    <argument ref="mountPointService" />
    <argument ref="rpcProviderRegistry" />

The next step is to initialize all the objects that TransportPCE needs to function. lighty.io has an abstract helper class called AbstractLightyModule which you should extend.

With this, you will be overriding an initProcedure function and whenever you call start on your lighty.io object, this function will be triggered. In this function, all you need to do is to create all the beans from blueprints and call their initializing function.

Start method from abstract class:

public synchronized ListenableFuture<Boolean> start() {
        if(this.running) {
            LOG.warn("LightyModule {} is already started.", this.getClass().getSimpleName());
            return Futures.immediateFuture(Boolean.valueOf(true));
        } else {
            if(this.executorService == null) {
                LOG.debug("Creating default single thread ExecutorService for LightyModule {}.", this.getClass().getSimpleName());
                this.executorService = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
            LOG.info("Submitted start of LightyModule {}.", this.getClass().getSimpleName());
            return this.executorService.submit(() -> {
                synchronized(this) {
                    LOG.debug("Starting initialization of LightyModule {}", this.getClass().getSimpleName());
                    boolean initResult = this.initProcedure();
                    this.running = true;
                    LOG.info("LightyModule {} started.", this.getClass().getSimpleName());
                    return Boolean.valueOf(initResult);

Initializing and calling start on lightyTransportPce object:

        //start TransportPce application
        final LightyTransportPce lightyTransportPce;
        final TransportPceBuilder lightyTransportPceBuilder = new TransportPceBuilder();
        lightyTransportPce = lightyTransportPceBuilder.from(lightyController.getServices()).build();
overriding a initProcedure method:
    protected boolean initProcedure() {
        final long startTime = System.nanoTime();
        LOG.info("Starting TransportPceProvider");
        transportpceProvider =
                new TransportpceProvider(dataBroker, mountPointService);
        LOG.info("Starting RendererProvider");
        rendererProvider = new RendererProvider(dataBroker, mountPointService,
        LOG.info("Starting OlmProvider");
        olmProvider = new OlmProvider(dataBroker, mountPointService, rpcProviderRegistry);
        LOG.info("Starting StubrendererProvider");
        stubrendererProvider = new StubrendererProvider(rpcProviderRegistry,
        LOG.info("Starting StubpceProvider");
        stubpceProvider = new StubpceProvider(rpcProviderRegistry, notificationService,
        LOG.info("Starting ServiceHandlerProvider");
        servicehandlerProvider = new ServicehandlerProvider(dataBroker, rpcProviderRegistry,
                notificationService, notificationPublishService);
        final float delay = (System.nanoTime() - startTime) / 1_000_000f;
        LOG.info("Lighty transportpce started in {}ms", delay);
        return true;

The last step is to override the stopProcedure function. This function needs to call the destroy method of an object that you created. Whenever shutdown is called on a lighty.io object, this method will be triggered.

Override a stopProcedure method:

    protected boolean stopProcedure() {
        if (transportpceProvider != null) {
            LOG.info("TransportpceProvider closed");
        if (olmProvider != null) {
            LOG.info("OlmProvider closed");
        if (rendererProvider != null) {
            LOG.info("RendererProvider stopped");
        if (rendererProvider != null) {
            LOG.info("RendererProvider stopped");
        if (servicehandlerProvider != null) {
            LOG.info("ServicehandlerProvider stopped");
        if (stubpceProvider != null) {
            LOG.info("StubpceProvider stopped");
        if (stubrendererProvider != null) {
            LOG.info("StubrendererProvider stopped");
        return true;
Shutdown method from abstract class
    public synchronized ListenableFuture<Boolean> shutdown() {
        if(!this.running) {
            LOG.warn("LightyModule {} is already shut down.", this.getClass().getSimpleName());
            return Futures.immediateFuture(Boolean.valueOf(true));
        } else {
            LOG.info("Submitted shutdown of LightyModule {}.", this.getClass().getSimpleName());
            ListenableFuture<Boolean> shutdownFuture = this.executorService.submit(() -> {
                synchronized(this) {
                    LOG.debug("Starting shutdown procedure of LightyModule {}.", this.getClass().getSimpleName());
                    boolean stopResult = this.stopProcedure();
                    this.running = false;
                    LOG.info("LightyModule {} shutdown complete.", this.getClass().getSimpleName());
                    return Boolean.valueOf(stopResult);
            return !this.executorIsProvided?Futures.transform(shutdownFuture, (result) -> {
                LOG.debug("Shutdown default ExecutorService of LightyModule {}.", this.getClass().getSimpleName());
                this.executorService = null;
                return Boolean.valueOf(true);
            }, MoreExecutors.directExecutor()):shutdownFuture;

Keep in mind, that your controller has to have access to all YANG model artifacts that you are using in your project. If you fail to do so, the schema context of the controller will be missing them and therefore some of your bean objects will not work properly.

As you can see, these steps are simple and straightforward. If all the OpenDaylight projects were this clean, a simple script would be able to take care of migration from OpenDaylight to lighty.io.