Mock Unix Service Example

From ControlTier

Jump to: navigation, search

Contents

Overview

Requires Version 3.4.3 (help?)

This Mock Unix Service example shows the basics of using an object of the Service type to manage the runtime lifecycle of a long running application Service. The Service type defines several commands useful for managing the startup and shutdown cycle of a software service.

This example shows you how to do the following things:

  1. Use the Start, Stop, and Status lifecycle commands
  2. customize the implementation of these commands using your shell scripts to perform the logic
  3. use the project.xml resource model format to define a Service object

The example provides a working set of scripts to implement the logic to manage a "mock", or simulated, application process, approximating a typical scenario where a system process is launched and controlled via a process identification file (PID file).

The lifecycle commands Start and Stop work in a similar way. Each one runs another command designed to "assert" that the run-state of the process is as expected. For example, the Start command runs assertServiceIsUp. This "assert" command does merely does one thing: it checks that the service is in the state specified (running or not running), and if it doesn't meet that expectation, it fails. If it succeeds, then the Start/Stop command knows it doesn't have to do anything else, and the entire command succeeds. However, if the "assert" command fails, then the Start/Stop command has to change the state of the service. It does this by subsequently invoking the startService or stopService command. These commands are meant to perform whatever action is necessary to start or stop the underlying service. If this command succeeds, then the Start/Stop command has succeeded, otherwise it has failed.

This mechanism separates the logic of managing the run-state of a service into discrete tasks that have clear semantics, allowing this abstract pattern to apply to the wide range of software services that need to be managed.

This example will use a set of simple shell scripts to plug in to each of these discrete tasks, allowing us to demonstrate the behavior of the lifecycle commands while only simulating an actual running service. You will see that the implementation of each of these tasks is simple and easy to customize for your own service management needs.

Dependencies

This demo has these dependencies.

  • ControlTier — 3.4.3
  • Unix
    • This example is not compatible with Windows. It is compatible with Linux, Mac OS X and most unix variants.

Building the Example

Follow the instructions in this section to setup the example code into your environment.

Note: Don't worry about what these commands do, as they just bootstrap the example code to work in your environment and to pre-load the resource model for you. (For complete detail about how to use the Examples see Using the Examples):

Execute these bootstrap steps:

  1. cd $CTIER_ROOT/examples/mock-unix-service
    • At the command line, navigate to the examples/mock-unix-service directory under your $CTIER_ROOT directory.
  2. ctl -p demo -m ProjectBuilder -c Register -- -xml projectbuilder.xml -install
    • This loads a ProjectBuilder object definition into the ControlTier Server.
  3. ctl -p demo -t ProjectBuilder -o mock-unix-service -c Build
    • Builds a working example based on template files and your working environment. Later see Further Customization

You are now ready to run the examples.

Running the Example

You can run any of the Service commands like so: ctl -p demo -t Service -o mock -c <command-name>

If you run the command without the "-c <command-name>" parameter you will see a listing of commands.

In the commands below, we will show the output of the command first, then an explanation of what occurred.

Run Start

The Start command ensures the service is started. When you execute it you should see that it in turn executes the assertServiceIsUp command.

execute:

ctl -p demo -t Service -o mock -c Start

output:

begin workflow command (1/1) -> "assertServiceIsUp " ...
Executing bourne shell script: /Users/greg/ctier2/examples/mock-unix-service/scripts/isup.sh
DOWN
Running handler command: startService
Executing bourne shell script: /Users/greg/ctier2/examples/mock-unix-service/scripts/start.sh
end workflow command (1/1) -> "assertServiceIsUp "

What happened?

The Start command invokes the startup sequence for the Service:

  1. the assertServiceIsUp command is invoked:
    • begin workflow command (1/1) -> "assertServiceIsUp " ...
  2. The assertServiceIsUp invokes the script defined as the "isup" script, named isup.sh
    • Executing bourne shell script: /Users/greg/ctier2/examples/mock-unix-service/scripts/isup.sh
  3. The isup.sh script fails because the service is not started, and it reports that the service is down:
    • DOWN
  4. Since the isup script failed, the Start command next invokes the startService:
    • Running handler command: startService
  5. The startService command invokes the script defined as the "start" script, named start.sh
    • Executing bourne shell script: /Users/greg/ctier2/examples/mock-unix-service/scripts/start.sh
  6. Finally, the assertServiceIsUp succeeds because the startService command succeeded.
    • end workflow command (1/1) -> "assertServiceIsUp "

