glu script

A glu script is a set of instructions backed by a state machine that the agent knows how to run. In general, and by default, a glu script represents the set of instructions which defines the lifecycle of what it means to install, configure, start, stop, unconfigure and uninstall an application.

Groovy Class

MyGluScript.groovy

A glu script is a groovy class which contains a set of closures where the name of each closure matches the name of the actions defined in the state machine. This example shows the default closure names. The script can also store state in attributes (like port and pid in this example).

Tip

The code of each closure can be any arbitrary groovy/java code but remember that the agent offers some capabilities to help you in writing more concise code.

State machine

Each glu script is backed by a state machine which is an instance of org.linkedin.groovy.util.state.StateMachine (StateMachine api). The default state machine is the following:

State Machine diagram

This is how the default state machine is defined.

State Machine Definition

The minimum (usefull) state machine that you can define could look like:

[
    NONE: [ [to: 'running', action: 'start'] ],
    running: [ [to: 'NONE', action: 'stop'] ]
]

Note

If an action is empty you don’t even have to define its equivalent action but you still need to call all prior actions to satisfy the state machine.

Defining your own state machine

In the event when the default state machine does not match your needs, you can define your own (system wide) state machine and configure glu to use it. See the section on how to configure a different state machine.

Configuring the console

You will need to configure the console (UI) to display your own actions if you want to. Check the Plans and MountPoint actions sections for more details.

Tip

In addition to your own state machine you can also use the plugin hook PlannerService_pre_computePlans to define your own custom actions!

Capabilities

As described in the section Capabilities, a glu script can use all the capabilities provided by the agent.

Tip

Implicitely (at runtime), all glu scripts implement the GluScript interface.

Table of all the properties usable from a GluScript:

Name Usage
children Access to the children of this glu script
log Write log messages in agent log file
mountPoint The mountPoint on which this script was mounted
params Access to the model initParameters section
parent Access to the parent glu script
shell Access to all shell like capabilities (mv, ls, etc...)
rootShell Access to all shell like capabilities (mv, ls, etc...)
shell.env Access to environment variables set at agent boot time
stateManager Manage/Query the state
state Shortcut to current state
timers Schedule/Cancel timers
agentZooKeeper Access to the ZooKeeper connection used by the agent

Parent Script

When you define the parent glu script (for use in the static model), you must add the following closure to the glu script:

def createChild = { args ->
  return args.script
}

This closure takes a map with 3 arguments:

  • mountPoint: the mountPoint on which the child script will be mounted
  • script: the raw child script just after it has been instantiated
  • initParameters: the init parameters that will be provided to the child

This closure must return the actual script to use. In its simplest form, the closure does nothing besides returning the script itself untouched.

Tip

This closure allows you to customize the child including returning a completely different one! For example:

class JettyParentGluScript
{
  def deployHotDir

  def install = {
    deployHotDir = ... // compute hot dir
  }

  def createChild = { args ->
    args.script.deployHotDir = deployHotDir // 'inject' deployHotDir in child
    return args.script
  }
}

In addition to this required closure, you may define 3 others to do custom work:

def onChildAdded = { args -> // child
  // note that the child you are getting here is different from the script you got in createChild
  // in createChild you get literally the instance of the class of the script
  // in onChildAdded you get an instance of GluScript which is the wrapped script
}

// symmetric of onChildAdded
def onChildRemoved = { args -> // child
}

// symmetric of createChild
def destroyChild = { args -> // mountPoint, script
}

Conventions

Logs

In order to be able to see (in the console) log files produced by an application deployed by the glu script, you can follow the convention described in the “Log Files Display” section.

Processes

In order to be able to see (in the console) processes managed by the glu script, you can follow the convention described in the “Processes Display” section.

Fields

All fields in a glu script are stored (locally on the agent) and exported (remotely to ZooKeeper). Check the “Integration with ZooKeeper” section.

An example of glu script

glu script example

