Project

General

Profile

Wiki

The documentation is at its early stages but you can contact for help.

Nappli

NAPPLI is a very lightweight application server, very easy to install and usable everywhere. NAPPLI stands for N applications, where N can be both interpreted as the first character of NOMAD as well as an unknown number. A NAPPLI server provides services for starting, stopping, synchronising and making distributed applications communicate. We can say that it is an application-oriented middleware. The lifecycle of remote applications can be entirely managed within the application. The server is accompanied with a client API in Java and C++ with a modern asynchronous programming model using the future concept. The available communication patterns between the applications are request/response, publisher/subscriber (synchronised or not) and return value at the end of the execution of the application. It is possible to use the application server in a non-intrusive way. Existing applications can be called directly without using the provided API. In this case, the application itself is directly responsible of communication with the outside world. The NAPPLI services are intended to be logic and network fault tolerant. An Application can terminate with an exception but the remote caller will be notified with an error, so that it will be able to take the decision to restart or not the application. The network layer also implements features to survive to failures.

Implementation

NAPPLI was designed taking into account the disadvantages of CORBA. Unlike CORBA which shares data references through a naming service, NAPPLI shares application instances. This is a real different approach. Thus the applications have the responsibility to organise the sharing of their data through persistent services where NAPPLI provides patterns for communication. Internally, a NAPPLI server is written in pure Java 8 so that it only requires a compatible virtual machine for running. That makes it very portable and easy to install. Moreover, NAPPLI application instances are processes started by the server. We take advantage of the continuous Java improvement in its process API to have a unified way to start and monitor processes on different platforms (Linux, Mac OS X, Windows). To organise the network services, we use the robust and reliable ZeroMQ message queue for which a 100% Java implementation called JeroMQ exists. ZeroMQ is not only an open-source library, it also provides a precise documentation on how to use it in different network contexts. For example we followed the recommendations to implement a real synchronised publisher/subscriber pattern. Internally, we use the Protocol Buffers library to serialise and parse messages exchanged by the application instances. Protocol Buffers offers a portable and fast data encoding and decoding. The main feature of a NAPPLI server is to start and stop applications on its own system. For that, a NAPPLI server is configured with a list of runnable applications. Each runnable application has a list of attributes so that an application instance can be seen as an enriched system process. We won’t provide all the available attributes here but we can cite:
  • Name: String identifier used by clients to start an application instance
  • Multiple: yes or no, no meaning that only a single instance of the application can run
  • Restart: An application instance is restarted in case of error termination
  • Stream: The application publishes its output and error streams to the clients
  • Executable: The path to the executable
  • Args: The list of arguments that are always passed to the executable

Note that it is really important to make the difference between an application configuration and its instances. Applications have a workflow state shown in the next figure.

Around the Running state, there are transitional states (Starting, Stopping, Killing, Processing Error) and terminal states (Success, Stopped, Killed, Error). Once the process of the application is launched, the application has the Starting state. It becomes Running when:
  • it remains alive for a certain time, defined as an attribute of the application or,
  • the application itself changes its state to Running.
When a client requests the stop of the application, it immediately becomes Stopping until it terminates. At that moment, its state becomes Stopped. Notice that after the crash of an application (segmentation fault in C++ or exception in Java), its state becomes Error. The changes of state are all sent to the clients of the application. The messages that are passed between applications can be of any type. NAPPLI provides binary messages as well as string messages so that the programmer can choose the encoding that can be Protocol Buffers or JSON. Arrays of integer and floating point number are also provided by convenience to speed up the coding. Theses messages can be used in the different communication contexts:
  • publisher/subscriber
  • request/response
  • return value

The return value of an application is implemented with a publisher/subscriber so that any connected client application receives the result. We provide a client API for C++ Java and a Python API is planned.

Example of code

We present a sample of code where two NAPPLI applications interact with a publisher/subscriber pattern. We define:
  • a Java application Publisher that publishes to a single subscriber.
  • a C++ application Subscriber which starts the Publisher application on the local nappli server and subscribes to it.

Code for the C++ Publisher application:

int main(int argc, char *argv[]) {

    // Initialise the nappli application represented by This. 
    application::This::init(argc, argv);

    // New block to ensure nappli objects are terminated before the application.
    {
        // Declare a publisher.
        std::auto_ptr<application::Publisher> publisher;

        try {
            // Define the publisher with name "pub" that accepts a single subscriber.
            publisher = application::This::publish("pub", 1);

            // Synchronize with the subscriber.
            publisher->waitForSubscribers();

            // Once here, we are sure that the subscriber is ready and will be able to receive all the messages.

        } catch (const PublisherCreationException& e) {
            // The Publisher cannot be created. Clean up.
            application::This::terminate();
            return -1;
        }

        // Force the state RUNNING.
        application::This::setRunning();

        // We can send data to the unique subscriber.
        publisher->send("hello");
        publisher->send("world");
        publisher->send("!");

        // Set the end of the stream.
        publisher->setEndOfStream();
    }

    // The application terminates.
    application::This::terminate();

    return 0;
}

Code for the Java Subscriber application:

public class SubscriberApplication {

    public static void main(String[] args) {

        // Initialise the nappli application represented by This.    
        Application.This.init(args);

        // Get the local nappli server (the one that started This).
        Server server = Application.This.getServer();

        // Declare the subscriber.        
        Application.Subscriber subscriber = null;

        try {
            // Start the publisher application with name "pubcpp" and get a reference to the running application with the Instance object.
            Application.Instance publisherApplication = server.start("pubcpp");

            // Check that the application really started.
            if (!publisherApplication.exists()) {
                System.exit(-1);
            }

            // Subscriber the publisher with name pub.
            subscriber = publisherApplication.subscribe("pub");

            // We are ready to receive data.
            while (true) {
                // Receive string encoded messages.
                String data = subscriber.receiveString();
                if (data != null) {
                    System.out.println("received " + data);
                } else {
                    break;
                }
            }

            // Finished to receive the data.
            // We can wait for the termination of the pubcpp application.
            application.State state = publisherApplication.waitFor();

            // At this point, the pubcpp application is terminated and its terminal state is state.

        } finally {
            // Do not forget to terminate the services and subscriber in Java.
            subscriber.terminate();
            Application.This.terminate();
        }
    }

}

Define the code is not enough to run NAPPLI applications. We need to configure a NAPPLI server with the following configuration file:

<?xml version="1.0" encoding="UTF-8"?>

<config port="7000" max_applications="20" log_directory="logs" debug="ON" polling_time="100">

    <applications>

        <!-- The C++ application with name pubcpp -->
        <application name="pubcpp" stream="yes" log_directory="logs">
            <start executable="pubcppapp"/>
        </application>

        <!-- The Java application with name subpubjava -->
        <application name="subpubjava" stream="yes">
            <start executable="java" args="-classpath bin.jar:jeromq-0.3.5.jar:protobuf-java-2.5.0.jar fr.ill.ics.nappli.test.SubscriberApplication"/>
        </application>

    </applications>

</config>

A NAPPLI server configuration file contains the list of application that can be started with their name, executable and other attributes. For example we can specify that the application will stream its standard and error outputs.
Now we have to start the subpubjava application. That can be realized manually by using the nappli console:

> nappli tcp://machine:7000 subpubjava start

The server running with the endpoint tcp://machine:7000 starts the subpubjava.