In this "mock" service, the start.sh script merely creates the PID file to simulate that the process was started. You should now see a PID file at $CTL_BASE/var/tmp/mock.pid:

ls $CTL_BASE/var/tmp/mock.pid
/Users/greg/ctier2/ctl/var/tmp/mock.pid

Run Status

The Status command checks if the service is up and running. When it is executed, it invokes the assertServiceIsUp command, which succeeds or fails when it finds that the underlying service is up or down.

execute:

ctl -p demo -t Service -o mock -c Status

output:

begin workflow command (1/1) -> "assertServiceIsUp " ...
Executing bourne shell script: /Users/greg/ctier2/examples/mock-unix-service/scripts/isup.sh
UP
end workflow command (1/1) -> "assertServiceIsUp "

What happened?

Just as before, when running Start, the assertServiceIsUp command runs the script that was defined for it named isup.sh

  1. The assertServiceIsUp invokes the script defined as the "isup" script, named isup.sh
    • Executing bourne shell script: /Users/greg/ctier2/examples/mock-unix-service/scripts/isup.sh
  2. The isup.sh script succeeds because the PID file now exists, and it reports that the service is UP:
    • UP
  3. Finally the Status command succeeds because the assertServiceIsUp command succeeds:
    • end workflow command (1/1) -> "assertServiceIsUp "

Since the PID file for the mock service exists, the isup.sh script succeeds, and the Status command succeeds.

Run Stop

The Stop command ensures the service is down. This command is essentially a mirror image of the Start command, running the assertServiceIsDown command instead of assertServiceIsUp.

execute:

ctl -p demo -t Service -o mock -c Stop

output:

begin workflow command (1/1) -> "assertServiceIsDown " ...
Executing bourne shell script: /Users/greg/ctier2/examples/mock-unix-service/scripts/isdown.sh
UP
Running handler command: stopService
Executing bourne shell script: /Users/greg/ctier2/examples/mock-unix-service/scripts/stop.sh
end workflow command (1/1) -> "assertServiceIsDown "

What happened?

The Stop command invokes the shutdown sequence for the Service:

  1. the assertServiceIsDown command is invoked:
    • begin workflow command (1/1) -> "assertServiceIsDown " ...
  2. The assertServiceIsDown invokes the script defined as the "isdown" script, named isdown.sh
    • Executing bourne shell script: /Users/greg/ctier2/examples/mock-unix-service/scripts/isdown.sh
  3. The isdown.sh script fails because the service is already started, and it reports that the service is UP:
    • UP
  4. Since the isdown.sh script failed, the Stop command next invokes the stopService:
    • Running handler command: stopService
  5. The stopService command invokes the script defined as the "stop" script, named stop.sh
    • Executing bourne shell script: /Users/greg/ctier2/examples/mock-unix-service/scripts/stop.sh
  6. Finally, the assertServiceIsDown succeeds because the stopService command succeeded.
    • end workflow command (1/1) -> "assertServiceIsDown "

The stop.sh script invoked by the stopService command removes the PID file that this mock service example uses to indicate the running process, so you should see that the PID file no longer exists:

ls $CTL_BASE/var/tmp/mock.pid
ls: /Users/greg/ctier2/ctl/var/tmp/mock.pid: No such file or directory

How it Works

From Workbench's "Service Manager" page you can see the mock Service's resource model in a graphical representation:

Image:mock-service-screenshot.png

You should see the Service and a set of Setting child dependencies, and that the Service is deployed to one Node.

The mock Service resource model is defined in a project XML file generated into the $CTIER_ROOT/examples/mock-unix-service. directory. This section walks through the XML file used to define the Setting and Service objects.

Examine the contents of this file to see the full resource model definition excerpted below:

$CTIER_ROOT/examples/mock-unix-service/default-object.xml

The Setting definitions

The Service type defines a set of standard workflows to drive the startup and shutdown processes. Each of the discrete management tasks can be customized by defining a Setting child dependency of a specific type for the Service.

UML model

