FPath and FScript Tutorial

Table of Contents

  1. Introduction
  2. Navigation With The FPath Notation
    1. Model
    2. Principle of operation
    3. Example expressions/requests
  3. FScript Reconfigurations
    1. Safety guarantees
  4. Usage
    1. Java API
    2. Interactive console
  5. Conclusion and Future Work

1. Introduction

One of the main features of the Fractal component model is that it is fully dynamic and reflexive: it is possible both to discover the structure of a Fractal application (introspection) and to modify it (intercession) at runtime. This makes it possible to build, for example, administration tools like Fractal Explorer with which it is easy to navigate inside a running application and modify it interactively. It is also possible to program dynamic reconfigurations, even unanticipated ones, to be executed in a running application. This is important in order to evolve applications without stopping and redeploying them (for example to update a component or subsystem) and to build self-adaptive and autonomic systems which must take reconfiguration decisions -- and apply them -- dynamically and automatically (i.e. without human intervention).

Such dynamic discovery and reconfigurations can be programmed in the same language than the application itself, for example Java, using the standard Fractal APIs. However, such an approach has several drawbacks:

In order to overcome these limitations while still retaining Fractal's advantages, we have designed and implemented a new language, called FScript, to navigate inside Fractal architectures and dynamically reconfigure them. FScript can be though of as the dual of the standard Fractal ADL: while the ADL (Architecture Description Language) uses a declarative approach to specify the initial configuration of an application, FScript is an imperative language and is used to incrementally reconfigure a running application.

To do this, FScript provides a special notation called FPath to navigate intuitively inside an architecture and select parts of it (Section 2). These elements can then be acted upon to reconfigure the architecture using primitive Fractal operations or user-defined reconfigurations scripts (Section 3). Beyond its direct syntactic support for Fractal concepts, the main feature of FScript is that it provides guarantees on the consistency of the reconfigurations (Section 3.1) by considering these as transactions. FScript has been implemented as a simple interpreter which can be easily embedded in a Fractal application (in Java), or used interactively through a text console (Section 4).

2. Navigation With The FPath Notation

FPath is a special notation used inside the FScript language to navigate inside Fractal architectures and select elements in it according to some predicate. Its syntax and execution model are inspired by the XPath language which solves the same problem on XML documents (although FPath does not use XML at all).

2.1. Model

FPath sees a given Fractal architecture as an oriented graph with labelled arcs. Different kinds of nodes represent all the architectural elements we chose to reify:

These nodes are connected by labelled arcs, which denote the kind of relation between them. For example, an arc labelled interface goes from a given component node to each interface node representing the component's interfaces. In the same way, if composite C1 contains C2 as a sub-component, the corresponding nodes N1 and N2 will be connected by two arcs: one labelled child from N1 to N2, and one labelled parent from N2 to N1.

The following types of arcs, called axes are defined in FPath:

2.2. Principle of operation

Given this representation, FPath expressions denote relative paths starting from an initial (set of) node(s) in the graph. Such a path is made of a series of steps, each made of up to three elements: axis::test[predicate] (the predicate is optional). On each step, an initial set of nodes is converted to a new set by following all the arcs with a label corresponding to the axis, then filtering the result using the test (on the node names) and optional predicates (boolean expressions applied to each candidate). For a multi-step path, this algorithm is repeated with the result of the previous step as the current node-set of the next.

For example, the FPath expression sibling::*/interface::*[provided(.)][not(bound(.))] is made of two steps. The first one uses the sibling axis, an "empty" test * (which is always true) and has no predicate. The second step uses the interface axis, no test either, and two predicates which are combined. Inside the predicates, the dot "." represents the current node on which the predicate is evaluated. Evaluating the complete expression starting from an initial component node will:

  1. select all its sibling components, however they are named;
  2. select all the external interfaces of these siblings;
  3. filter this set of interfaces to return only server interfaces (provided()) which are not already bound.

The expressions used as predicates can be any FPath expression, which includes not only paths but also standard arithmetic operations, comparisons, function calls, litteral strings and numbers and finally variable references ($varName). When a path expression is used as a predicate, it is considered true if and only if it returns a non-empty set of nodes. For example, to find all the components in a application which provide configuration attributes, one could use the following expression on the application's root component: descendant-or-self::*[./attribute::*]. This initially selects all the components contained in the root, recursively, and then filters this set to retain only those from which the step ./attribute::* returns a non-empty set, i.e. the nodes which have configuration attributes. Note that this expression is different from descendant-or-self::*/attribute::*, which returns the configuration attributes themselves, not the components which provide them.

2.3. Example expressions/requests

These example expressions are described in the context of the following example application:

Example Fracal architecture

3. FScript Reconfigurations

The preceding section described the FPath notation which is used to navigate inside a Fractal architecture and select parts of it, but can not modify the architecture. The complete FScript language, of which FPath is just a part, enables the definition of reconfiguration actions to apply to a running application. FScript is a simple imperative/procedural language whose main features are:

In this section, we present the syntax and semantics of the rest of the language, beyond FPath.

Here is a simple example of the definition of an FScript reconfiguration action which illustrates almost all of FScript constructs. It automatically connects a component's required interfaces by discovering the compatible server interfaces on sibling components.

