Skip to content
Gary Hale edited this page Aug 1, 2013 · 31 revisions

Gradle Glu Plugin

This is a plugin for programmatically controlling glu deployment servers using gradle. This plugin allows you to store your glu json model in source control and apply it to the fabric as a gradle task. The model can be stored as a json file or generated using Maps or JsonBuilder closures. It also provides a simple templating function where an "application" is defined that can generate entries for multiple agents with agent-specific overrides.

Configuring Fabrics

The following example shows a basic configuration where the model is stored in the project as a file and applied to the server without changes.

apply plugin: "glu"

glu {
        servers {
                test {
                        url "http://localhost:8080/console"
                        username "admin"
                        password "admin"
                }
        }

        fabrics {
                "glu-test-1" {
                        server servers.test
                        zookeeper "localhost:2181"
                        model file("model.json")
                }
        }
}

The next example shows how to programmatically build a model and use applications to create template configurations for model entries.

apply plugin: "glu"

glu {
        servers {
                test {
                        url "http://localhost:8080/console"
                        username "admin"
                        password "admin"
                }
        }

        applications {
                app1 {
                        mountPoint "/app1"
                        script "http://localhost:8080/glu/repository/scripts/app1_deployment_script.groovy"
                        tags = [ 'app1' ]
                }
        }

        fabrics {
                "glu-test-1" {
                        server servers.test
                        zookeeper "localhost:2181"
                        model merge(
                                applications.app1.generate([
                                        'agents': [ 'agent-1': [ 'tst1', 'step001' ] ],
                                        'initParameters': [ "port": 9000 ]
                                ])
                        )
                        model merge(
                                applications.app1.generate([
                                        'agents': [ 
                                                    'agent-2': [ 'tst2', 'step002' ],
                                                    'agent-3': [ 'tst2', 'step003' ]
                                                  ],
                                        'initParameters': [ "port": 9001 ]
                                ])
                        )
                }
        }
}

In the example above, we create an application that is deployed on multiple agents. We use a single application definition to define the mountpoint, the script and any tags that we want to apply to all instances of the application. Then we generate an entry in the model for each agent that we want to deploy this application on, setting any agent-specific values. Note that we can define multiple agents in a single generate call that use the same initParameters. This approach has value when there are dozens of agents where the same application is being deployed. Each agent can have specific tags applied to it (along with any tags applied at the application level) which would allow you to cross-cut execution plans on different sets of agents.

The plugin provides a "generateModels" task which will dump the json model for each fabric as a file to the project's buildDir. This allows you to inspect the model first before loading it on the server.

Configuring Executions

So, you have a model defined in gradle. Awesome. Now you want to do something with it. Well, first thing that makes sense is to load the model into a fabric:

import com.terrafolio.gradle.plugins.glu.GluLoadModelTask

...

task('loadModel', type: GluLoadModelTask) {
        fabric glu.fabrics.'glu-test-1'
}

This takes your json model and loads it into the fabric on the server. Now, presumably you have a delta between the new model and the previous model. A composable task type (GluExecutionTask) is provided that allows you to create executions of any type. The simplest and most obvious execution is to deploy any deltas in the model:

import com.terrafolio.gradle.plugins.glu.GluExecutionTask

...

task("deploy", type: GluExecutionTask) {
        fabric glu.fabrics.'glu-test-1'
        // deploy all changes to the model
        deploy()
}

In the event that there are no deltas between the models, this task would do nothing. Or, rather, it would create a deployment plan, see that there is nothing to do in the plan, and politely exit with a warning that no actions were performed.

We could instead deploy only changes to entries that match a specific tag:

task("deployApp1", type: GluExecutionTask) {
        fabric glu.fabrics.'glu-test-1'
        // deploy only changes for app1
        deploy(tags: [ 'app1' ])
}

The task above would be the same as selecting the 'Plans' tab in the console, setting a filter for 'tag=app1', selecting the 'Deploy' action, and executing the plan. As with a situation where there are no changes to the model, in the event that the tags do not match any entries, the task will simply display a warning and do nothing.

You can also execute any of the default actions (start, stop, deploy, redeploy, bounce, undeploy).

task("stopApp1", type: GluExecutionTask) {
        fabric glu.fabrics.'glu-test-1'
        // stop only app1
        stop(tags: [ 'app1' ])
}

task("undeploy", type: GluExecutionTask) {
        fabric glu.fabrics.'glu-test-1'
        // undeploy all applications
        undeploy()
}

Or you can compose multiple steps in a task:

task("quiesceAndDeploy", type: GluExecutionTask) {
        fabric glu.fabrics.'glu-test-1'
        
        // stop incoming connections
        stop(tags: [ 'frontend' ])
        // deploy backend processing nodes
        deploy(tags: [ 'processing-node' ])
        // restart incoming connections
        start(tags: [ 'frontend' ])
}

By default, executions are performed in parallel, but you can also perform them in sequence:

task("rollingDeploy", type: GluExecutionTask) {
        fabric glu.fabrics.'glu-test-1'
        // roll changes across each agent for app1
        deploy(tags: [ 'app1' ], order: 'sequential')
}

Or all of the above:

task("quiesceWithRollingDeploy", type: GluExecutionTask) {
        fabric glu.fabrics.'glu-test-1'
        
        // stop incoming connections (parallel)
        stop(tags: [ 'frontend' ])
        // rolling deploy of backend processing nodes (sequential)
        deploy(tags: [ 'processing-node' ], order: 'sequential')
        // restart incoming connections (parallel)
        start(tags: [ 'frontend' ])
}

Merging Maps

There are situations where while defining the model, the semantics of the DSL may be ambiguous, especially around values that have singular or aggregate values. For instance:

        fabrics {
                "glu-test-1" {
                        server servers.test
                        zookeeper "localhost:2181"
                        model [
                                metadata: [ name: 'My Model' ],
                                agentTags: [
                                        'test': [ 'tag2', 'tag3' ]
                                ],
			        
                                ...
                        ]
                        model merge(metadata: [ name: 'My Merged Model' ])
                        model merge(agentTags: [ 'test': [ 'tag1' ] ])
                }
        }

In this situation, what should the resulting value of the agentTags.test be? [ 'tag1' ] or [ 'tag1', 'tag2', 'tag3' ]? What about the value of metadata.name? When the plugin comes across such situations it follows simple rules:

  • If the value is singular, (a string or a number), it overwrites it with the merged-in value.
  • If the value is a list, it appends the merged-in value to the end of the list.
  • If the value is a map, it merges the new map in following the two rules above.

In the example above, the metadata.name gets set to "My Merged Model" (singular value), while agentTags.test gets set to [ 'tag1', 'tag2', 'tag3' ]. In the event that you want to set the value of a list instead of merging it, you need to explicitly set it rather than merging:

model.agentTags.test = [ 'tag1' ]

Roadmap

This is still in a beta state, but I plan to add the following features over time:

  • Ability to filter based on values other than tags (e.g. mountpoint or metadata)
  • Add tasks to interrogate the model for any deltas and display them for informational purposes.

Suggestions, contributions and/or bug reports are welcome. Please log any as a pull request or an issue in github (https://github.com/ghale/gradle-jenkins-plugin/issues) or just email me (address is in build.gradle) if you would prefer.

Clone this wiki locally