Service control
Overview
Some application deployments have long running processes that have a runtime state. Examples include web servers, application servers, databases, messaging services, as well as custom developed proprietary services. Each of these deployments has a set of procedures for start up and shutdown. Some also have procedures to check the state of the long running process. Yet others have procedures to query the service to obtain diagnostics or performance data.
Service, one of the standard modules of the ControlTier base library, provides a standard set of commands that model the lifecycle of a deployment that has long running processes. Service establishes an interface for managing start up, shutdown and status checking of long running processes.
Hook commands
The Service type declares four commands responsible for controlling the runtime state of a long running service. These commands are actually hooks, that can be configured to call one of your scripts.
Additionally, these hook commands are actually called by higher level command workflows that use the hook commands as primitive operations. The table below describes each hook command and notes the workflow command that uses it.
| Name | Description | Workflow |
|---|---|---|
| assertServiceIsUp | Check if service is running. Exit 0 if running. Exit non-zero otherwise. | Start, Status |
| assertServiceIsDown | Check if service is not running. Exit 0 if down. Exit non-zero otherwise. | Stop |
| startService | Start the service. Exit 0 if successful. Exit non-zero otherwise. | Start |
| stopService | Stop the service. Exit 0 if successful. Exit non-zero otherwise. | Stop |
The one vital assumption the workflows make of your scripts is how they return exit codes. Your scripts should exit 0 when successful and non-zero (e.g., exit code 1) when not successful.
Hooking in your scripts
You can attach your scripts to a Service by registering them as a setting of your Service object. The XML file below describes a Service named "tomcat1" and four scripts (hypothetically) residing in $HOME/bin.
Each script is registered as a setting that exports an attribute used by the Service commands to look up the location of your script.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE project PUBLIC "-//ControlTier Software Inc.//DTD Project Document 1.0//EN"
"project.dtd">
<project>
<!--
**
** Defines path to the Service control scripts metadata
**
-->
<setting type="ServiceStartScript" name="tomcat1"
description="The script used by startService"
settingValue="$HOME/bin/tomcat-start.sh" settingType="script"/>
<setting type="ServiceStopScript" name="tomcat1"
description="The script used by stopService"
settingValue="$HOME/bin/tomcat-stop.sh" settingType="script"/>
<setting type="ServiceIsUpScript" name="tomcat1"
description="The script used by assertServicesIsUp"
settingValue="$HOME/bin/tomcat-isup.sh" settingType="script"/>
<setting type="ServiceIsDownScript" name="tomcat1"
description="The script used by assertServicesIsDown"
settingValue="$HOME/bin/tomcat-isdown.sh" settingType="script"/>
<!--
**
** Defines the Service metadata
**
-->
<deployment
type="Service"
name="tomcat1"
description="The Tomcat deployment."
installRoot="/demo/apache-tomcat-5.5.26"
basedir="/demo/apache-tomcat-5.5.26">
<!--
**
** References the scripts to be run by the Service lifecycle commands
**
-->
<resources>
<resource name="tomcat1" type="ServiceStopScript" />
<resource name="tomcat1" type="ServiceStartScript" />
<resource name="tomcat1" type="ServiceIsDownScript" />
<resource name="tomcat1" type="ServiceIsUpScript" />
</resources>
</deployment>
</project>
The bold text represents your procedure defined inside a script. The italicized text shows a convention that uses the Service's name ("tomcat1") as a the setting object's name. This acts as a visual key to help show the association between them.

