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¶
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:
This is how the default state machine is defined.
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¶
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.