Creating utilities for post-installation control
Overview
After your packages are installed, it is sometimes necessary to perform one or more management procedures. Such management procedures might control the runtime state of a server application, invoke an administrative function, check some condition, or retrieve some data (performance or application).
You probably have some of these procedures formalized into locally written scripts. If not, they might be documented in a Wiki. Of course, the procedures might not be formalized yet, and may be typed in by hand whenever they need to be performed.
This document describes how you can formalize your management procedures into a control module utility. Once in the form of a module, your procedures are exposed to the CTL node dispatcher and can then be used to administrate your deployments in distributed fashion.
Formalizing your control procedures
CTL modules are the vehicle to formalizing your control procedures. Writing a module is a simple process facilitated by ProjectBuilder. Each procedure will be formalized as a command in a module. You can declare command-line options for each command, and assign default values for the options in the form of attributes of your new module type.
Make a utility module
1. Create a new module
Module creation is done via the "ProjectBuilder" module's create-type command. For this example, a module called "tomcatutil" is created.
ctl -p default -m ProjectBuilder -c create-type -- -supertype Managed-Entity -type tomcatutil
After running the command you will have the initial source code for the tomcatutil module. By default, the source should be found in: $CTL_BASE/src/modules/tomcatutil.
Modules have a standard directory structure with standard subdirectories where you can store your files.
module_name
|
|--- type.xml // file containing module definitions
|
+--- bin/ // optional binaries, shell scripts, etc.
|
+--- commands/ // contains generated command files
|
+--- lib/ // optional resource files
By convention, the bin directory is where you can locate scripts called by the prepare and finish steps. In the lib subdirectory, you can store other files like templates, or data files your scripts might need.
2. Assign defaults
Your management procedures often make assumptions about key settings. These settings might be executable paths, network ports, configuration file locations, or something peculiar to your procedure. It's a good idea to externalize this data in the form of named attributes. Doing so establishes a data model for your module and allows your procedures to be more flexible. Once externalized, your procedures refer to the attribute name rather than a hard coded value.
Defaults are defined using the attribute-default tags inside the type.xml file.
Edit the generated type.xml file and modify the attribute definitions (eg, edit $CTL_BASE/src/modules/tomcatutil/type.xml). Locate the attributes tags and introduce your attribute defaults between them using the attribute-default tag:
<attributes>
...
<attribute-default name="catalinaHome"
value="/demo/tomcat">
<doc>Path to CATALINA_HOME</doc>
</attribute-default>
<attribute-default name="catalinaBase"
value="/demo/tomcat">
<doc>Path to CATALINA_BASE</doc>
</attribute-default>
<attribute-default name="port"
value="8080">
<doc>Tomcat server listening port</doc>
</attribute-default>
</attributes>
In this example several attribute-default definitions are declared: one for the catalinaHome and catalinaBase that declare the Tomcat installation directories. The third attribute default declares the server listening port. Each of these attributes will be used by the procedures declared in step#3.
3. Declare your procedures
As mentioned earlier, your procedures may already be in the form of scripts, while others might be an accepted manual set of steps.
CTL allows you to capture your procedures, accommodating existing scripts or letting you express procedures in commands directly. The following examples show several ways to express procedures as module commands.
The first set of examples will show how to define procedures that invoke scripts that are external to the module, in this case left behind after the package was installed.
<commands>
<!--
**
** stopService
**
-->
<command name="stopService" description="Stop command." command-type="Command" is-static="true">
<execution-string>bash</execution-string>
<argument-string><![CDATA[
export CATALINA_HOME=${opts.basedir};
export CATALINA_BASE=${opts.basedir};
$CATALINA_HOME/bin/shutdown.sh
]]>
</argument-string>
<opts>
<opt parameter="basedir" description="catalina home" required="false"
property="opts.basedir" type="string" defaultproperty="entity.attribute.catalinaHome"/>
</opts>
</command>
<!--
**
** startService
**
-->
<command name="startService" description="Start command." command-type="Command" is-static="true">
<execution-string>bash</execution-string>
<argument-string><![CDATA[
export CATALINA_HOME=${opts.basedir};
export CATALINA_BASE=${opts.basedir};
$CATALINA_HOME/bin/startup.sh
]]>
</argument-string>
<opts>
<opt parameter="basedir" description="catalina home" required="false"
property="opts.basedir" type="string" defaultproperty="entity.attribute.catalinaHome"/>
</opts>
</command>
The example above, defined two procedures as commands named "stopService" and "startService". Each of these commands calls an external script assumed to already be present on the machines where the procedure runs.
The attribute default definitions are used to default the -basedir paramaters in each of the commands.
The next exmple shows how to package an existing script into a module. The benefit of keeping scripts in the module is that you can rely on the ControlTier platform to distribute them for you, avoiding version mismatch problems.
<!--
**
** Status
**
-->
<command name="Status" description="Status command." command-type="Command" is-static="true">
<execution-string>${module.dir}/bin/status.sh</execution-string>
<argument-string>-port ${opts.port}</argument-string>
<opts>
<opt parameter="port" description="tomcat listing port" required="false"
property="opts.port" type="string" defaultproperty="entity.attribute.port"/>
</opts>
</command>
In this example, it is assumed the user copied an existing script named status.sh into the bin/ subdirectory of the module source. The ${module.dir}/bin path represents the module directory's bin/ path after the module has been installed.
Similar to the previous examples, an attribute is used to default a command line option.
4. Build the the module
The last step before you can use your commands is to build your module and upload them to the repository. Use the build-type command in ProjectBuilder and specify your module name and the -upload flag.
ctl -p default -m ProjectBuilder -c build-type -- -type tomcatutil -upload -deploy
The -deploy flag will deploy the module into your current CTL base so that you can immediately execute its commands. If you need to modify your commands or scripts, do so and re-run the build-type command to rebuild the module.
Execute your control procedures
CLI execution
To run a command from your utility you'll use the following form:
ctl -p project -m tomcatutil -c command
To run the "Status" command on the local host run:
ctl -p default -m tomcatutil -c Status
...or to run the "Status" command on nodes tagged "tomcats" in the project named "default" run:
ctl -I tags=tomcats -p default -m tomcatutil -c Status
GUI execution
You can execute the command shown above using JobCenter GUI. Go to the JobCenter webapp. Click the "Create a new job" button. Choose your project and then press the "Execute a Defined Command" radio button. Scroll down the list box and locate the "tomcatutil" type. This will load the commands list box. Scroll down and click the desired command (eg "Status").
Configure the node dispatch option (perhaps speciying tags=tomcats) and then press "Run". Of course, you can save this job to run at a later time, if desired.


