Skip to content
This repository has been archived by the owner on Feb 26, 2023. It is now read-only.

Latest commit

 

History

History
195 lines (150 loc) · 8.36 KB

GettingStarted.md

File metadata and controls

195 lines (150 loc) · 8.36 KB

Getting Started with the Amelia DSL

The helloworld-rmi Example

The helloworld-rmi example is a small project that comes with the FraSCAti distribution. During this tutorial we will use this example to explain the basics of specifying deployment artifacts with the Amelia DSL.

This example comprises two SCA components: a Server, exposing a print service through RMI, and a Client, consuming that service to print a message in the standard output. These components are configured by default to run in the same machine, but with litle effort we can change that, running each component in a separate node.

Generally, to run this example you only need to do two main tasks: compile the source code, and then run the components having into account the dependencies among them. The latter is generally time consuming and error prone in complex projects, because you have to carefully execute each component into its corresponding node respecting the dependencies order. Moreover, when you add several machines to the scenario, you need to perform another task: transport either the source code or the compiled artifacts to the corresponding computing nodes. This may also requires relocating local resources, so they are available for the components using them.

Specifying Deployment Artifacts with Amelia

Amelia provides the syntax and semantics to abstract those repetitive deployment tasks into composable elements that will allow you to perform systematic executions of SCA systems.

In Amelia there are two compilation units (think of Java interfaces and classes): subsystems and deployments. The first ones contain the definition of hosts (computing nodes) and execution rules; the second ones allow to configure deployment strategies from subsystem definitions.

Subsystems

First, let's specify the host in which the Server and Client components will run, i.e., localhost.

Note: Amelia makes use of SSH to communicate with remote machines (including localhost), and FTP to transport resources. In this tutorial we use the default SSH & FTP ports 21 and 22.

var Host localhost = new Host("localhost", 21, 22, "user", "pass", "Ubuntu-16.04")

Notice that you can specify hosts using either the constructors in Host or the helper methods in Hosts.

Now, to specify deployment actions you only need to group command declarations as rules. An execution rule has three parts: target, dependencies (other targets), and commands. It's syntax looks like this:

target2: target0, target1, ...;
    command1
    command2
    ...

If a rule has no dependencies, don't use the trailing semi-colon. Let's see how to specify the compilation and execution of the helloworld-rmi example:

compilation:
    cd "$FRASCATI_HOME/examples/helloworld-rmi"
    compile "server/src" "server"
    compile "client/src" "client"

execution: compilation;
    cd "$FRASCATI_HOME/examples/helloworld-rmi"
    run "helloworld-rmi-server" -libpath "server.jar"
    run "helloworld-rmi-client" -libpath "client.jar" -s "r" -m "run"

Notice that $FRASCATI_HOME is not related to Amelia in any way, it's just an environment variable that gets resolved during the SSH session.

Of course, different set of rules can express the same, that's how programming works! let's see another way of specifying the deployment actions above:

server:
    cd "$FRASCATI_HOME/examples/helloworld-rmi"
    compile "server/src" "server"
    run "helloworld-rmi-server" -libpath "server.jar"

client: server;
    cd "$FRASCATI_HOME/examples/helloworld-rmi"
    compile "client/src" "client"
    run "helloworld-rmi-client" -libpath "client.jar" -s "r" -m "run"

In the first case the overall execution would be:

  1. Change the current directory to $FRASCATI_HOME/examples/helloworld-rmi
  2. Compile source code of the Server component
  3. Compile source code of the Client component
  4. Change the current directory to $FRASCATI_HOME/examples/helloworld-rmi
  5. Run the Server component
  6. Run the Client component

As these commands are executed in the same SSH session, you can ommit step 4. Now it's your turn, what would be the overall execution for the second set of rules?

Putting it all together

Now that we know how to specify hosts and execution rules, let's create a subsystem HelloworldRMI:

package subsystems

import org.amelia.dsl.lib.descriptors.Host

subsystem HelloworldRMI {
    
    var Host localhost = new Host("localhost", 21, 22, "user", "pass", "Ubuntu-16.04")
    
    on localhost {
        server:
            cd "$FRASCATI_HOME/examples/helloworld-rmi"
            compile "server/src" "server"
            run "helloworld-rmi-server" -libpath "server.jar"

        client: server;
            cd "$FRASCATI_HOME/examples/helloworld-rmi"
            compile "client/src" "client"
            run "helloworld-rmi-client" -libpath "client.jar" -s "r" -m "run"
    }
}

All rules without dependencies are executed concurrently*, while commands within the same rule are executed sequentially.

*: In fact, if they are executed using the same SSH session they will be executed sequentially. If concurrent execution is desired, you must create several instances of the same node, for example:

package ^package

import org.amelia.dsl.lib.descriptors.Host

subsystem Subsystem {
    
    var Host localhost1 = new Host("localhost", 21, 22, "user", "pass", "local1")
    var Host localhost2 = new Host("localhost", 21, 22, "user", "pass", "local2")
    
    on localhost1 {
        target1: ...
            cmd "echo concurrent"
    }
    
    on localhost2 {
        target2:
            cmd "echo concurrent"
        target3: target1;
            cmd "echo after target1"
    }
}

You can find a complete list of the supported commands here.

Subsystems are parameterizable. Parameters are useful for instantiating subsystems with different values. For example, the host may be a parameter, that way the components may be executed in several hosts abstracting the execution rules:

package subsystems

import org.amelia.dsl.lib.descriptors.Host

subsystem HelloworldRMI {
    
    param Host host
    
    on host {
        server:
            cd "$FRASCATI_HOME/examples/helloworld-rmi"
            compile "server/src" "server"
            run "helloworld-rmi-server" -libpath "server.jar"

        client: server;
            cd "$FRASCATI_HOME/examples/helloworld-rmi"
            compile "client/src" "client"
            run "helloworld-rmi-client" -libpath "client.jar" -s "r" -m "run"
    }
}

In the next section, we will see how to pass a parameter value to the subsystem.

Deployment

When executing the subsystem HelloworldRMI you're getting the default deployment. That is, a single execution with no automatic shutdown. However, if a subsystem has dependencies or parameters, it is necessary to create a deployment strategy. Deployment strategies are also useful for automatically repeating the same deployment, or retrying on failure; these are custom behaviors regarding how the deployment is executed. For example:

package deployments

import org.amelia.dsl.lib.util.RetryOnFailure

includes subsystems.HelloworldRMI

deployment RetryOnFailure {
    add(new HelloworldRMI) // deploy one instance of the subsystem
	var helper = new RetryableDeployment()
	helper.deploy([
		start(true) // Deploy and stop executed components when finish
	], 3) // In case of failure, retry 2 more times
}
package deployments

import org.amelia.dsl.lib.util.RetryOnFailure

includes subsystems.HelloworldRMI

deployment SequentialDeployments {
    add(new HelloworldRMI)
	for (i : 1..5) {
		start(true) // Deploy and stop executed components when finish
	}
}

If the subsystem expects a parameter, this is the way to go:

package deployments

import org.amelia.dsl.lib.util.RetryOnFailure

includes subsystems.HelloworldRMI

deployment SimpleDeployment {
    var Host host = new Host("localhost", 21, 22, "user", "pass", "Ubuntu-16.04")
    add(new HelloworldRMI(host))
    start(true) // Deploy and stop executed components when finish
}

In case a subsystem expects several parameters, they must be passed in the same order they were defined.