Custom Entities

So far we’ve covered how to configure and compose entities. There’s a large library of blueprints available, but there are also times when you’ll want to write your own.

For complex use cases, you can write JVM, but for many common situations, some of the highly-configurable blueprints make it easy to write in YAML, including bash and Chef.

Vanilla Software using bash

The following blueprint shows how a simple script can be embedded in the YAML (the | character is special YAML which makes it easier to insert multi-line text):

name: Simple Netcat Server Example
location: localhost
services:
- type: brooklyn.entity.basic.VanillaSoftwareProcess
  name: Simple Netcat Server
  launch.command: |
    echo hello | nc -l 4321 &
    echo $! > $PID_FILE

  # The following overrides demonstrate the use of a custom shell environment as well as
  # check-running and stop commands. These are optional; default behavior will "do the
  # right thing" with the pid file automatically.

  env:                  { CHECK_MARKER: "checkRunning", STOP_MARKER: "stop" }
  checkRunning.command: echo $CHECK_MARKER >> DATE && test -f "$PID_FILE" && ps -p `cat $PID_FILE` >/dev/null
  stop.command:         echo $STOP_MARKER  >> DATE && test -f "$PID_FILE" && { kill -9 `cat $PID_FILE`; rm /tmp/vanilla.pid; }

# can also define download.url, in which case the launch command defaults to ./start.sh in that (archive) file

This starts a simple nc listener on port 4321 which will respond hello to the first session which connects to it. Test it by running telnet localhost 4321.

This is just a simple script, but it shows how any script can be easily embedded here, including a script to download and run other artifacts. Many artifacts are already packaged such that they can be downloaded and launched with a simple script, and VanillaSoftwareProcess can also be used for them. We can specify a download.url which downloads artifacts (unpacking TAR, TGZ, and ZIP archives) before running launch.command relative to where that file is installed (or unpacked), with ./start.sh being the default launch.command.

So if we create a file /tmp/netcat-server.tgz containing just start.sh in the root which consists of the two lines in the previous example, we can instead write our example as:

name: Simple Netcat Example From File
location: localhost
services:
- type: brooklyn.entity.basic.VanillaSoftwareProcess
  name: Simple Netcat Server
  download.url: file:///tmp/netcat-server.tgz

The one requirement of the script is that it store the process ID (PID) in the file pointed to by $PID_FILE, hence the second line of the script. This is because Brooklyn wants to monitor the services under management. (There are other options; you can set checkRunning.command and stop.command instead, as documented on the Javadoc of the VanillaSoftwareProcess class, and those scripts will be used instead of checking and stopping the process whose PID is in $PID_FILE.)

And indeed, once you’ve run one telnet to the server, you’ll see that the service has gone “on fire” in Brooklyn – because the nc process has stopped. Besides detecting this failure, Brooklyn policies can be added to the YAML to take appropriate action. A simple recovery here might just be to restart the process:

name: Simple Netcat Example with Restarter Policy
location: localhost
services:
- type: brooklyn.entity.basic.VanillaSoftwareProcess
  id: netcat-server
  name: Simple Netcat Server
  launch.command: |
    echo hello | nc -l 4321 &
    echo $! > $PID_FILE
  brooklyn.enrichers:
  - type: brooklyn.policy.ha.ServiceFailureDetector
    brooklyn.config:
      # wait 15s after service fails before propagating failure
      serviceFailedStabilizationDelay: 15s
  brooklyn.policies:
  - type: brooklyn.policy.ha.ServiceRestarter
    brooklyn.config:
      # repeated failures in a time window can cause the restarter to abort,
      # propagating the failure; a time window of 0 will mean it always restarts!
      failOnRecurringFailuresInThisDuration: 0

Autonomic management in Brooklyn often follows the principle that complex behaviours emerge from composing simple policies. The blueprint above uses one policy to triggering a failure sensor when the service is down, and another responds to such failures by restarting the service. This makes it easy to configure various aspects, such as to delay to see if the service itself recovers (which here we’ve set to 15 seconds) or to bail out on multiple failures within a time window (which again we are not doing). Running with this blueprint, you’ll see that the service shows as on fire for 15s after a telnet, before the policy restarts it.

For an even more interesting way to test it, look at the blueprint defining a netcat server and client. This uses initializers to define an effector to sayHiNetcat on the Simple Pinger client, using env variables to inject the netcat-server location and parameters to pass in per-effector data:

  env:
    TARGET_HOSTNAME: $brooklyn:component("netcat-server").attributeWhenReady("host.name")
  brooklyn.initializers:
  - type: brooklyn.entity.software.ssh.SshCommandEffector
    brooklyn.config:
      name: sayHiNetcat
      description: Echo a small hello string to the netcat entity
      command: |
        echo $message | nc $TARGET_HOSTNAME 4321
      parameters:
        message:
          description: The string to pass to netcat
          defaultValue: hi netcat

This blueprint also uses initializers to define sensors on the netcat-server entity so that the $message we passed above gets logged and reported back:

  launch.command: |
    echo hello | nc -l 4321 >> server-input &
    echo $! > $PID_FILE
  brooklyn.initializers:
  - type: brooklyn.entity.software.ssh.SshCommandSensor
    brooklyn.config:
      name: output.last
      command: tail -1 server-input

This is still a simple example, but worth going through carefully. It shows many of the building blocks used in real-world blueprints, and how they can often be easily described and combined in Brooklyn YAML blueprints. Next, if you need to drive off-piste, or you want to write tests against these blueprints, have a look at, for example, VanillaBashNetcatYamlTest.java in the Brooklyn codebase, or follow the other references below.