The graphic above describes the mapping of Service object to the settings that represent the four scripts.
Making your own service controllers
The Service object establishes a working set of commands that govern the runtime state of application deployments with long running processes. Service provides a ready means to hook your scripts into that life cycle control structure. There are times though where you may wish to create your own service controller.
For example, sometimes it is advantageous to package your scripts so control operations are self-sufficient (eg., to not rely on external scripts being present, they may be missing or they may be the wrong version).
You might also prefer to implement your procedures directly inside CTL, or you might even want to invent your own life cycle control structure. In addition, you might want to establish a set of defaults that fit with the assumptions of your local environment.
CTL modules are the vehicle for packaging your scripts, defining defaults, and customizing the Service control lifecycle commands. Writing a module is a simple process facilitated by ProjectBuilder. Each procedure will be formalized as a command in a module. You can declare and assign defaults in the form of attributes of your new module type.
Make a new Service control module
1. Create a new module
Module creation is done via the "ProjectBuilder" module's create-type command. For this example, a module called "TomcatController" is created to control the runtime state of Tomcat. TomcatController will be a subtype of Service and inherit the default control commands.
ctl -p default -m ProjectBuilder -c create-type -- -supertype Service -type TomcatController
After running create-type, the initial source code for the new module is generated. In this example, the source is found at: $CTL_BASE/src/modules/TomcatController.
Modules have a standard directory structure with standard subdirectories where you can store files.
module_name
|
|--- type.xml // file containing module definition
|
+--- bin/ // location to store your control scripts.
|
+--- commands/ // contains generated command files
|
+--- lib/ // optional resource files used by your commands
By convention, the bin directory is where scripts called by service control commands can be located. In the lib subdirectory, ancillary files like templates, or data files can be stored.
2. Establishing 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/TomcatController/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.
The first exmple shows how to package existing scripts in a module. This makes the module self sufficent and avoids problems due dependencies on external scripts (eg, they are missing, out of date).
Imagine a set of existing scripts existed: status.sh, start.sh, isup.sh, isdown.sh. These would be copied to the bin/ subdirectory of the module source (e.g, $CTL_BASE/src/modules/TomcatController/bin). These scripts can then be referred to as ${module.dir}/bin/script-name. The ${module.dir}/bin path represents the module directory's bin/ path after the module has been installed.
You then define defaults for the module establishing the new script locations. This will simplify setup of the new module, later. The attribute-default definitions below map each script to their appropriate attribute:
<attributes>
<attribute-default name="service-start-script"
value="${module.dir}/bin/start.sh">
<doc>script to execute for startService command</doc>
</attribute-default>
<attribute-default name="service-stop-script"
value="${module.dir}/bin/stop.sh">
<doc>script to execute for stopService command</doc>
</attribute-default>
<attribute-default name="service-isup-script"
value="${module.dir}/bin/isup.sh">
<doc>script to execute for assertServiceIsUp command</doc>
</attribute-default>
<attribute-default name="service-isdown-script"
value="${module.dir}/bin/isdown.sh">
<doc>script to execute for assertServiceIsDown command</doc>
</attribute-default>
...
</attributes>
The bold text shows each of the scripts relative to their location in the module. No assumption is made about what these scripts are called.
So far two sets of attributes have been defined. One set defined a set of settings describing configuration aspects of the Tomcat server (step#2). The second set defined a set of default locations for the scripts after they were copied to the module. The next example shows how a command definition can make use of both.
Passing arguments to your scripts
Your procedures might need parameters of their own. The example below shows how to override the standard Service's implementation to define a new option that references one of the new attributes defined earlier as an argument value:
<commands>
...
<!--
**
** assertServiceIsUp
**
-->
<command name="assertServiceIsUp" description="assertServiceIsUp command."
command-type="Command" is-static="true">
<execution-string>${module.dir}/bin/isup.sh</execution-string>
<argument-string>-port ${opts.port}</argument-string>
<!--
**
** define an option called -port and default it to the attribute:
**
-->
<opts>
<opt parameter="port" description="tomcat listing port" required="false"
property="opts.port" type="string" defaultproperty="entity.attribute.port"/>
</opts>
</command>
</commands>
You can see the entity.attribute.port property is used as the default for the -port option.
Setting environment variables with command options
The next set of examples show how to overide Service command definitions to allow the setting of environment variables. These environment variables are set with values passed in by the command. Additionally, the option is defaulted to an attribute value.
<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};
${module.dir}/bin/stop.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};
${module.dir}/bin/start.sh
]]>
</argument-string>
<opts>
<opt parameter="basedir" description="catalina home" required="false"
property="opts.basedir" type="string" defaultproperty="entity.attribute.catalinaHome"/>
</opts>
</command>
</commands>
The example above, defined two procedures as commands named "stopService" and "startService". Each of these commands calls a script located inside the module. The -basedir option has been added and its initial value is set to the attribute default, catalinaHome. Two environment variables, CATALINA_BASE and CATALINA_HOME, are also set to the value of the catalinaHome attribute.
The examples in this section were confined to passing arguments and setting environment variables. Of course, you could have radically changed these command definitions. CTL allows you to define commands in many different ways using different implementation languages. The important thing is to preserve the semantics of the commands you override, since higher level workflows rely on them.
4. Build the the module
The last step before you can use your module is to build and upload it 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 TomcatController -upload
The new module is ready for use.
Preparations for use
To begin using the TomcatController type, an instance of it must be defined. TomcatController objects can then be mapped to the Nodes on which they will be deployed.
Because attributes were used to default command options to reasonable defaults, the definition of a new service object is very simple in XML using a deployment tag:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE project PUBLIC "-//ControlTier Software Inc.//DTD Project Document 1.0//EN"
"project.dtd">
<project>
<!--
**
** Defines a TomcatController object named "tomcat1"
**
-->
<deployment
type="TomcatController"
name="tomcat1"
description="The Tomcat deployment."
installRoot="/demo/apache-tomcat-5.5.26"
basedir="/demo/apache-tomcat-5.5.26" >
<!--
**
** References the localhost node where the Tomcat server will run
**
-->
<referrers replace="false">
<resource type="Node" name="localhost"/>
</referrers>
</deployment>
</project>
Notice because attributes were used to default the location of the scripts (now also inside the module) no additional settings were required to define their locations.
In this case, the script locations won't change but it is possible that some of the Tomcat configuration settings might change. Maybe Tomcat server port is different in QA.
This example also shows the "tomcat1" server will run on the Node "localhost".
The XML example below shows the definition of a TomcatController named "qaTomcatServer" that has its own port setting.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE project PUBLIC "-//ControlTier Software Inc.//DTD Project Document 1.0//EN"
"project.dtd">
<project>
<!--
**
** Defines a setting different server port value for QA
**
-->
<setting type="Port" name="qaTomcatServer"
description="The port used by the QA tomcat server"
settingValue="18080" settingType="http"/>
<!--
**
** Defines a deployment resource for TomcatController named "qaTomcatServer"
**
-->
<deployment
type="TomcatController"
name="qaTomcatServer"
description="The Tomcat deployment."
installRoot="/demo/apache-tomcat-5.5.26"
basedir="/demo/apache-tomcat-5.5.26" >
<!--
**
** References the QA port setting.
**
-->
<resources>
<resource name="qaTomcatServer" type="Port" />
</resources>
<!--
**
** References the QA node where the Tomcat server will run
**
-->
<referrers replace="false">
<resource type="Node" name="qa-app1"/>
</referrers>
</deployment>
</project>
Following a convention described earlier the TomcatController object name is used to key to the Port setting for visual aid. This example also shows the Tomcat server will run on the Node "qa-app1".
These object definitions can be loaded via ProjectBuilder using the load-objects command:
ctl -p project -m ProjectBuilder -c load-objects -- -filename /path/to/metadata.xml
With the objects registered to the repository, the last step is to prepare CTL via ctl-depot.
ctl-exec -p project -- ctl-depot -p project -a install
Execute the service control commands
CLI execution
To run a command from your utility you'll use the following form:
ctl -p project -t TomcatController -o name -c command
To run the "assertServiceIsUp" command for the "tomcat1" Tomcat server on the local host run:
ctl -p default -t TomcatController -o tomcat1 -c assertServiceIsUp
...or to run the "assertServiceIsUp" command on nodes tagged "tomcats" in the project named "default" run:
ctl -I tags=tomcats -p default -t TomcatController -o tomcat1 -c assertServiceIsUp
You might recall that assertServiceIsUp command is actually used by higher level workflow commands. You can call assertServiceIsUp via the Status command like so:
ctl -p default -t TomcatController -o tomcat1 -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 "TomcatController" type. This will load the objects list box. Scroll down and click the desired object (eg "tomcat1").
Choose the desired command (eg "assertServiceIsUp")
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.


