Skip to content
ghale edited this page Sep 16, 2014 · 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. One can also execute the default actions (start, stop, deploy, etc) as well as compose aggregate tasks that execute arbitrarily complex combinations of actions.

Adding the Plugin

See the Gradle plugin portal page for this plugin.

Note that with versions 1.2+, the plugin id has changed to a qualified form to comply with the new Gradle plugin portal (i.e. from 'glu' to 'com.terrafolio.glu').

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"
     
                        // agent-1
                        model merge(
                                applications.app1.generate([
                                        'agents': [ 'agent-1': [ 'tst1', 'step001' ] ],
                                        'initParameters': [ "port": 9000 ]
                                ])
                        )

                        // agent-2, agent-3
                        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.

You can also set any of the other values of an entry in an application definition or override the value during a generate:

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' ]
                        metadata [ 'version': '1.2.1', 'domain': 'dev' ]
                }
        }

        fabrics {
                "glu-test-1" {
                        server servers.test
                        zookeeper "localhost:2181"

                        // agent-1 /app1
                        model merge(
                                applications.app1.generate([
                                        'agents': [ 'agent-1': [ 'tst1', 'step001' ] ],
                                        'initParameters': [ "port": 9000 ],
                                        'metadata': [ 'domain': 'test' ],
                                        'parent': '/root'
                                ])
                        )

                        // agent-2, agent-3 /app1
                        model merge(
                                applications.app1.generate([
                                        'agents': [ 
                                                    'agent-2': [ 'tst2', 'step002' ],
                                                    'agent-3': [ 'tst2', 'step003' ]
                                                  ],
                                        'initParameters': [ "port": 9001 ]
                                ])
                        )

                        // agent-1 /root using JsonBuilder closure
                        model merge({
                            entries (
					[
						[
							agent: 'agent-1',
							mountPoint: '/root',
                                                        script: "http://localhost:8080/glu/repository/scripts/root_deployment_script.groovy",
                                                        initParameters: [ port: 8080 ]
						]
					]
				)
                        })
                }
        }
}

The script above would generate the agent1 entry with metadata of version: '1.2.1', domain: 'test', while the agent2 and agent3 entries would have metadata of version '1.2.1', domain: 'dev'. The agent1 entry would also have its parent set to /root. It also demonstrates the use of a JsonBuilder closure to add the /root entry to the model.

Convention Tasks

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.

Several tasks are created by convention for each fabric. Each convention task name is the action appended by the name of the fabric. For example, if the fabric name is "test", the convention task for the deploy action would be named "deployTest". Convention tasks are created for each of the following actions for each fabric:

  • loadModel - Loads the model into the fabric on the server (e.g. task name for the "test" fabric would be "loadModelTest").
  • deploy - Deploys any and all deltas to the model. In the event that there are no deltas between the current model and the loaded model, 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.
  • undeploy - Undeploys all entries in the model.
  • redeploy - Undeploys and redeploys all entries in the model.
  • start - Starts all entries in the model if any are not running.
  • stop - Stops all entries in the model if they are running.
  • bounce - Bounces all entries in the model.

Configuring Executions

So, you have a model defined. Awesome. Now you want to do something interesting with it. Presumably you have a delta between the loaded model and the currently running model. A composable task type (GluExecutionTask) is provided that allows you to create executions of any type. One thing you could do is apply changes to all entries that match a given tag:

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

...

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 other default actions (start, stop, deploy, redeploy, bounce, undeploy).

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

task("undeploy", type: GluExecutionTask) {
        fabric glu.fabrics.'glu-test-1'
        // undeploy all applications on linux agents
        undeploy(tags: [ 'linux' ])
}

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' ]

Deployment Status Callbacks

By default, while a given deployment is running, it polls the server for status periodically and prints out a log message ("X of Y steps completed..."). Once the deployment is complete (success or failure) it logs the status of each step along with the time it took. On the other hand, one may want to override this default behavior and do something more sophisticated during each polling interval and/or on deployment completion. A callback can be added to any Execution task by defining custom processing closures.

A callback can be added on an Execution task by setting the following:

// To set the action on every polling interval
withDeploymentPollingAction { String executionId, Context context ->
 ...
}

// To set the action to run at the completion of the deployment
withDeploymentCompleteAction { String executionId, Context context ->
 ...
}

The context is an instance of org.apache.commons.chain.Context and can be treated as a Map. Some values available in the context:

  • CONSOLE_URL - The url of the glu instance.
  • LOGGER - The default logger for the ExecutionCommand.
  • FABRIC - The name of the fabric set on the task.
  • SERVICE - An instance of GluService configured for this fabric.
  • POLLING_STATUS - An instance of DeploymentStatus from the last polling interval.
  • STATUS - The execution status document returned by Glu on status completion (ie the result of a rest call to * GET /deployment/current). This value is only available after the deployment completes.

Changelog

v0.3.1 (09/15/14)

Upgraded to Gradle v2.1 and made changes to support inclusion at plugins.gradle.org. This release changes the plugin id from 'glu' to 'com.terrafolio.glu' to comply with the new id naming scheme.

v0.3.0 (10/22/13)

  • Issue #8: Add callback capability during deployment status intervals

Roadmap

  • 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.
  • Allow extensible support for custom actions.

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.