Real life example

You can find a real life example of a glu script called JettyGluScript which shows how to deploy a webapp container (jetty), install web applications in it and monitor it.

Developing and unit testing a glu script

The glu script test framework allows you to develop and unit test your glu script without having to worry about setting up all the components. To write a unit test for a glu script, you can simply inherit from the GluScriptBaseTest, setup a couple of parameters and run the convenient methods provided by the framework:

class TestMyGluScript extends GluScriptBaseTest
{
  public void setUp() {
    super.setUp()
    initParameters = [ p1: 'v1' ]
  }

  // this method is not required if you follow the conventions
  public String getScriptClass() {
    return MyGluScript.getClass().getName()
  }

  public void testHappyPath() {
    deploy()
    undeploy()
  }
}

In order to compile the script and the unit test, you need the following dependencies (make sure you use the appropriate versions which may differ from this example!):

// gradle format
dependencies {
  compile "org.linkedin:org.linkedin.util-groovy:1.7.0"
  compile "org.linkedin:org.linkedin.glu.agent-api:3.1.0"
  groovy  "org.codehaus.groovy:groovy:1.7.5"

  testCompile "org.linkedin:org.linkedin.glu.scripts-test-fwk:3.1.0"
  testCompile "junit:junit:4.4"
}

Note

You can use maven or any other dependency management system as long you include the proper dependencies.

Tip

For more information and examples, you can check the following:

  • GluScriptBaseTest to check what the framework has to offer (javadoc is fairly comprehensive)
  • TestJettyGluScript for a real life example of unit testing a glu script
  • glu-scripts-contrib is the project that contains glu script contributed by the community as well as a sample
  • sample is a sample glu script and unit test with comprehensive documentation demonstrating several features about writing and unit testing a glu script

Packaging a glu script

A glu script can be packaged in 2 different ways:

  • as a simple groovy file, in which case the script entry in the model is a URI pointing directly to the groovy file. Example:

    "script": "http://host:port/x/c/v/MyGluScript.groovy"
    
  • already compiled and packaged in a jar file (new since 4.2.0), in which case the script entry in the model is a special URI of the form:

    class:/<FQCN>?cp=<URI to jar>&cp=<URI to jar>...
    

    Example:

    "script": "class:/com.acme.MyGluScript?cp=http%3A%2F%2Facme.com%2Fjars%2Fscript.jar&cp=http%3A%2F%2Facme.com%2Fjars%2Fdependency.jar"
    

    Tip

    In this second form, the script can be split into multiple files and have external dependencies (as long as they are provided as classpath elements)

    Warning

    Every classpath element (cp) being a query string paramater must be properly URL encoded!

Inheritance

New since 4.2.0, a glu script can now inherit from another one (in which case you should use the second packaging technique so that you can distribute the base script as a dependency). Here is an example:

The base script:

package test.agent.base

class BaseScript
{
  def base1
  def base2
  def base3

  def install = { args ->
    log.info "base.install"
    base1 = params.base1Value
    log.info "base.install.\${args.sub}.\${subValue}"
  }

  def baseConfigure = { args ->
    base2 = args.base2Value
    return "base.baseConfigure.\${args.sub}.\${subValue}"
  }

  protected def getSubValue()
  {
    return "fromBaseScript"
  }
}

The subclass script:

package test.agent.sub

import test.agent.base.BaseScript

class SubScript extends BaseScript
{
  String sub1

  def configure = { args ->
    sub1 = baseConfigure(args)
    base3 = params.base3Value
  }

  protected def getSubValue()
  {
    return "fromSubScript"
  }
}

A few words about the example:

  • all attributes defined in the base script will automatically be exported to ZooKeeper as if they were defined in the subclass!
  • since glu uses closures (and not methods), you cannot override a lifecycle method. Instead you should use a technique similar to the example in which the base class defines a closure (baseConfigure) that gets called directly by the subclass.
Google+