In this tutorial you will learn how to adapt a simple distributed application to use LSim and also a few basic characteristics that LSim offers. A basic knowledge of the LSim is assumed. A description of LSim can be found in the index page.

In section 1 the initial application is described and sections 2 to 5 describe the process to adapt the initial application so it can be executed in a distributed environment using the LSim.

Finally sections 6 and 7 explain how to run the application in a local environment simulating a distributed environment. This may be useful to test applications before deploy them in a distributed environment.

You can download the code used on the tutorial from here: lsim-hello-world_src.zip. All the libraries needed are here: lsim-libraries.zip.

1. Initial application

Our application is composed by two types of components:

  • client: sends a number. There may be several instances of this type.
  • server: receives the numbers from clients and sums them up. There’s only one instance of this type. It waits to receive as many numbers as clients.

Client code

package application;

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

public class Application {

  public static void main(String argv[]) {
    System.out.println("Application running");
    String str = argv[0];
    System.out.println("number to send: " + str);
    Socket clientSocket;
    try {
      clientSocket = new Socket("localhost", 6789);
      DataOutputStream outToServer = new DataOutputStream(
                clientSocket.getOutputStream());
        outToServer.writeChars(Integer.parseInt(str)+"\n");
        clientSocket.close();
      } catch (UnknownHostException e) {
        e.printStackTrace();
      } catch (IOException e) {
        e.printStackTrace();
      }
  }

}

Server code