The diagram below describes the resource model for a Service deployed to one Node and configured to use several script settings. These settings specify a particular script to call via the related Service command.

Image:mock-service-example.png

When a Service life cycle command is called, the appropriate script is looked up and then invoked. The table below shows each command has a particular setting type where you declare the path to the script.

Setting Command Example Script
ServiceStartScript startService start.sh
ServiceStopScript stopService stop.sh
ServiceIsUpScript assertServiceIsUp isup.sh
ServiceIsDownScript assertServiceIsDown isdown.sh

Below is the XML used to define these settings for the Service's resource model. The setting tag is used to define each setting and the corresponding script location. Notice that the type= attribute declares the appropriate Setting type, the name attributes all match the name of the Service we will define, and that the settingValue is the full path of the appropriate script for that management task.

<setting type="ServiceIsUpScript" name="mock" 
	   description="The script used by assertServicesIsUp" 
	   settingValue="${env.CTIER_ROOT}/examples/mock-unix-service/scripts/isup.sh" settingType="script"/>
<setting type="ServiceIsDownScript" name="mock" 
	   description="The script used by assertServicesIsDown" 
	   settingValue="${env.CTIER_ROOT}/examples/mock-unix-service/scripts/isdown.sh" settingType="script"/>  
<setting type="ServiceStopScript" name="mock" 
	   description="The script used by stopService" 
	   settingValue="${env.CTIER_ROOT}/examples/mock-unix-service/scripts/stop.sh" settingType="script"/>  
<setting type="ServiceStartScript" name="mock" 
	   description="The script used by StartService" 
	   settingValue="${env.CTIER_ROOT}/examples/mock-unix-service/scripts/start.sh" settingType="script"/>

Each of the settings have use the same name (e.g,name="mock") to make it more obvious that these settings are associated with the Service "mock", defined below. This is a popular naming convention.

The Service definition

The Service is defined by a deployment tag. This element declares the name, type, and some paths (installRoot, basedir). (These paths are often useful for defining a location to install the software components or other file resources for the Service object, but in this example they are not used.)

The settings are referenced in the resources element. The Node where this Service is to be deployed is referenced in the referrers element.

The Settings (described above) and the Node are reference using a Resource Reference element, which identifies the appropriate object via the name and type attributes.

(Also note: if packages would be installed during deployment, they would be included in the <resources> as well. You can see an example with the Service Package Deployment Example).

<deployment 
    type="Service"
    name="mock" 
    description="A mock Service." 
    installRoot="${env.CTIER_ROOT}/examples/mock-unix-service" 
    basedir="${env.CTIER_ROOT}/examples/mock-unix-service" 
    startuprank="1">
 
  <resources>
    <!--
	  **
	  ** Service cycle scripts
	  **
    -->
    <resource name="mock" type="ServiceIsDownScript" />
    <resource name="mock" type="ServiceIsUpScript" />
    <resource name="mock" type="ServiceStartScript" />
    <resource name="mock" type="ServiceStopScript" />
  </resources>
 
  <!--
	**
	** Define a parent dependency to the node where you are running this example. 
	**
  -->
  <referrers replace="false">
    <!--
	  ** Change this line to one that matches your node name:
    -->
    <resource type="Node" name="localhost"/>
  </referrers>
</deployment>

Command dispatching

The startup and shutdown processes are managed via two Service workflow commands: Start and Stop. Both workflows share a common design. Each check for a condition and if the condition is not met, an action is performed. Let's look at each of these workflows.

Service provides an implementation supporting a generic start up procedure that first checks if the service is up and if not, to then run a command to start the service.

Image:Idempotent-start.png

You can see in the diagram that Start calls assertServiceIsUp first. If it fails because the service is not running, then startService is executed.

Service also provides a program skeleton supporting a generic shutdown procedure that first checks if the service is down and if not, to then run a command to stop the service.

Image:Idempotent-stop.png

You can see in the diagram that Stop calls assertServiceIsDown first. If it fails because the service is running, then stopService is executed.

Underlying these Service commands are the scripts that were configured via the setting definitions discussed above. All the service life cycle commands that call scripts assume a basic convention. If the script exits with a non-zero exit code, the command will fail otherwise it's interpreted as successful.

The scripts called by our simple mock Service are trivial but are representative of how you can incorporate your own procedures via scripts attached to Service commands. Let's look at each of them in turn.