action auto-bind(comp) = {
  // Selects the interfaces to connect
  clients = $comp/interface::*[required(.)][not(bound(.))];
  for itf : $clients {
    // Search for candidates compatible interfaces
    candidates = $comp/sibling::*/interface::*[compatible?($itf, .)];
    if ($candidates) {
      // Connect one of these candidates
      bind($itf, one-of($candidates));
    }
  }
  return size($comp/interface::*[required(.)][not(bound(.))]) == 0;
}

This defines a new reconfiguration action named auto-bind, which takes one parameter, named comp. The body of the action is defined inside braces, and consists in a sequence of simple statements (assignements and procedure calls) ended with semicolons and control structures (iteration and conditionals). FScript also supports comments, using C/C++ syntax.

Given a component comp as parameter, this action first uses an FPath expression to find all its client interfaces which are not yet bound, and stores the result in variable clients. The action the iterates over this set of client interfaces using the itf iteration variables. On each iteration, the action searches for compatible interfaces on the siblings of com, again using an FPath expression. This set of candidates is stored in variable candidates. Finally, the action tests whether this set is empty, and if not, uses the primitive action bind() (which corresponds to Fractal's BindingController#bindFc() method) to connect the client interface itf to one of the candidates. Finally, it returns a boolean indicating whether all client interfaces have been bound.

FScript distinguishes two kinds of procedures: functions and actions. Functions are guaranteed to be side-effect free, and can only introspect an architecture, not modify it. They can be used safely inside FPath requests, for example in the predicates. Functions are defined like actions, expect that they use the function keyword instead of action, and can only invoke other functions, not actions (be they primitive or user defined). FScript provides a standard library of primitive functions and actions which gives the user access to all the information available from the Fractal API, and all the standard reconfigurations.

The complete list of primitive actions is the following:

As Fractal is designed to be extensible, new controllers -- and hence new reconfigurations operations -- can be added to the model. FScript is designed and implemented so that it is easy to add the corresponding FScript primitives.

Variables in FScript are not typed (values are). They are created either explicitely on their first assignment (varName = expression;) or implicitely when entering inside the body of a procedure, where parameters behave like local variables. Variable reference is done by preceding the name of the variable with a dollar sign (varName). Variables are lexically scoped, but only procedure bodies introduce a new scope, not conditionals and iterations.

The control structures available in FScript are voluntarily limited so that we can guarantee the termination of all reconfigurations. They are:

3.1. Safety guarantees

FScript's design and implementation guarantee the consistency of reconfigurations. Because these reconfigurations are applied to running applications, we must guarantee that they will not break the target system. To this end, we have chosen a set of consistency criterion, in particular transactional integrity (atomicity, consistency of the final state, isolation) and termination of the reconfigurations. The validation of these criteria is guaranteed in part by the language's structure itself, whose expressive power has been limited, and in part by the implementation. More precisely:

4. Usage

FScript is currently implemented in Java as a simple interpreter. Care has been taken to make it easy to embed in existing Java applications, with only one external dependency outside of Fractal and Fractal ADL (namely, the ANTLR parsing tool).

4.1. Java API

Once fscript.jar and antlr.jar are available, one can use FScript to query or reconfigure an application in this way:

  1. First, create an interpreter:
    FScriptInterpreter fscript = new FScriptInterpreter();
  2. Optionally load custom functions and actions definitions stored in external files (this can be done at any time):
    fscript.loadDefinitions(new FileReader("mydefinitions.script"));
  3. Evaluate an FPath expression to query a running Fractal application. This requires setting up the variables used in the expression, especially node variables.
    Map vars = new HashMap();
    vars.put("root", fscript.createComponentNode(aComponent));
    Object result = fscript.evaluate("$root/descendant::*[./attribute::size]", vars);
    for (Node n : (Set<Node>) result) {
      Component c = ((ComponentNode) n).getComponent();
      // do something with c
    }
  4. Execute FScript reconfigurations:
    result = fscript.execute("my-reconfiguration($root);", vars);

4.2. Interactive console

To enable interactive experimentation, FScript also provides a simple, text-based console. This console provides a prompt where FPath requests and FScript statements can be entered and executed immediatly. The console also offers a few special commands (starting with !) to load an external file containing custom functions and actions definitions and to launch a component through a Runnable interface. Here is the transcript of a sample session in which we instanciate the Comanche HTTP server, launch it and reconfigure it dynamically using custom actions loaded dynamically (lines starting with # are comments and not part of the session):

# Instanciate Comanche from its ADL definition
FScript> c = new("comanche.Comanche");

# Start the component. This maps to LifecycleController.startFc()
# and only activates the component. It does not start the server.
FScript> start($c);

# Actually launches (in a separate thread) the server through the
# "r" interface (of type Runnable) of the $c component.
FScript> :run r $c

# Load custom actions to manage a cache component
FScript> :load cache.fscript

# Enables the cache on an internal component using a custom
# action. The server does not need to be stopped.
FScript> h = $c/descendant::rh;
FScript> enable-cache($h);

# Get the newly added cache component
FScript> cache = $h/child::cfh;

# Lookup its current maximum size
FScript> $cache/attribute::maximumSize
0

# Change this attribute
FScript> set-value($cache/attribute::maximumSize, 42*1024);