package server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    
    public static void main(String argv[]) {
        int total = 0;
        int n = Integer.parseInt(argv[0]);
        ServerSocket welcomeSocket;
        try {
            welcomeSocket = new ServerSocket(6789);
            for (int i = 0; i < n; i++) {
                Socket connectionSocket = welcomeSocket.accept();
                BufferedReader inFromClient = new BufferedReader(
                        new InputStreamReader(connectionSocket.getInputStream()));
                total += inFromClient.read();
            }
            System.out.println("Total: " + total);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

2. Adapting the application to run with LSim (Workers)

LSim library is packed in the file lsim-library-{version}.jar. This library must be added to the project when developing or in the classpath on compiling. The packet lsim-commons-{version}.jar is also needed, it includes several classes used to create the running environment. You can download this libraries here: lsim-libraries.zip.

Client

First steps

1. The class Application needs to implement ApplicationManager class that is located on lsim.application.ApplicationManager.

2. Copy the code on main() method to method start(LSimDispatcherHandler dispatcher). This way the application can be executed with LSim and using main method also. In case you only want to execute your application with LSim, you can remove the method.

When this two steps are done, application code should be like this:

package application;

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.List;
import lsim.LSimDispatcherHandler;
import lsim.application.ApplicationManager;
import lsim.application.handler.DummyHandler;
import lsim.application.handler.InitHandler;
import lsim.worker.LSimWorker;

public class Application implements ApplicationManager {

    // main method omitted
    // ...
    
    @Override
    public boolean isAlive() {
        return true;
    }

    @Override
    public void start() {
    }

    @Override
    public void start(LSimDispatcherHandler dispatcher) { 
        System.out.println("Application running");
        String str = argv[0];
        System.out.println("number to send: " + str);
        Socket clientSocket;
        try {   
            clientSocket = new Socket("localhost", 6789);
            DataOutputStream outToServer = new DataOutputStream( clientSocket.getOutputStream());
            outToServer.writeChars(Integer.parseInt(str)+"\n");
            clientSocket.close();
        } catch (UnknownHostException e) { 
            e.printStackTrace();
        } catch (IOException e) { 
            e.printStackTrace();
        }
    }

    @Override
    public void stop() { 
    }

}

Notice that return value of isAlive() method has been set to true.

3. The following variables should be declared in method start to make the application behave as Worker.

LSimWorker lsim = LSimFactory.getWorkerInstance();
lsim.setDispatcher(dispatcher);

Init

4. Application should wait for init information and parameters from Coordinator. Each application instance will receive the parameters sent by the Coordinator through a handler.

InitHandler init = new InitHandler(lsim, 5);
lsim.init(init);

In this code an InitHandler object included on LSim library is used. It has two parameters: (a) Lsim instance and (b) the maximum time in minutes that the experiment should last before LSim force its ending abruptly stopping it. Time should be optimistically estimated. It should include the required time to initialize and start all instances.

5. The method getParameters() allows to obtain the received parameters needed to initialize the application instance. The parameters are defined in the specification (XML) using a key and a value for each one. Then, the way to obtain the parameters is as simple as calling the method get(String key) indicating the key of the desired parameter.

LSimParameters param = init.getParameters();
String num = (String) param.get("num");

In this example, there is only one parameter, the number that the application has to send to the server.

Start

6. Once initialized, the applications gets blocked until it receives the command to start execution.

lsim.start(new DummyHandler());

The application doesn’t need any parameter to start, so here the DummyHandler is used. This handler is also part of the library and its only purpose is to wait for the Coordinator to signal the start.

7. The application should finish invoking stop() method. This command is needed for coordination purposes and informs the Coordinator that the Worker has finished his work so it can be removed from running applications list.

lsim.stop(new DummyHandler());

After these steps are done the method start should look like this:

@Override
public void start(LSimDispatcherHandler dispatcher) {
    System.out.println("Application running");
    LSimWorker lsim = LSimFactory.getWorkerInstance();
    lsim.setDispatcher(dispatcher);

    // init 
    InitHandler init = new InitHandler(lsim, 5);
    lsim.init(init);

    // getting parameters
    LSimParameters param = init.getParameters();
    String num = (String) param.get("num");
    System.out.println("number to send: " + num);
    
    // start
    lsim.start(new DummyHandler());
    
    Socket clientSocket;
    try {
        clientSocket = new Socket("localhost", 6789);
        DataOutputStream outToServer = new DataOutputStream(clientSocket.getOutputStream());
        outToServer.writeChars(Integer.parseInt(num)+"\n");
        clientSocket.close();
    } catch (UnknownHostException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

    // stop
    lsim.stop(new DummyHandler());
}

Server

8. Similarly, the server adaptation will result in this:

package server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import edu.uoc.dpcs.lsim.LSimFactory;
import edu.uoc.dpcs.lsim.utils.LSimParameters;
import lsim.LSimDispatcherHandler;
import lsim.application.ApplicationManager;
import lsim.application.handler.DummyHandler;
import lsim.application.handler.InitHandler;
import lsim.application.handler.ResultHandler;
import lsim.worker.LSimWorker;

public class Server implements ApplicationManager {

    @Override
    public boolean isAlive() {
        return true;
    }

    @Override
    public void start() {
    }

    @Override
    public void start(LSimDispatcherHandler dispatcher) {
        LSimWorker lsim = LSimFactory.getWorkerInstance();
        lsim.setDispatcher(dispatcher);
        int total = 0;

        // init
        InitHandler init = new InitHandler(lsim, 5);
        lsim.init(init);

        // getting parametres
        LSimParameters param = init.getParameters();
        int numApplications = Integer.parseInt((String) param.get("numApplications"));
        System.out.println("I've recieved: " + numApplications);

        // open socket before starting
        ServerSocket welcomeSocket = null;
        try {
            welcomeSocket = new ServerSocket(6789);
        } catch (IOException e1) {
            e1.printStackTrace();
        }

        // start
        lsim.start(new DummyHandler());

        try {
            for (int i = 0; i < numApplications; i++) {
                Socket connectionSocket = welcomeSocket.accept();
                BufferedReader inFromClient = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
                int valueFromApp = Integer.parseInt(inFromClient.readLine().trim());
                total += valueFromApp;
            }
            System.out.println("Total: " + total);
            welcomeSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        // stop
        lsim.stop(new DummyHandler());
    }

    @Override
    public void stop() {
    }

}

9. We want to send the sum of all received numbers to the Evaluator. Calling sendResult will use the default evaluator.

// send result
lsim.sendResult(new ResultHandler(total));

This example uses ResultHandler avaliable in LSim library. This handler has one parameter where we have to include the object result that will be passed to evaluator.

10. So, here is the resulting code of start method:

@Override
public void start(LSimDispatcherHandler dispatcher) {
    LSimWorker lsim = LSimFactory.getWorkerInstance();
    lsim.setDispatcher(dispatcher);
    int total = 0;

    // init
    InitHandler init = new InitHandler(lsim, 5);
    lsim.init(init);

    // getting parametres
    LSimParameters param = init.getParameters();
    int numApplications = Integer.parseInt((String) param.get("numApplications"));
    System.out.println("I've recieved: " + numApplications);

    // open socket before starting
    ServerSocket welcomeSocket = null;
    try {
        welcomeSocket = new ServerSocket(6789);
    } catch (IOException e1) {
        e1.printStackTrace();
    }

    // start
    lsim.start(new DummyHandler());

    try {
        for (int i = 0; i < numApplications; i++) {
            Socket connectionSocket = welcomeSocket.accept();
            BufferedReader inFromClient = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
            int valueFromApp = Integer.parseInt(inFromClient.readLine().trim());
            total += valueFromApp;
        }
        System.out.println("Total: " + total);
        welcomeSocket.close();
    } catch (IOException e) {
        e.printStackTrace();
    }

    // send result
    lsim.sendResult(new ResultHandler(total));

    // stop
    lsim.stop(new DummyHandler());
}

3. Creating the Coordinator

We will implement a simple Coordinator for our experiment. The Coordinator is responsible of receiving the experiment specification and send to workers all parameters that they need. All communications is transparently handled by the library so we only need to call init, start and stop methods.

Here is the Coordinator:

package coordinator;

import lsim.LSimDispatcherHandler;
import lsim.application.ApplicationManager;
import lsim.application.handler.DummyHandler;
import lsim.application.handler.InitHandler;
import lsim.coordinator.LSimCoordinator;
import edu.uoc.dpcs.lsim.LSimFactory;

public class TutorialCoordinator implements ApplicationManager {

    @Override
    public boolean isAlive() {
        return true;
    }

    @Override
    public void start() {

    }

    @Override
    public void start(LSimDispatcherHandler disp) {
        LSimCoordinator lsim = LSimFactory.getCoordinatorInstance();
        lsim.setDispatcher(disp);

        // init
        InitHandler init = new InitHandler(lsim, 30);
        lsim.init(init);

        System.out.println("Before start");

        // start
        lsim.start(new DummyHandler());

        // stop
        lsim.stop(new DummyHandler());
    }

    @Override
    public void stop() {
    }

}

4. Creating the Evaluator

We want to evaluate that the sum done by the server is correct and this will be the work of the evaluator.

package evaluator;

import lsim.LSimDispatcherHandler;
import lsim.application.ApplicationManager;
import lsim.application.handler.DummyHandler;
import lsim.application.handler.InitHandler;
import lsim.evaluator.DefaultResultHandler;
import lsim.evaluator.GetResultTimeoutException;
import lsim.evaluator.LSimEvaluator;
import edu.uoc.dpcs.lsim.LSimFactory;
import edu.uoc.dpcs.lsim.utils.LSimParameters;

public class Evaluator implements ApplicationManager {

    @Override
    public boolean isAlive() {
        return true;
    }

    @Override
    public void start() {
    }

    @Override
    public void start(LSimDispatcherHandler disp) {
        LSimEvaluator lsim = LSimFactory.getEvaluatorInstance();
        lsim.setDispatcher(disp);

        // init
        InitHandler init = new InitHandler(lsim, 5);
        lsim.init(init);

        // getting parameters
        LSimParameters param = init.getParameters();
        String result = (String) param.get("result");
        System.out.println("number to check: " + result);

        // set result handler to standard (receive results and return one result a time)
        DefaultResultHandler rh = new DefaultResultHandler();
        lsim.setResultHandler(rh);

        // start
        lsim.start(new DummyHandler());

        EvaluatorHandler handler = new EvaluatorHandler();
        System.out.println("obtaining result");
        try {
            lsim.getResult(handler);
        } catch (GetResultTimeoutException e) {
            e.printStackTrace();
        }
        Integer res = handler.getValue();
        if (res == Integer.parseInt(result)){
            lsim.store("Correct", "Correct result received!!!");
            System.out.println("Correct result received!!!");
        } else {
            lsim.store("Incorrect", "Incorrect result received!!!");
            System.out.println("Incorrect result received!!!");
        }

        // stop
        lsim.stop(new DummyHandler());
    }

    @Override
    public void stop() {
    }

}

DefaultResultHandler is a class of the library that allows to the Evaluator to receive results one by one. There are most advanced ResultHandlers that allow an Evaluator to receive the results in many different ways. They will be explained in more advanced examples. The store method receives two parameters: a summary of the execution result and the execution result.

The EvaluatorHandler is responsible to receive the object and return it to the Evaluator. Here is the code:

package evaluator;

import lsim.application.handler.Handler;
import lsim.result.Result;

public class EvaluatorHandler implements Handler {
    
    private Result value;
    
    @Override
    public Object execute(Object obj) {
        value = (Result) obj;        
        return value.value();
    }

    public Integer getValue() {
        return (Integer) value.value();
    }

}

5. Creating the specification

The specification contains the description of the multiple parts of the experiment: Coordinator, Workers and Evaluators.

This is the specification, named specification.xml:

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

    <experimentId>lsim-hello-world</experimentId>

    <Coordinators>
        <num>1</num> <!-- Number of instances -->
        <codeRef>lsim.LSimDispatcherHandler::/home/dllamazares/lsim/tutorial/elements/coordinator</codeRef> <!-- Local path -->
        <timeInit>5</timeInit> <!-- Expected initialization time in minutes -->
        <timeEval>1</timeEval> <!-- Expected evaluation time in minutes -->
        <class>coordinator.TutorialCoordinator</class> <!-- Name of the class to execute -->
        <jarName>test-app.jar</jarName> <!-- Name of the jar to use -->
    </Coordinators>

    <Evaluators>
        <Evaluator>
            <codeRef>lsim.LSimDispatcherHandler::/home/dllamazares/lsim/tutorial/elements/evaluator</codeRef> <!-- Local path -->
            <idTest>1</idTest> <!-- Don't modify this -->
            <Node> <!-- Don't modify this -->
                <idNode>server</idNode>
                <nMess>1</nMess>
            </Node>
            <params> <!-- Initialization parameters -->
                <result>12</result>
            </params>
            <class>evaluator.Evaluator</class> <!-- Name of the class to execute -->
            <jarName>test-app.jar</jarName> <!-- Name of the jar to use -->
        </Evaluator>
    </Evaluators>

    <Workers>
        <Worker>
            <num>1</num> <!-- Number of instances -->
            <idWorker>server</idWorker> <!-- Worker identificator -->
            <codeRef>lsim.LSimDispatcherHandler::/home/dllamazares/lsim/tutorial/elements/server</codeRef> <!-- Local path -->
            <params> <!-- Initialization parameters -->
                <numApplications>2</numApplications>
            </params>
            <class>server.Server</class> <!-- Name of the class to execute -->
            <jarName>test-app.jar</jarName> <!-- Name of the jar to use -->
        </Worker>
        <Worker>
            <num>1</num> <!-- Number of instances -->
            <idWorker>application1</idWorker> <!-- Worker identificator -->
            <codeRef>lsim.LSimDispatcherHandler::/home/dllamazares/lsim/tutorial/elements/application</codeRef> <!-- Local path -->
            <params> <!-- Initialization parameters -->
                <num>5</num>
            </params>
            <class>application.Application</class> <!-- Name of the class to execute -->
            <jarName>test-app.jar</jarName> <!-- Name of the jar to use -->
        </Worker>
        <Worker>
            <num>1</num> <!-- Number of instances -->
            <idWorker>application2</idWorker> <!-- Worker identificator -->
            <codeRef>lsim.LSimDispatcherHandler::/home/dllamazares/lsim/tutorial/elements/application</codeRef> <!-- Local path -->
            <params> <!-- Initialization parameters -->
                <num>7</num>
            </params>
            <class>application.Application</class> <!-- Name of the class to execute -->
            <jarName>test-app.jar</jarName> <!-- Name of the jar to use -->
        </Worker>
    </Workers>

</Specification>

We defined a Coordinator, an Evaluators, a worker that will be the server and two workers that will be client applications. We defined the client applications separatedly to be able to send diferent parameters to each one. There are other options to do this that will be covered in other examples.

codeRef must be defined for each element. It indicates the location of the code to be executed for the element that we are defining.

The codeRef of the coordinator is:

<codeRef>lsim.LSimDispatcherHandler::/home/manel/lsim/tutorial/elements/coordinator</codeRef>

As you see, the structure is:

lsim.LSimDispatcherHandler::directory_path

Where the first part is always the same and the directory path is where the element will be found.

The defined path is according the local environment where the application will be executed. You can find more information about the codeRef structure here.

We have defined a directory structure that will be explained in the next section.

6. Setting up a local environment to execute the experiment

In order to test the application in a local environment you have to define the following structure:

tutorial
├── applications                        # LSim will create here at run time a folder for each
│                                       # type of worker,coordinator and evaluator
├── launcher
│   ├── libraries
│   │   └── {bunch of needed libraries}
│   ├── configAPI.properties            # Port, host and name of CoDeS service
│   ├── specification.xml               # Experiment specification that we've seen
│   ├── lsim-launcher-0.1.4.jar
│   └── launchExperiment.sh             # Execution script for launcher
├── codes
│   ├── libraries
│   │   └── codes-1.0M.jar
│   ├── configAPI.properties            # Same content as the files from launcher
│   ├── config.properties               # Local dispatcher location
│   ├── lsim-codes-0.1.4.jar
│   └── startCodes.sh                   # Execution script for CoDeS
├── dispatcher
│   ├── libraries                       # Libraries that will be copied to each component
│   │   └── {bunch of needed libraries}
│   ├── configDispRun.properties        # Dispatcher configuration
│   ├── lsim-dispatcher.jar
│   ├── script.sh                       # Generic script that will be copied to each component
│   ├── script.bat                      # Generic script that will be copied to each component
│   └── startDispacher.sh               # Execution script for dispatcher
├── elements
│   ├── application
│   │   └── test-app.jar
│   ├── coordinator
│   │   ├── configAPI.properties        # Same content as the file from launcher
│   │   └── test-app.jar
│   ├── evaluator
│   │   └── test-app.jar
│   └── server
│       └── test-app.jar
├── README.txt                          # Some things important to remember
└── run.sh                              # Execution script that executes the three scripts

7. Launching the experiment

Once you have created all this structure and the scripts, libraries, and properties files are on their location, you can try to launch the experiment.

This are the steps to run the experiment:

  1. Execute resource pool:

    In codes folder, run script startCodes.sh (or .bat)

  2. Run the dispatcher:

    In dispatcher folder, run script startDispatcher.sh ( or .bat). You can see the ouput of the dispatcher with the command tail -f log.txt

  3. Launch the experiment with launcher:

    In launcher folder, run the script launchExperiment.sh (or .bat). You will see the progress of the execution.

If you want to do it faster, there is a script run.sh in the tutorial folder that runs the three scripts.

If the execution is successful you can see the results of the execution on each folder in the applications folder. This folders have the name like lsim-hello-world[timestamp]X0componentname. There should be a folder for each instance of each element: server, application1, application2, coordinator and evaluator. You can access to this folders because you are using a local environment. If you use a distributed environment, you have to specify in the XML specification the storage that the frontend will use.

The log of the evaluator log.txt in evaluator folder should contain the output of the evaluation result. In the last lines you should see:

Get Result!!!!!!!! Correct result received!!!
Correct result received!!!

And that’s all! We will publish more advanced tutorials and documentation soon!

Annex

A zip file containing the folder structure and needed libraries and files defined on section 6 can be downloaded from: lsim-hello-world.zip. After download and unzip the files, the path to the tutorial folder must be changed in the following files:

  • specification.xml (in folder launcher)
  • configDispRun.properties (in folder dispatcher)

List of files of the tutorial



Published

18 December 2012

Tags