The "isdown.sh" script is called by assertServiceIsDown. This command checks for the condition of the service is not up. In this case, the isdown.sh script checks for the non-existence of the mock.pid file. If it exists, the script exits with a non-zero exit (ie, "exit 1").

File listing: isdown.sh

#!/bin/sh
[ ! -f $CTL_BASE/var/tmp/mock.pid ] && { echo "DOWN" ; } || { echo "UP" ; exit 1 ; }

The "isup.sh" script is called by assertServiceIsUp. This command checks if the service is running. In this case, if the mock.pid file exists, the service is assumed running otherwise it exits with a non-zero exit code.

File listing: isup.sh

#!/bin/sh
[ -f $CTL_BASE/var/tmp/mock.pid ] && { echo "UP" ; } || { echo "DOWN" ; exit 1 ; }

The "start.sh" script is called by startService. The "start.sh" script simulates the service startup procedure. Normally, this script would launch a process. For this trivial implementation, it creates the "mock.pid" file.

File listing: start.sh

#!/bin/sh
touch $CTL_BASE/var/tmp/mock.pid

The "stop.sh" script is called by the stopService command. The "stop.sh" script simulates the service shutdown procedure. Normally, this script would stop the service process in some way. For this example, it simply removes the "mock.pid" file.

File listing: stop.sh

#!/bin/sh
rm $CTL_BASE/var/tmp/mock.pid

The Output

First let's look at the Start command.

ctl -p demo -t Service -o mock -c Start

You can see from the output the Start command is a workflow that calls assertServiceIsUp. That command is configured to call the script defined by the ServiceIsUpScript setting, "isup.sh". The first time Start is called the mock.pid file does not exist so isup.sh exits with a non-zero code causing the workflow to pass control to the error-handler. The error handler then runs startService which is configured via the ServiceStartScript setting to call "start.sh":

begin workflow command (1/1) -> "assertServiceIsUp " ...
Executing bourne shell script: /Users/alexh/ctier/examples/mock-unix-service/scripts/isup.sh
DOWN
Running handler command: startService
Executing bourne shell script: /Users/alexh/ctier/examples/mock-unix-service/scripts/start.sh
end workflow command (1/1) -> "assertServiceIsUp "

Next, let's look at the Stop command.

ctl -p demo -t Service -o mock -c Stop

It works very much like the Start command. You can see in the output the assertServiceIsDown command is called. That command is configured to call the script defined by ServiceIsDownScript setting, "isdown.sh". If the isdown.sh script finds an existing mock.pid file, then it exits with a non-zero code causing the workflow to pass control to the error-handler. The error handler runs "stopService" which is configured via the ServiceStopScript setting to call "stop.sh":

begin workflow command (1/1) -> "assertServiceIsDown " ...
Executing bourne shell script: /Users/alexh/ctier/examples/mock-unix-service/scripts/isdown.sh
UP
Running handler command: stopService
Executing bourne shell script: /Users/alexh/ctier/examples/mock-unix-service/scripts/stop.sh
end workflow command (1/1) -> "assertServiceIsDown "

You can manually create and remove the mock.pid file outside of these commands to and re run the individual commands.

Remove the pid file:

rm $CTL_BASE/var/tmp/mock.pid

Now run the assertServiceIsUp command. You will see it fail.

$ ctl -p demo -t Service -o mock -c assertServiceIsUp
Executing bourne shell script: /Users/alexh/ctier/examples/mock-unix-service/scripts/isup.sh
DOWN

Command failed: The following error occurred while executing this line:
/Users/alexh/ctier/ctl/depots/demo/modules/Service/lib/antlib.xml:50: exec returned: 1

Now touch the file.

touch $CTL_BASE/var/tmp/mock.pid

Run the assertServiceIsUp command again:

$ ctl -p demo -t Service -o mock -c assertServiceIsUp
Executing bourne shell script: /Users/alexh/ctier/examples/mock-unix-service/scripts/isup.sh
UP

The command completes successfully.

Related Topics

  • Mock Site Example explains how to manage a set of Services as a logically controlled group.
  • Service Package Deployment Example shows the deployment of an application server container and webapp along with startup/shutdown and configuration file customization.
Development