Let's start by discussing why workflow is introduced and where it can and should be used.
The Apache Brooklyn Workflow is designed to make it easy to describe behaviour of entities, effectors, sensors, and policies in blueprints. It has the sophistication of a programming language including conditions, loops, and error-handling and variables, but more important are the steps which delegate to other systems, such as containers or SSH or HTTP endpoints. Complex programming logic will typically be done in another system, such as a container; but where effectors and sensors need to interact with Brooklyn, such as reading and setting sensors, or invoking effectors, it is often simplest to do that directly in the blueprint.
Workflow in a blueprint has the benefit of being transparent to users and having the Brooklyn context easily available. It also has the advantage that Brooklyn will retry in certain situations. However it has the disadvantage that, although we've tried to make it as easy as we can for people to write, you are still writing code in YAML rather than in a first-class programming language.
The following are places that workflow can be used.
An effector can be defined using workflow and added to an entity as follows:
- type: some-entity brooklyn.initializers: - type: workflow-effector brooklyn.config: name: say-hi-and-publish-sensor steps: - log Hi - set-sensor boolean said_hi = true
This initializer will define the effector say-hi-and-publish-sensor
which uses workflow to do just that. The config to define the effector is:
name
: the name of the effector to define (required)parameters
: an optional map of parameters to advertise for the effector, keyed by the parameter name against the definition as a map including optionally type
, description
, and defaultValue
; see nested workflow for more detailsTo define the workflow, this requires:
steps
: to supply the list of steps defining the workflow (required)And the following optional common configuration keys are supported, with the same semantics as for individual steps as described under Common Step Properties:
condition
: an optional condition on the effector which if set and evaluating to false, prevents the effector workflow from running; this does not support interpolated variables
input
: a map of keys to values to make accessible in the workflow, in addition to effector parameters
output
: defines the output of the workflow, often referring to workflow variables or the output of the last step in the workflow
other common step properties such as timeout
, on-error
, and next
other workflow settings properties such as lock
, retention
, replayable
, and idempotent
A sensor feed to use a workflow to compute a sensor value based on triggers and/or a schedule can be defined as follows:
- type: some-entity brooklyn.initializers: - type: workflow-sensor brooklyn.config: sensor: count-how-often-other_sensor-is-published triggers: - other_sensor steps: - let integer x = ${entity.sensor.x} + 1 ?? 0 - return ${x}
This initializer will add the sensor to the entity's type signature, then run the indicated workflow, periodically and/or on sensors, and set the return value as the value of the sensor. The config to define the sensor feed is:
sensor
: (required) a string specifying the sensor name or map with keys name
(required) being the sensor name and optionally type
being a type to record for the sensortriggers
: a list of sensors which should trigger this when published, each entry declared either as the string name if on the local entity, or a map of sensor
containing the name and entity
containing the entity or entity ID where the sensor should be listened for, or if just a single local sensor, that sensor name supplied as a stringperiod
: whether the feed should run periodicallyskip_initial_run
: by default sensors and policies will run when created (if any condition
is met); this can be set true
to prevent that, ensuring it is only run after the initial period
or when one of the triggers
firesIn addition to defining the sensor
name, at least one of triggers
or period
must be supplied. The steps
must also be defined, as per workflow-effector
above, and the same common configuration is supported.
A policy to to run a workflow based on triggers and/or a schedule can be defined as follows:
- type: some-entity brooklyn.policies: - type: workflow-policy brooklyn.config: name: invoke-effector-other_sensor-is-published triggers: - other_sensor steps: - invoke-effector some_effector
This initializer will add the policy to the entity's defined management adjuncts, then run the indicated workflow, periodically and/or on sensors. The config to define the policy feed is:
name
: the name for the policy (recommended)triggers
and/or period
: per workflow-sensor
above (at least one required)The steps
must also be defined, as per above, and the same common configuration is supported.
A workflow can be made to run when an entity is created using a workflow-initializer
. This can do any custom entity setup, including the above tasks using add-policy
or apply-initializer
steps, as follows:
- type: some-entity brooklyn.initializers: - type: workflow-initializer brooklyn.config: name: initializer-to-say-hi-then-add-effector-and-sensor steps: - log Hi this workflow initializer will run at entity creation - step: add-policy blueprint: type: workflow-policy brooklyn.config: name: invoke-effector-other_sensor-is-published triggers: - other_sensor steps: - invoke-effector say-hi-and-publish-sensor - step: apply-initializer blueprint: type: workflow-effector brooklyn.config: name: say-hi-and-publish-sensor steps: - log Hi - set-sensor boolean said_hi = true
The workflow-initializer
takes the same config as workflow-effector
with the exception of parameters
.
New entities can be written to use workflow for their start and stop behavior by extending the type workflow-entity
. This requires a start
and stop
configuration to be supplied defining the workflow for those steps. Optionally restart
can be supplied, which if omitted will default to stopping then starting.
- type: workflow-entity brooklyn.config: start: steps: - log Starting up stop: steps: - log Stopping
The workflow-entity
will automatically set the service.isUp
and service.state
sensors based on invocation of start
and stop
and the success of the workflow.
It will also take into consideration the map sensors service.problems
and service.notUp.indicators
, where any entry in the former will cause service.state
to show as “on-fire” if it is meant to be running, and any entry in the latter will cause service.isUp
to become false (which will trigger an entry in service.problems
if it is meant to be running). Thus liveness and health checks can, and often should, be added, such as in the following getting the status_code
from the main.uri
, and setting a service.problems
if it is unavailable:
- type: workflow-entity brooklyn.config: start: steps: - ... # start the service, e.g. using container or http call - clear-sensor status_code - set-sensor main.uri = ... # get the URL from the previous; this will trigger sensor feed - step: wait ${entity.sensor.status_code} timeout: 5m stop: # omitted brooklyn.initializers: - type: workflow-sensor brooklyn.config: sensor: status_code period: 1m triggers: - main.uri steps: - step: http ${main.uri} on-error: - set-sensor service.problems['endpoint-live'] = ${error} - fail rethrow message Endpoint is not accessible - clear-sensor service.problems['endpoint-live'] - return ${status_code}
The workflow entity does not automatically start or stop children. If this is required, it should be part of the start/stop workflow, as follows:
- type: workflow-entity brooklyn.config: start: steps: - ... # start this # now start children - type: workflow target: children steps: - invoke-effector start stop: steps: # stop children first - type: workflow target: children steps: - invoke-effector stop - ... # then stop this
Entities that run software or another machine-based process can also be defined, relying on Apache Brooklyn to provision a machine if required, then using workflow for the install, customize, launch, check-running, and stop phases. These entities will provision a machine if if given a cloud or machine provisioning location, and use the same latches and pre/post phase on-box commands as the ofther SoftwareProcess
entities, but for the key phases it will take a workflow
object as for workflow-entity
.
The steps
will often run ssh
and possibly set-sensor
, and they can access the run dir and install dir using Freemarker ${entity.driver.installDir}
and ${entity.driver.runDir}
. The workflow
object can also define inputs and outputs for use locally, and on-error
handlers.
The checkRunning.workflow
should return true
or false
to indicate whether the software is running. Alternatively, by setting the entity config usePidFile: true
and in the launch command writing the process id (PID) to ${entity.driver.pidFile}
, Brooklyn's automatic PID detection can be used.
As an example:
- type: workflow-software-process brooklyn.config: install.workflow: steps: - ssh yum update - ssh yum install ... launch.workflow: steps: - let PORT = 4321 - 'ssh { echo hello | nc -l ${PORT} & } ; echo $! > /tmp/brooklyn-nc.pid' - 'set-sensor main.uri = http://localhost:${PORT}/' checkRunning.workflow: steps: - step: ssh ps -p `cat /tmp/brooklyn-nc.pid` timeout: 10s on-error: - return false - return true stop.workflow: steps: - ssh kill -9 `cat /tmp/brooklyn-nc.pid`