* This module was always a weird entity in the system having just one function that essentially creates a dynamic class. It was created just because we didn't understand where else to put this function. But now after the action provider refactoring we don't need it anymore. Action instantiation is now a responsibility of action descriptor classes. Change-Id: Ic4b6a9a7ca2784a892d2998359edb220ff8c8911
1743 lines
60 KiB
ReStructuredText
1743 lines
60 KiB
ReStructuredText
Mistral Workflow Language (v2)
|
||
==============================
|
||
|
||
Introduction
|
||
------------
|
||
|
||
This document fully describes Mistral Workflow Language version 2 of Mistral
|
||
Workflow Service. Since version 1 issued in May 2014 Mistral team completely
|
||
reworked the language pursuing the goal in mind to make it easier to understand
|
||
while more consistent and flexible.
|
||
|
||
Unlike Mistral Workflow Language v1, v2 assumes that all entities that Mistral
|
||
works with like workflows and actions are completely independent in terms of
|
||
how they're referenced and accessed through API (and also Python Client API and
|
||
CLI). Workbook, the entity that can combine workflows and actions still exists
|
||
in the language but only for namespacing and convenience purposes. See
|
||
`Workbooks section <#workbooks>`__ for more details.
|
||
|
||
**NOTE**: Mistral Workflow Language and API of version 1 has not been supported
|
||
since April 2015 and version 2 is now the only way to interact with Mistral
|
||
service.
|
||
|
||
Mistral Workflow Language consists of the following main object(entity) types
|
||
that will be described in details below:
|
||
|
||
- `Workflows <#workflows>`__
|
||
- `Actions <#actions>`__
|
||
|
||
Prerequisites
|
||
-------------
|
||
|
||
Mistral Workflow Language supports
|
||
`YAQL <https://pypi.org/project/yaql>`__ and
|
||
`Jinja2 <http://jinja.pocoo.org/docs/dev/>`__ expression languages to reference
|
||
workflow context variables and thereby implements passing data between workflow
|
||
tasks. It's also referred to as Data Flow mechanism. YAQL is a simple but
|
||
powerful query language that allows to extract needed information from JSON
|
||
structured data. Although Jinja2 is primarily a templating technology, Mistral
|
||
also uses it for evaluating expressions so users have a choice between YAQL and
|
||
Jinja2. It's also possible to combine both expression languages within one
|
||
workflow definition. The only limitation is that it's impossible to use both
|
||
types of expressions within one line. As long as there are YAQL and Jinja2
|
||
expressions on different lines of the workflow definition text, it is valid.
|
||
It is allowed to use YAQL/Jinja2 in the following sections of
|
||
Mistral Workflow Language:
|
||
|
||
- Workflow `'output' attribute <#common-workflow-attributes>`__
|
||
- Workflow `'task-defaults' attribute <#common-workflow-attributes>`__
|
||
- `Direct workflow <#direct-workflow>`__ transitions
|
||
- Task `'publish' attribute <#common-task-attributes>`__
|
||
- Task `'input' attribute <#common-task-attributes>`__
|
||
- Task `'with-items' attribute <#common-task-attributes>`__
|
||
- Task `'target' attribute <#common-task-attributes>`__
|
||
- Any attribute of `task policies <#policies>`__
|
||
- Action `'base-input' attribute <#attributes>`__
|
||
- Action `'output' attribute <#attributes>`__
|
||
|
||
Mistral Workflow Language is fully based on YAML and knowledge of YAML is a
|
||
plus for better understanding of the material in this specification. It also
|
||
takes advantage of supported query languages to define expressions in workflow
|
||
and action definitions.
|
||
|
||
- Yet Another Markup Language (YAML): http://yaml.org
|
||
- Yet Another Query Language (YAQL): https://pypi.org/project/yaql/1.0.0
|
||
- Jinja 2: http://jinja.pocoo.org/docs/dev/
|
||
|
||
Workflows
|
||
---------
|
||
|
||
Workflow is the main building block of Mistral Workflow Language, the reason
|
||
why the project exists. Workflow represents a process that can be described in
|
||
a various number of ways and that can do some job interesting to the end user.
|
||
Each workflow consists of tasks (at least one) describing what exact steps
|
||
should be made during workflow execution.
|
||
|
||
You should use '<% $.x %>' in YAQL or '{{ _.x }}' in Jinja expressions to get
|
||
access to the x variable in a data context of workflow execution.
|
||
|
||
YAML example
|
||
^^^^^^^^^^^^
|
||
::
|
||
|
||
---
|
||
version: '2.0'
|
||
|
||
create_vm:
|
||
description: Simple workflow example
|
||
|
||
input:
|
||
- vm_name
|
||
- image_ref
|
||
- flavor_ref
|
||
output:
|
||
vm_id: "{{ _.vm_id }}"
|
||
vm_status: <% $.vm_status %>
|
||
|
||
tasks:
|
||
create_server:
|
||
action: nova.servers_create name=<% $.vm_name %> image=<% $.image_ref %> flavor=<% $.flavor_ref %>
|
||
publish:
|
||
vm_id: <% task().result.id %>
|
||
on-success:
|
||
- wait_for_instance
|
||
|
||
wait_for_instance:
|
||
action: nova.servers_find id={{ _.vm_id }} status='ACTIVE'
|
||
retry:
|
||
delay: 5
|
||
count: 15
|
||
publish:
|
||
vm_status: "{{ task().result.status }}"
|
||
|
||
This example workflow simply sends a command to OpenStack Compute
|
||
service Nova to start creating a virtual machine and wait till it's
|
||
created using special "retry" policy.
|
||
|
||
Workflow types
|
||
^^^^^^^^^^^^^^
|
||
|
||
Mistral Workflow Language v2 introduces different workflow types and the
|
||
structure of each workflow type varies according to its semantics. Basically,
|
||
workflow type encapsulates workflow processing logic, a set of meta rules
|
||
defining how all workflows of this type should work. Currently, Mistral
|
||
provides two workflow types:
|
||
|
||
- `Direct workflow <#direct-workflow>`__
|
||
- `Reverse workflow <#reverse-workflow>`__
|
||
|
||
See corresponding sections for details.
|
||
|
||
Common workflow attributes
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
- **type** - Workflow type. Either 'direct' or 'reverse'. *Optional*. 'direct'
|
||
by default.
|
||
- **description** - Arbitrary text containing workflow description. *Optional*.
|
||
- **input** - List defining required input parameter names and
|
||
optionally their default values in a form "my_param: 123". *Optional*.
|
||
- **output** - Any data structure arbitrarily containing YAQL/Jinja2
|
||
expressions that defines workflow output. May be nested. *Optional*.
|
||
- **output-on-error** - Any data structure arbitrarily containing YAQL/Jinja2
|
||
expressions that defines output of a workflow to be returned if it goes into
|
||
error state. May be nested. *Optional*.
|
||
- **task-defaults** - Default settings for some of task attributes
|
||
defined at workflow level. *Optional*. Corresponding attribute
|
||
defined for a specific task always takes precedence. Specific task
|
||
attributes that could be defined in **task-defaults** are the
|
||
following:
|
||
|
||
- **on-error** - List of tasks which will run after the task has
|
||
completed with an error. For `direct
|
||
workflow <#direct-workflow>`__ only. *Optional*.
|
||
- **on-success** - List of tasks which will run after the task has
|
||
completed successfully. For `direct workflow <#direct-workflow>`__
|
||
only. *Optional*.
|
||
- **on-complete** - List of tasks which will run after the task has
|
||
completed regardless of whether it is successful or not. For
|
||
`direct workflow <#direct-workflow>`__ only. *Optional*.
|
||
- **requires** - List of tasks that a task depends on. For `reverse
|
||
workflow <#Reverse_Workflow>`__ only. *Optional*.
|
||
- **pause-before** - Configures pause-before policy. *Optional*.
|
||
- **wait-before** - Configures wait-before policy. *Optional*.
|
||
- **wait-after** - Configures wait-after policy. *Optional*.
|
||
- **fail-on** - Configures fail-on policy. *Optional*.
|
||
- **timeout** - Configures timeout policy. *Optional*.
|
||
- **retry** - Configures retry policy. *Optional*.
|
||
- **concurrency** - Configures concurrency policy. *Optional*.
|
||
- **safe-rerun** - Configures safe-rerun policy. *Optional*.
|
||
|
||
- **tasks** - Dictionary containing workflow tasks. See below for more
|
||
details. *Required*.
|
||
|
||
Tasks
|
||
^^^^^
|
||
|
||
Task is what a workflow consists of. It defines a specific computational
|
||
step in the workflow. When the workflow engine processes entities described
|
||
in the workflow text written in YAML it schedules tasks for execution.
|
||
Scheduling a task means that it's now eligible for execution and will be run
|
||
some time later. When exactly it will run depends on the system load and
|
||
configuration. Each task can optionally take input data and produce output.
|
||
In Mistral Workflow Language v2, task can be associated with an action or a
|
||
workflow. In the example below there are two tasks of different types:
|
||
|
||
::
|
||
|
||
action_based_task:
|
||
action: std.http url='openstack.org'
|
||
|
||
workflow_based_task:
|
||
workflow: backup_vm_workflow vm_id=<% $.vm_id %>
|
||
|
||
Actions will be explained below in an individual paragraph but looking
|
||
ahead it's worth saying that Mistral provides a lot of actions out of
|
||
the box (including actions for most of the core OpenStack services) and
|
||
it's also easy to plug new actions into Mistral.
|
||
|
||
Common task attributes
|
||
''''''''''''''''''''''
|
||
|
||
All Mistral tasks, regardless of workflow type, have the following common
|
||
attributes:
|
||
|
||
- **name** - Task name must not equal *noop*, *fail*, *succeed* or *pause*
|
||
. The max length is 255 symbols. For tasks with *join* control flow this
|
||
restriction is 208 symbols.
|
||
- **description** - Arbitrary text containing task description.
|
||
*Optional*.
|
||
- **action** - Name of the action associated with the task. Can be a static
|
||
value or an expression (for example, "{{ _.action_name }}").
|
||
*Mutually exclusive with* **workflow**. If neither action nor workflow are
|
||
provided then the action 'std.noop' will be used that does nothing.
|
||
- **workflow** - Name of the workflow associated with the task. Can be a static
|
||
value or an expression (for example, "{{ _.subworkflow_name }}").
|
||
*Mutually exclusive with* **action**.
|
||
- **input** - Actual input parameter values of the task's action or workflow.
|
||
*Optional*. Value of each parameter is a JSON-compliant type such as number,
|
||
string etc, dictionary or list. It can also be a YAQL/Jinja2 expression to
|
||
retrieve value from task context or any of the mentioned types
|
||
containing inline expressions (for example, string "<%
|
||
$.movie_name %> is a cool movie!") Can be an expression that evaluates to
|
||
a JSON object.
|
||
- **publish** - Dictionary of variables to publish to the workflow
|
||
context. Any JSON-compatible data structure optionally containing
|
||
expression to select precisely what needs to be published.
|
||
Published variables will be accessible for downstream tasks via using
|
||
expressions. **NOTE!** Mistral saves variables into a storage (context)
|
||
which is associated only with a branch. For example, the expression
|
||
“$.my_var” in the declaration of A1 will always evaluate to 1, for B1
|
||
it will always evaluate to 2. This does’t depend on the order in which
|
||
A and B will run. This is because we have two branches (A -> A1 and B
|
||
-> B1) for which the variable “my_var” has its own different version.
|
||
*Optional*.
|
||
|
||
::
|
||
|
||
version: '2.0'
|
||
wf:
|
||
tasks:
|
||
A:
|
||
action: std.noop
|
||
publish:
|
||
my_var: 1
|
||
on-success: A1
|
||
A1:
|
||
action: my_action param1=<% $.my_var %>
|
||
B:
|
||
action: std.noop
|
||
publish:
|
||
my_var: 2
|
||
on-success: B1
|
||
B1:
|
||
action: my_action param1=<% $.my_var %>
|
||
|
||
- **publish-on-error** - Same as **publish** but evaluated in case of
|
||
task execution failures. *Optional*
|
||
- **with-items** - If configured, it allows to run action or workflow
|
||
associated with a task multiple times on a provided list of items.
|
||
See `Processing collections using
|
||
'with-items' <#processing-collections>`__ for details. *Optional*.
|
||
- **keep-result** - Boolean value allowing to not store action results
|
||
after task completion (e.g. if they are large and not needed
|
||
afterwards). *Optional*. By default is 'true'.
|
||
- **target** - String parameter. It defines an executor to which a task
|
||
action should be sent to. Target here physically means the name of the
|
||
executor. The name of the executor can be defined with the "host"
|
||
property in the Mistral configuration file. If more than executors have
|
||
the same name then the task action will be sent only to one of them.
|
||
*Optional*.
|
||
- **pause-before** - Configures pause-before policy. *Optional*.
|
||
- **wait-before** - Configures wait-before policy. *Optional*.
|
||
- **wait-after** - Configures wait-after policy. *Optional*.
|
||
- **fail-on** - Configures fail-on policy. *Optional*.
|
||
- **timeout** - Configures timeout policy. *Optional*.
|
||
- **retry** - Configures retry policy. *Optional*.
|
||
- **concurrency** - Configures concurrency policy. *Optional*.
|
||
- **safe-rerun** - Boolean value allowing to rerun task if executor dies
|
||
during action execution. If set to 'true' task may be run twice.
|
||
*Optional*. By default set to 'false'.
|
||
|
||
workflow
|
||
''''''''
|
||
If a task has the attribute 'workflow' it synchronously starts a sub-workflow
|
||
with the given name.
|
||
|
||
Example of a static sub-workflow name:
|
||
|
||
::
|
||
|
||
my_task:
|
||
workflow: name_of_my_workflow
|
||
|
||
Example of a dynamic sub-workflow name:
|
||
|
||
::
|
||
|
||
---
|
||
version: '2.0'
|
||
|
||
framework:
|
||
input:
|
||
- magic_workflow_name: show_weather
|
||
|
||
tasks:
|
||
weather_data:
|
||
action: std.echo
|
||
input:
|
||
output:
|
||
location: wherever
|
||
temperature: "22C"
|
||
publish:
|
||
weather_data: <% task().result %>
|
||
on-success:
|
||
- do_magic
|
||
|
||
do_magic:
|
||
# Reference workflow by parameter.
|
||
workflow: <% $.magic_workflow_name %>
|
||
# Expand dictionary to input parameters.
|
||
input: <% $.weather_data %>
|
||
|
||
show_weather:
|
||
input:
|
||
- location
|
||
- temperature
|
||
|
||
tasks:
|
||
write_data:
|
||
action: std.echo
|
||
input:
|
||
output: "<% $.location %>: <% $.temperature %>"
|
||
|
||
In this example, we defined two workflows in one YAML snippet and the workflow
|
||
'framework' may call the workflow 'show_weather' if 'framework' receives the
|
||
corresponding workflow name through the input parameter 'magic_workflow_name'.
|
||
In this case it is set by default so a user doesn't need to pass anything
|
||
explicitly.
|
||
|
||
Note: Typical use for the dynamic sub-workflow selection is when parts of a
|
||
workflow can be customized. E.g. collect some weather data and then execute
|
||
some custom workflow on it.
|
||
|
||
|
||
Policies
|
||
''''''''
|
||
|
||
Any Mistral task regardless of its workflow type can optionally have
|
||
configured policies.
|
||
|
||
YAML example
|
||
|
||
::
|
||
|
||
my_task:
|
||
action: my_action
|
||
pause-before: true
|
||
wait-before: 2
|
||
wait-after: 4
|
||
fail-on: <% $.some_value < 4 %>
|
||
timeout: 30
|
||
retry:
|
||
count: 10
|
||
delay: 20
|
||
break-on: <% $.my_var = true %>
|
||
continue-on: <% $.my_var = false %>
|
||
|
||
**pause-before**
|
||
|
||
Defines whether Mistral Engine should put the workflow on hold or not
|
||
before starting a task.
|
||
|
||
|
||
**wait-before**
|
||
|
||
Defines a delay in seconds that Mistral Engine should wait before
|
||
starting a task.
|
||
|
||
|
||
**wait-after**
|
||
|
||
Defines a delay in seconds that Mistral Engine should wait after a task
|
||
has completed before starting next tasks defined in *on-success*,
|
||
*on-error* or *on-complete*.
|
||
|
||
|
||
**fail-on**
|
||
|
||
Defines a condition under which the task will fail, even if
|
||
the action was completed successfully.
|
||
|
||
|
||
**timeout**
|
||
|
||
Defines a period of time in seconds after which a task will be failed
|
||
automatically by engine if it hasn't completed.
|
||
|
||
|
||
**concurrency**
|
||
|
||
Defines a max number of actions running simultaneously in a task. *Applicable*
|
||
only for tasks that have *with-items*. If *concurrency* task property is not
|
||
set then actions (or workflows in case of nested workflows) of the task will
|
||
be scheduled for execution all at once.
|
||
|
||
|
||
**retry**
|
||
|
||
Defines a pattern how task should be repeated in case of an error.
|
||
|
||
- **count** - Defines a maximum number of times that a task can be
|
||
repeated.
|
||
- **delay** - Defines a delay in seconds between subsequent task
|
||
iterations.
|
||
- **break-on** - Defines an expression that will break iteration
|
||
loop if it evaluates to 'true'. If it fires then the task is
|
||
considered error.
|
||
- **continue-on** - Defines an expression that will continue iteration
|
||
loop if it evaluates to 'true'. If it fires then the task is
|
||
considered successful. If it evaluates to 'false' then policy will break the
|
||
iteration.
|
||
|
||
Retry policy can also be configured on a single line as:
|
||
|
||
::
|
||
|
||
task1:
|
||
action: my_action
|
||
retry: count=10 delay=5 break-on=<% $.foo = 'bar' %>
|
||
|
||
All parameter values for any policy can be defined as YAQL/Jinja2 expressions.
|
||
|
||
**NOTE:** It would be rare to use both break-on and continue-on in the same
|
||
retry block. *break-on* should be used when one expects the action to be in an
|
||
ERROR state for some amount of tries, but may eventually go to a SUCCESS state,
|
||
thereby stopping the loop. But if *break-on* is *'true'* then the retries will
|
||
stop and the task will be in ERROR. *continue-on* should be used if the action
|
||
will usually return *SUCCESS*, but the action has other results that can be
|
||
used to signal whether to continue the loop or not.
|
||
|
||
**NOTE**: Retry task policy doesn't work after the timeout policy is
|
||
triggered. You should use the *on-error* in case of direct workflow or task
|
||
rerun to re-execute a task.
|
||
|
||
Input syntax
|
||
''''''''''''
|
||
|
||
When describing a workflow task it's possible to specify its input
|
||
parameters in two ways:
|
||
|
||
Full syntax:
|
||
|
||
::
|
||
|
||
my_task:
|
||
action: std.http
|
||
input:
|
||
url: http://mywebsite.org
|
||
method: GET
|
||
|
||
Simplified syntax:
|
||
|
||
::
|
||
|
||
my_task:
|
||
action: std.http url="http://mywebsite.org" method="GET"
|
||
|
||
Syntax with dynamic input parameter map:
|
||
|
||
::
|
||
|
||
---
|
||
version: '2.0'
|
||
|
||
example_workflow:
|
||
input:
|
||
- http_request_parameters:
|
||
url: http://mywebsite.org
|
||
method: GET
|
||
|
||
tasks:
|
||
setup_task:
|
||
action: std.http
|
||
input: <% $.http_request_parameters %>
|
||
|
||
|
||
The same rules apply to tasks associated with workflows.
|
||
|
||
Full syntax:
|
||
|
||
::
|
||
|
||
my_task:
|
||
workflow: some_nested_workflow
|
||
input:
|
||
param1: val1
|
||
param2: val2
|
||
|
||
Simplified syntax:
|
||
|
||
::
|
||
|
||
my_task:
|
||
workflow: some_nested_workflow param1='val1' param2='val2'
|
||
|
||
Syntax with dynamic input parameter map:
|
||
|
||
::
|
||
|
||
---
|
||
version: '2.0'
|
||
|
||
example_workflow:
|
||
input:
|
||
- nested_params: {"param1": "val1", "param2": "val2"}
|
||
|
||
tasks:
|
||
setup_task:
|
||
workflow: some_nested_workflow
|
||
input: <% $.nested_params %>
|
||
|
||
**NOTE**: It's also possible to merge these two approaches and specify a part
|
||
of parameters using simplified key-value pairs syntax and using keyword *input*.
|
||
In this case all the parameters will be effectively merged. If the same
|
||
parameter is specified in both ways then the one under *input* keyword takes
|
||
precedence.
|
||
|
||
Direct workflow
|
||
^^^^^^^^^^^^^^^
|
||
|
||
Direct workflow consists of tasks combined in a graph where every next task
|
||
starts after another one depending on produced result. So direct workflow has a
|
||
notion of transition. Direct workflow is considered to be completed if there
|
||
aren't any transitions left that could be used to jump to next tasks.
|
||
|
||
.. image:: /user/terminology/img/direct_workflow.png
|
||
|
||
Figure 1. Mistral Direct Workflow.
|
||
|
||
YAML example
|
||
''''''''''''
|
||
|
||
::
|
||
|
||
---
|
||
version: '2.0'
|
||
|
||
create_vm_and_send_email:
|
||
type: direct
|
||
|
||
input:
|
||
- vm_name
|
||
- image_id
|
||
- flavor_id
|
||
|
||
output:
|
||
result: <% $.vm_id %>
|
||
|
||
tasks:
|
||
create_vm:
|
||
action: nova.servers_create name=<% $.vm_name %> image=<% $.image_id %> flavor=<% $.flavor_id %>
|
||
publish:
|
||
vm_id: <% task(create_vm).result.id %>
|
||
on-error:
|
||
- send_error_email
|
||
on-success:
|
||
- send_success_email
|
||
|
||
send_error_email:
|
||
action: send_email to_addrs=['admin@mysite.org'] body='Failed to create a VM'
|
||
on-complete:
|
||
- fail
|
||
|
||
send_success_email:
|
||
action: send_email to_addrs=['admin@mysite.org'] body='Vm is successfully created and its id <% $.vm_id %>'
|
||
|
||
Direct workflow task attributes
|
||
'''''''''''''''''''''''''''''''
|
||
|
||
Mistral supports the following task transitions:
|
||
|
||
- **on-success** - List of tasks which will run after the task has
|
||
completed successfully. *Optional*.
|
||
- **on-error** - List of tasks which will run after the task has
|
||
completed with an error. *Optional*.
|
||
- **on-complete** - List of tasks which will run after the task has
|
||
completed regardless of whether it is successful or not. *Optional*.
|
||
|
||
You can define the task transitions in two ways:
|
||
|
||
The first is just a list of tasks. You can find the example of workflow
|
||
above. The second way is:
|
||
|
||
::
|
||
|
||
*transition*:
|
||
publish:
|
||
global:
|
||
some_global_variable: some_value
|
||
branch:
|
||
some_branch_variable: some_value
|
||
next:
|
||
- *next_task*
|
||
|
||
The publish defined under *transitions* can optionally define scopes to be
|
||
able to publish into different scopes: ‘branch’ and ‘global’.
|
||
Specifying variables under ‘branch’ will make Mistral publish into a branch
|
||
workflow context just like ‘publish’ and ‘publish-on-error’. Specifying
|
||
variables under ‘global’ will make Mistral publish into a global workflow
|
||
context. You can use “$.” in YAQL and “_.” in Jinja to access to a global
|
||
variable but branch variables can shadow them if they are
|
||
published in the current branch. To prevent it, you may use the YAQL/Jinja
|
||
function “global()” to explicitly access variables in workflow global context.
|
||
|
||
If ‘publish’ is defined in ‘on-complete’ and also in ‘on-success’ and/or
|
||
‘on-error’ then the result of publishing will be a merge of what
|
||
‘on-complete’ publishes with what ‘on-success’ or ‘on-error’ publishes
|
||
depending on the task status. If ‘on-complete’ publishes variables that
|
||
are also published by ‘on-success’ or ‘on-error’ then latter take precedence.
|
||
In other words, ‘on-complete’ in this case is considered a default which can
|
||
be overridden by more specific ‘on-XXX’ clause.
|
||
|
||
The keyword ‘next’ defined under *transitions* optionally contains list of
|
||
tasks which will run after the current task finished.
|
||
|
||
Example of writing and reading global variables
|
||
'''''''''''''''''''''''''''''''''''''''''''''''
|
||
|
||
::
|
||
|
||
---
|
||
version: '2.0'
|
||
|
||
wf:
|
||
tasks:
|
||
A:
|
||
action: std.noop
|
||
on-success:
|
||
publish:
|
||
branch:
|
||
my_var: "branch value"
|
||
global:
|
||
my_var: "global value"
|
||
next: A1
|
||
|
||
A1:
|
||
# $.my_var will always evaluate to "branch value" because A1 belongs
|
||
# to the same branch as A and runs after A. When using "$" to access
|
||
# context variables branch values have higher priority.
|
||
# In order to access global context reliably we need to use YAQL/Jinja
|
||
# function 'global'. So global(my_var) will always evaluate to
|
||
# 'global value'.
|
||
action: my_action1 param1=<% $.my_var %> param2=<% global(my_var) %>
|
||
|
||
B:
|
||
# $.my_var will evaluate to "global value" if task A completes
|
||
# before task B and "null", if not. It's because A and B are
|
||
# parallel and 'publish' in A doesn't apply to B, only
|
||
# 'publish-global' does. In this example global(my_var) has the same
|
||
# meaning as $.my_var because there's no ambiguity from what context
|
||
# we should take variable 'my_var'.
|
||
action: my_action2 param1=<% $.my_var %> param2=<% global(my_var) %>
|
||
|
||
**NOTE!** It’s important to note that this is an unprotected way of modifying
|
||
data because race conditions are possible when writing different values for
|
||
same variables in the global context from parallel branches. In other words,
|
||
if we have branches A and B and there are tasks in these branches that first
|
||
read global variable X, then increment it and write the new value Mistral
|
||
won’t provide any guarantee that the result value after finishing tasks A
|
||
and B will be X + 2. In some cases it can be X + 1 because the following may
|
||
happen: task A read X, Task B read X, Task B incremented X, Task B wrote
|
||
X + 1, Task A incremented X (the old one, not incremented by B), Task A
|
||
wrote X + 1.
|
||
|
||
Note: All of the above clauses cannot contain task names evaluated as
|
||
YAQL/Jinja expressions. They have to be static values. However, task
|
||
transitions can be conditional, based on expressions. See
|
||
`Transitions with expressions <#transitions-with-expressions>`__ for more
|
||
details.
|
||
|
||
It is important to understand the semantics of **on-success**, **on-error**
|
||
and **on-complete** around handling action errors.
|
||
|
||
In case if task action returned an error **on-success** and **on-complete**
|
||
won't prevent from failing the entire workflow execution. Only **on-error**
|
||
will. The closest analogy is *try-catch-finally* blocks in regular
|
||
programming languages. **on-error** is similar to *catch* and it serves
|
||
as an exception handler for possible errors expected by design. Whereas
|
||
**on-complete** is like *finally* that will run in any case but it won't
|
||
stop the exception from bubbling up to an upper layer. So **on-complete**
|
||
should only be understood as a language construction that allows to
|
||
define some clean up actions.
|
||
|
||
Having that said, it's important to know the order in which these clauses
|
||
are processed by Mistral.
|
||
|
||
::
|
||
|
||
taskA:
|
||
action: my_action
|
||
on-success:
|
||
- taskB
|
||
- taskC
|
||
on-complete:
|
||
- taskD
|
||
- taskE
|
||
|
||
In this example, if the task action ('my_action') completes successfully then
|
||
Mistral will first process the 'on-success' clause and schedule tasks 'taskB'
|
||
and 'taskC' and then process the 'on-complete' clause and schedule 'taskC' and
|
||
'taskE'. In most cases, this processing order is not so important but there are
|
||
situations when it matters, especially when both 'on-success' and 'on-complete'
|
||
lists have `engine commands <#engine-commands>`__ that are explained later in
|
||
this document.
|
||
|
||
If 'on-success' and 'on-error' are both defined in the task definition, they
|
||
never clash because they are mutually exclusive which means that only one of
|
||
them can be processed depending on whether the task action failed or succeeded.
|
||
|
||
|
||
Transitions with expressions
|
||
''''''''''''''''''''''''''''
|
||
|
||
Task transitions can be determined by success/error/completeness of the
|
||
previous tasks and also by additional guard expressions that can access any
|
||
data produced by upstream tasks and as workflow input. So in the example above
|
||
task 'create_vm' could also have a YAQL expression on transition to task
|
||
'send_success_email' as follows:
|
||
|
||
::
|
||
|
||
create_vm:
|
||
...
|
||
on-success:
|
||
- send_success_email: <% $.vm_id != null %>
|
||
|
||
And this would tell Mistral to run 'send_success_email' task only if 'vm_id'
|
||
variable published by task 'create_vm' is not empty. Expressions can also be
|
||
applied to 'on-error' and 'on-complete'.
|
||
|
||
Engine Commands
|
||
'''''''''''''''
|
||
|
||
Mistral has a number of engine commands that can be called within direct
|
||
workflows. These commands are used to change the workflow state.
|
||
|
||
- **succeed** - will end the current workflow and set its state to SUCCESS.
|
||
- **pause** - will end the current workflow and set its state to PAUSED.
|
||
- **fail** - will end the current workflow and set its state to ERROR.
|
||
|
||
Each of the engine commands accepts a ``msg`` input. This is optional, but if
|
||
provided, it will be stored in the state info on the workflow execution.
|
||
|
||
Workflows that have been ended with ``succeed`` or ``fail`` may not be resumed
|
||
later, but workflows that have been ended with ``pause`` may be.
|
||
|
||
YAML example
|
||
''''''''''''
|
||
|
||
::
|
||
|
||
---
|
||
version: '2.0'
|
||
|
||
send_error_mail:
|
||
tasks:
|
||
create_server:
|
||
action: nova.servers_create name=<% $.vm_name %>
|
||
publish:
|
||
vm_id: <% task().result.id %>
|
||
on-complete:
|
||
- fail: <% not $.vm_id %>
|
||
|
||
In this example we have a short workflow with one task that creates a server
|
||
in Nova. The task publishes the ID of the virtual machine, but if this value
|
||
is empty then it will fail the workflow.
|
||
|
||
::
|
||
|
||
on-complete:
|
||
- taskA
|
||
- fail
|
||
- taskB
|
||
|
||
When the engine commands are used with task names in a single list, they are
|
||
processed one at a time until the workflow reaches a terminal state. In the
|
||
above example, the ``on-complete`` has three steps to complete - these are
|
||
executed in order until the workflow reaches a terminal state. So in this case
|
||
``taskA`` is scheduled first, then the ``fail`` engine command sets the
|
||
workflow state to ERROR and ``taskB`` is never scheduled. ``taskB`` would not be
|
||
scheduled if ``succeed`` was used in this example either.
|
||
|
||
The ``pause`` command pauses the workflow. This means that the workflow can
|
||
continue when its state is set to RUNNING by using the update Rest API call.
|
||
|
||
YAML example:
|
||
|
||
::
|
||
|
||
on-complete:
|
||
- taskA
|
||
- pause
|
||
- taskB
|
||
|
||
In this case when Mistral processes the 'on-complete' clause it will schedule
|
||
``taskA`` and then set the workflow state to PAUSED, and stop scheduling new
|
||
tasks. However, if the workflow is later resumed manually then Mistral will
|
||
schedule ``taskB`` because in the 'on-complete' list it goes right after the
|
||
``pause`` command.
|
||
|
||
Given the order in which Mistral processes 'on-success' (or 'on-error') and
|
||
'on-complete' clauses it's important to understand what will happen if both
|
||
clauses have engine commands listed in them.
|
||
|
||
::
|
||
|
||
taskA:
|
||
action: my_action
|
||
on-error:
|
||
- taskB
|
||
- fail
|
||
- taskC
|
||
on-complete:
|
||
- taskD
|
||
- pause
|
||
- taskE
|
||
|
||
As was explained above, 'on-complete' is always processed after 'on-success'
|
||
(or 'on-error') because it plays the similar role as 'finally' in most general
|
||
purpose programming languages. Let's consider two scenarios that can happen
|
||
in the example above when 'taskA' runs, i.e. its action 'my_action' runs.
|
||
|
||
- If 'my_action' fails then Mistral will schedule 'taskB' because it's
|
||
listed in the 'on-error' clause which is processed before the
|
||
'on-complete' and then will set the state of the workflow to ERROR.
|
||
This will prevent from scheduling other new tasks so neither 'taskC' nor
|
||
'taskD' and 'taskE' will be ever be scheduled. In other words, the whole
|
||
'on-complete' clause will never be processed because the 'fail' command in
|
||
the 'on-error' sets the workflow state to ERROR.
|
||
- If 'my_action' succeeds then the 'on-error' clause will be ignored and since
|
||
'on-success' is not defined then Mistral will process the 'on-complete'
|
||
clause. And while doing that, it will schedule 'taskD' first and then pause
|
||
the workflow because of the 'pause' command. 'taskE' will be scheduled if
|
||
this workflow is resumed manually at some later time through the API.
|
||
|
||
This illustrates that, while designing a workflow, it's important to know
|
||
precisely how Mistral processes 'on-success', 'on-error' and 'on-complete'
|
||
and engine commands.
|
||
|
||
Engine commands and tasks
|
||
'''''''''''''''''''''''''
|
||
|
||
The **on-*** clauses in direct workflows can refer both to tasks and engine
|
||
commands, as demonstrated earlier. It is possible to use the engine commands
|
||
as names for tasks. For example, one can create a task named `noop` or `fail`.
|
||
These tasks will override the engine commands, that is, the action defined
|
||
in these tasks will be executed instead of the engine commands. This is a
|
||
method to succinctly extend the default behavior of the Mistral engine or
|
||
provide side-effect free workflow examples.
|
||
|
||
The order in which task names are resolved is the following:
|
||
|
||
1. the task with the given name is searched
|
||
2. the engine command with the given name is selected
|
||
|
||
The first option that matches is executed.
|
||
|
||
Fork
|
||
''''
|
||
|
||
There are situations when we need to be able to run more than one task after
|
||
some task has completed.
|
||
|
||
::
|
||
|
||
create_vm:
|
||
...
|
||
on-success:
|
||
- register_vm_in_load_balancer
|
||
- register_vm_in_dns
|
||
|
||
In this case Mistral will run both "register_xxx" tasks simultaneously and this
|
||
will lead to multiple independent workflow routes being processed in parallel.
|
||
|
||
Join
|
||
''''
|
||
|
||
Join flow control allows to synchronize multiple parallel workflow branches and
|
||
aggregate their data.
|
||
|
||
Full Join (join: all)
|
||
|
||
::
|
||
|
||
register_vm_in_load_balancer:
|
||
...
|
||
on-success:
|
||
- wait_for_all_registrations
|
||
|
||
register_vm_in_dns:
|
||
...
|
||
on-success:
|
||
- wait_for_all_registrations
|
||
|
||
try_to_do_something_without_registration:
|
||
...
|
||
on-error:
|
||
- wait_for_all_registrations
|
||
|
||
wait_for_all_registrations:
|
||
join: all
|
||
action: send_email
|
||
|
||
When a task has property "join" assigned with value "all" the task will run
|
||
only if all upstream tasks (ones that lead to this task) are completed and
|
||
corresponding conditions have triggered. Task A is considered an upstream task
|
||
of Task B if Task A has Task B mentioned in any of its "on-success", "on-error"
|
||
and "on-complete" clauses regardless of guard expressions.
|
||
|
||
Partial Join (join: 2)
|
||
|
||
::
|
||
|
||
register_vm_in_load_balancer:
|
||
...
|
||
on-success:
|
||
- wait_for_two_registrations
|
||
|
||
register_vm_in_dns:
|
||
...
|
||
on-success:
|
||
- wait_for_two_registrations
|
||
|
||
register_vm_in_zabbix:
|
||
...
|
||
on-success:
|
||
- wait_for_two_registrations
|
||
|
||
wait_for_two_registrations:
|
||
join: 2
|
||
action: send_email
|
||
|
||
When a task has property "join" assigned with a numeric value then the task
|
||
will run when at least this number of upstream tasks are completed and
|
||
corresponding conditions have triggered. In the example above task
|
||
"wait_for_two_registrations" will run if two any of
|
||
"register_vm_xxx" tasks complete.
|
||
|
||
Discriminator (join: one)
|
||
|
||
|
||
Discriminator is a special case of Partial Join when "join" property has value
|
||
1. It means Mistral will wait for any completed task. In this case instead of 1
|
||
it is possible to specify special string value "one" which is introduced for
|
||
symmetry with "all". However, it's up to the user whether to use "1" or "one".
|
||
|
||
Reverse workflow
|
||
^^^^^^^^^^^^^^^^
|
||
|
||
In reverse workflow all relationships in workflow task graph are dependencies.
|
||
In order to run this type of workflow we need to specify a task that needs to
|
||
be completed, it can be conventionally called 'target task'. When Mistral
|
||
Engine starts a workflow it recursively identifies all the dependencies that
|
||
need to be completed first.
|
||
|
||
.. image:: /user/terminology/img/reverse_workflow.png
|
||
|
||
Figure 2 explains how reverse workflow works. In the example, task **T1** is
|
||
chosen a target task. So when the workflow starts Mistral will run only tasks
|
||
**T7**, **T8**, **T5**, **T6**, **T2** and **T1** in the specified order
|
||
(starting from tasks that have no dependencies). Tasks **T3** and **T4** won't
|
||
be a part of this workflow because there's no route in the directed graph from
|
||
**T1** to **T3** or **T4**.
|
||
|
||
YAML example
|
||
''''''''''''
|
||
|
||
::
|
||
|
||
---
|
||
version: '2.0'
|
||
|
||
create_vm_and_send_email:
|
||
type: reverse
|
||
|
||
input:
|
||
- vm_name
|
||
- image_id
|
||
- flavor_id
|
||
|
||
output:
|
||
result: <% $.vm_id %>
|
||
|
||
tasks:
|
||
create_vm:
|
||
action: nova.servers_create name=<% $.vm_name %> image=<% $.image_id %> flavor=<% $.flavor_id %>
|
||
publish:
|
||
vm_id: <% task(create_vm).result.id %>
|
||
|
||
search_for_ip:
|
||
action: nova.floating_ips_findall instance_id=null
|
||
publish:
|
||
vm_ip: <% task(search_for_ip).result[0].ip %>
|
||
|
||
associate_ip:
|
||
action: nova.servers_add_floating_ip server=<% $.vm_id %> address=<% $.vm_ip %>
|
||
requires: [search_for_ip]
|
||
|
||
send_email:
|
||
action: send_email to='admin@mysite.org' body='Vm is created and id <% $.vm_id %> and ip address <% $.vm_ip %>'
|
||
requires: [create_vm, associate_ip]
|
||
|
||
Reverse workflow task attributes
|
||
''''''''''''''''''''''''''''''''
|
||
|
||
- **requires** - List of tasks which should be executed before this
|
||
task. *Optional*.
|
||
|
||
Processing collections
|
||
^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
YAML example
|
||
''''''''''''
|
||
|
||
::
|
||
|
||
---
|
||
version: '2.0'
|
||
|
||
create_vms:
|
||
description: Creating multiple virtual servers using "with-items".
|
||
|
||
input:
|
||
- vm_names
|
||
- image_ref
|
||
- flavor_ref
|
||
|
||
output:
|
||
vm_ids: <% $.vm_ids %>
|
||
|
||
tasks:
|
||
create_servers:
|
||
with-items: vm_name in <% $.vm_names %>
|
||
action: nova.servers_create name=<% $.vm_name %> image=<% $.image_ref %> flavor=<% $.flavor_ref %>
|
||
publish:
|
||
vm_ids: <% task(create_servers).result.id %>
|
||
on-success:
|
||
- wait_for_servers
|
||
|
||
wait_for_servers:
|
||
with-items: vm_id in <% $.vm_ids %>
|
||
action: nova.servers_find id=<% $.vm_id %> status='ACTIVE'
|
||
retry:
|
||
delay: 5
|
||
count: <% $.vm_names.len() * 10 %>
|
||
|
||
Workflow "create_vms" in this example creates as many virtual servers as we
|
||
provide in "vm_names" input parameter. E.g., if we specify
|
||
vm_names=["vm1", "vm2"] then it'll create servers with these names based on
|
||
same image and flavor. It is possible because of using "with-items" keyword
|
||
that makes an action or a workflow associated with a task run multiple times.
|
||
Value of "with-items" task property contains an expression in the form:
|
||
'my_var' in <% YAQL_expression %>. Similar for Jinja2 expression: 'my_var' in
|
||
{{ Jinja2_expression }}.
|
||
|
||
The most common form is:
|
||
|
||
::
|
||
|
||
with-items:
|
||
- var1 in <% YAQL_expression_1 %> # or: var1 in <% Jinja2_expression_1 %>
|
||
- var2 in <% YAQL_expression_2 %> # or: var2 in <% Jinja2_expression_2 %>
|
||
...
|
||
- varN in <% YAQL_expression_N %> # or: varN in <% Jinja2_expression_N %>
|
||
|
||
where collections expressed as YAQL_expression_1, YAQL_expression_2,
|
||
YAQL_expression_N must have equal sizes. When a task gets started Mistral will
|
||
iterate over all collections in parallel, i.e. number of iterations will be
|
||
equal to length of any collections.
|
||
|
||
Note that in case of using "with-items" task result accessible in workflow
|
||
context as <% task(task_name).result %> will be a list containing results of
|
||
corresponding action/workflow calls. If at least one action/workflow call has
|
||
failed then the whole task will get into ERROR state. It's also possible to
|
||
apply retry policy for tasks with "with-items" property. In this case retry
|
||
policy will be relaunching all action/workflow calls according to "with-items"
|
||
configuration. Other policies can also be used the same way as with regular non
|
||
"with-items" tasks.
|
||
|
||
.. _actions-dsl:
|
||
|
||
Actions
|
||
-------
|
||
|
||
Action defines what exactly needs to be done when task starts. Action is
|
||
similar to a regular function in general purpose programming language like
|
||
Python. It has a name and parameters. Mistral distinguishes 'system actions'
|
||
and 'Ad-hoc actions'.
|
||
|
||
System actions
|
||
^^^^^^^^^^^^^^
|
||
|
||
System actions are provided by Mistral out of the box and can be used by anyone.
|
||
It is also possible to add system actions for specific Mistral installation via
|
||
a special plugin mechanism. Currently, built-in system actions are:
|
||
|
||
std.fail
|
||
''''''''
|
||
|
||
This action always fails. It can be used to manually fail a workflow task..
|
||
|
||
::
|
||
|
||
wf:
|
||
tasks:
|
||
manual_fail:
|
||
action: std.fail
|
||
|
||
The action can be passed the `error_data` parameter. This data will be used as
|
||
the action return value.
|
||
|
||
::
|
||
|
||
wf:
|
||
tasks:
|
||
manual_fail:
|
||
action: std.fail
|
||
input: error_data={x:1,y:2}
|
||
|
||
std.http
|
||
''''''''
|
||
|
||
Sends an HTTP request.
|
||
|
||
Input parameters:
|
||
|
||
- **url** - URL for the HTTP request. *Required*.
|
||
- **method** - method for the HTTP request. *Optional*. Default is
|
||
'GET'.
|
||
- **params** - Dictionary or bytes to be sent in the query string for
|
||
the HTTP request. *Optional*.
|
||
- **body** - Dictionary, bytes, or file-like object to send in the body
|
||
of the HTTP request. *Optional*.
|
||
- **headers** - Dictionary of HTTP Headers to send with the HTTP
|
||
request. *Optional*.
|
||
- **cookies** - Dictionary of HTTP Cookies to send with the HTTP
|
||
request. *Optional*.
|
||
- **auth** - Auth to enable Basic/Digest/Custom HTTP Auth. *Optional*.
|
||
- **timeout** - Float describing the timeout of the request in seconds.
|
||
*Optional*.
|
||
- **allow_redirects** - Boolean. Set to True if POST/PUT/DELETE
|
||
redirect following is allowed. *Optional*.
|
||
- **proxies** - Dictionary mapping protocol to the URL of the proxy.
|
||
*Optional*.
|
||
- **verify** - Either a boolean, in which case it controls whether we verify
|
||
the server's TLS certificate, or a string, in which case it must be a path
|
||
to a CA bundle to use. *Optional*. Default is 'True'.
|
||
|
||
Example:
|
||
|
||
::
|
||
|
||
http_task:
|
||
action: std.http url='google.com'
|
||
|
||
std.mistral_http
|
||
''''''''''''''''
|
||
|
||
This action works just like 'std.http' with the only exception: when sending a
|
||
request it inserts the following HTTP headers:
|
||
|
||
- **Mistral-Workflow-Name** - Name of the workflow that the current
|
||
action execution is associated with.
|
||
- **Mistral-Execution-Id** - Identifier of the workflow execution this
|
||
action is associated with.
|
||
- **Mistral-Task-Id** - Identifier of the task execution this action
|
||
execution is associated with.
|
||
- **Mistral-Action-Execution-Id** - Identifier of the current action
|
||
execution.
|
||
|
||
Using this action makes it possible to do any work in asynchronous manner
|
||
triggered via HTTP protocol. That means that Mistral can send a request using
|
||
'std.mistral_http' and then any time later whatever system that received this
|
||
request can notify Mistral back (using its public API) with the result of this
|
||
action. Header **Mistral-Action-Execution-Id** is required for this operation
|
||
because it is used a key to find corresponding action execution in Mistral to
|
||
attach the result to.
|
||
|
||
std.email
|
||
'''''''''
|
||
|
||
Sends an email message via SMTP protocol.
|
||
|
||
- **to_addrs** - Comma separated list of recipients. *Required*.
|
||
- **cc_addrs** - Comma separated list of CC recipients. *Optional*.
|
||
- **bcc_addrs** - Comma separated list of BCC recipients. *Optional*.
|
||
- **reply_to** - Comma separated list of email address. *Optional*.
|
||
- **subject** - Subject of the message. *Optional*.
|
||
- **body** - Text containing message body. *Optional*.
|
||
- **html_body** - Text containing the message in HTML format. *Optional*.
|
||
- **from_addr** - Sender email address. *Required*.
|
||
- **smtp_server** - SMTP server host name. *Required*.
|
||
- **smtp_password** - SMTP server password. *Optional*.
|
||
|
||
Example:
|
||
|
||
::
|
||
|
||
send_email_task:
|
||
action: std.email
|
||
input:
|
||
to_addrs: [admin@mywebsite.org]
|
||
subject: Hello from Mistral :)
|
||
body: |
|
||
Cheers! (:_:)
|
||
-- Thanks, Mistral Team.
|
||
from_addr: mistral@openstack.org
|
||
smtp_server: smtp.google.com
|
||
smtp_password: SECRET
|
||
|
||
The syntax of 'std.emal' action is pretty verbose. However, it can be
|
||
significantly simplified using Ad-hoc actions. More about them
|
||
`below <#ad-hoc-actions>`__.
|
||
|
||
std.ssh
|
||
'''''''
|
||
|
||
Runs Secure Shell command.
|
||
|
||
Input parameters:
|
||
|
||
- **cmd** - String containing a shell command that needs to be
|
||
executed. *Required*.
|
||
- **host** - Host name that the command needs to be executed on.
|
||
*Required*.
|
||
- **username** - User name to authenticate on the host. *Required*.
|
||
- **password** - User password to authenticate on the host. *Optional*.
|
||
- **private_key_filename** - Private key file name which will be used for
|
||
authentication on remote host. All private keys should be on the executor
|
||
host in **<home-user-directory>/.ssh** directory or absolute path of
|
||
the key should be provided. The file needs to be accessible
|
||
for the user account running the executor. *Optional*.
|
||
|
||
**NOTE**: Authentication using key pairs is supported, key should be
|
||
on Mistral Executor server machine.
|
||
|
||
std.echo
|
||
''''''''
|
||
|
||
Simple action mostly needed for testing purposes that returns a predefined
|
||
result.
|
||
|
||
Input parameters:
|
||
|
||
- **output** - Value of any type that needs to be returned as a result
|
||
of the action. *Required*.
|
||
- **delay** - Float value that defines with what delay (in seconds)
|
||
the result should be returned. *Optional*.
|
||
|
||
std.javascript
|
||
''''''''''''''
|
||
|
||
Evaluates given JavaScript code.
|
||
|
||
**NOTE**: std.js is an alias for std.javascript i.e, std.js can be used in
|
||
place of std.javascript.
|
||
|
||
Input parameters:
|
||
|
||
- **script** - The text of JavaScript snippet that needs to be
|
||
executed. *Required*.
|
||
- **context** - This object will be assigned to the *$* javascript variable.
|
||
The default value is None.
|
||
|
||
To use std.javascript, it is needed to install the
|
||
`py_mini_racer <https://github.com/sqreen/PyMiniRacer>`__ and set
|
||
*py_mini_racer* to *js_implementation* parameter in *mistral.conf*:
|
||
|
||
.. code-block:: bash
|
||
|
||
pip install py_mini_racer
|
||
|
||
Other available implementations:
|
||
|
||
- `pyv8 <https://code.google.com/archive/p/pyv8>`__
|
||
- `v8eval <https://github.com/sony/v8eval>`__
|
||
|
||
Example with *context*:
|
||
|
||
::
|
||
|
||
---
|
||
version: '2.0'
|
||
|
||
generate_uuid:
|
||
description: Generates a Universal Unique ID
|
||
|
||
input:
|
||
- radix: 16
|
||
|
||
output:
|
||
uuid: <% $.generated_uuid %>
|
||
|
||
tasks:
|
||
generate_uuid_task:
|
||
action: std.js
|
||
input:
|
||
context: <% $ %>
|
||
script: |
|
||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r&0x3|0x8);
|
||
return v.toString($.radix);
|
||
});
|
||
publish:
|
||
generated_uuid: <% task().result %>
|
||
|
||
Another example for getting the current date and time:
|
||
|
||
::
|
||
|
||
---
|
||
version: '2.0'
|
||
|
||
get_date_workflow:
|
||
description: Get the current date
|
||
|
||
output:
|
||
current_date: <% $.current_date %>
|
||
|
||
tasks:
|
||
get_date_task:
|
||
action: std.js
|
||
input:
|
||
script: |
|
||
var date = new Date();
|
||
return date; // returns "2015-07-12T10:32:12.460000" or use date.toLocaleDateString() for "Sunday, July 12, 2015"
|
||
publish:
|
||
current_date: <% task().result %>
|
||
|
||
Ad-hoc actions
|
||
^^^^^^^^^^^^^^
|
||
|
||
Ad-hoc action is a special type of action that can be created by user. Ad-hoc
|
||
action is always created as a wrapper around any other existing system action
|
||
and its main goal is to simplify using same actions many times with similar
|
||
pattern.
|
||
|
||
YAML example
|
||
''''''''''''
|
||
|
||
::
|
||
|
||
---
|
||
version: '2.0'
|
||
|
||
error_email:
|
||
input:
|
||
- execution_id
|
||
base: std.email
|
||
base-input:
|
||
to_addrs: ['admin@mywebsite.org']
|
||
subject: 'Something went wrong with your Mistral workflow :('
|
||
body: |
|
||
Please take a look at Mistral Dashboard to find out what's wrong
|
||
with your workflow execution <% $.execution_id %>.
|
||
Everything's going to be alright!
|
||
-- Sincerely, Mistral Team.
|
||
from_addr: 'mistral@openstack.org'
|
||
smtp_server: 'smtp.google.com'
|
||
smtp_password: 'SECRET'
|
||
|
||
Once this action is uploaded to Mistral any workflow will be able to use it as
|
||
follows:
|
||
|
||
::
|
||
|
||
my_workflow:
|
||
tasks:
|
||
...
|
||
send_error_email:
|
||
action: error_email execution_id=<% execution().id %>
|
||
|
||
Attributes
|
||
''''''''''
|
||
|
||
- **base** - Name of base action that this action is built on top of.
|
||
*Required*.
|
||
- **base-input** - Actual input parameters provided to base action. Look at the
|
||
example above. *Optional*.
|
||
- **input** - List of declared action parameters which should be specified as
|
||
corresponding task input. This attribute is optional and used only for
|
||
documenting purposes. Mistral now does not enforce actual input parameters to
|
||
exactly correspond to this list. Base parameters will be calculated based on
|
||
provided actual parameters with using expressions so what's used in
|
||
expressions implicitly define real input parameters. Dictionary of actual
|
||
input parameters (expression context) is referenced as '$.' in YAQL and as
|
||
'_.' in Jinja. Redundant parameters will be simply ignored.
|
||
- **output** - Any data structure defining how to calculate output of this
|
||
action based on output of base action. It can optionally have expressions to
|
||
access properties of base action output through expression context.
|
||
|
||
Workbooks
|
||
---------
|
||
|
||
As mentioned before, workbooks still exist in Mistral Workflow Language version
|
||
2 but purely for convenience. Using workbooks users can combine multiple
|
||
entities of any type (workflows, actions and triggers) into one document and
|
||
upload to Mistral service. When uploading a workbook Mistral will parse it and
|
||
save its workflows, actions and triggers as independent objects which will be
|
||
accessible via their own API endpoints (/workflows, /actions and /triggers/).
|
||
Once it's done the workbook comes out of the game. User can just start workflows
|
||
and use references to workflows/actions/triggers as if they were uploaded
|
||
without workbook in the first place. However, if we want to modify these
|
||
individual objects we can modify the same workbook definition and re-upload it
|
||
to Mistral (or, of course, we can do it independently).
|
||
|
||
Namespacing
|
||
^^^^^^^^^^^
|
||
|
||
One thing that's worth noting is that when using a workbook Mistral uses its
|
||
name as a prefix for generating final names of workflows, actions and triggers
|
||
included into the workbook. To illustrate this principle let's take a look at
|
||
the figure below.
|
||
|
||
.. image:: /user/terminology/img/workbook_namespacing.png
|
||
|
||
So after a workbook has been uploaded its workflows and actions become
|
||
independent objects but with slightly different names.
|
||
|
||
YAML example
|
||
''''''''''''
|
||
|
||
::
|
||
|
||
---
|
||
version: '2.0'
|
||
|
||
name: my_workbook
|
||
|
||
description: My set of workflows and ad-hoc actions
|
||
|
||
workflows:
|
||
local_workflow1:
|
||
type: direct
|
||
|
||
tasks:
|
||
task1:
|
||
action: local_action str1='Hi' str2=' Mistral!'
|
||
on-complete:
|
||
- task2
|
||
|
||
task2:
|
||
action: global_action
|
||
...
|
||
|
||
local_workflow2:
|
||
type: reverse
|
||
|
||
tasks:
|
||
task1:
|
||
workflow: local_workflow1
|
||
|
||
task2:
|
||
workflow: global_workflow param1='val1' param2='val2'
|
||
requires: [task1]
|
||
...
|
||
actions:
|
||
local_action:
|
||
input:
|
||
- str1
|
||
- str2
|
||
base: std.echo output="<% $.str1 %><% $.str2 %>"
|
||
|
||
**NOTE**: Even though names of objects inside workbooks change upon uploading
|
||
Mistral allows referencing between those objects using local names declared in
|
||
the original workbook.
|
||
|
||
Attributes
|
||
^^^^^^^^^^
|
||
|
||
- **name** - Workbook name. *Required*.
|
||
- **description** - Workbook description. *Optional*.
|
||
- **tags** - String with arbitrary comma-separated values.
|
||
**Optional**.
|
||
- **workflows** - Dictionary containing workflow definitions.
|
||
*Optional*.
|
||
- **actions** - Dictionary containing ad-hoc action definitions.
|
||
*Optional*.
|
||
|
||
Predefined values/Functions in execution data context
|
||
-----------------------------------------------------
|
||
|
||
Using expressions it is possible to use some predefined values in Mistral
|
||
Workflow Language.
|
||
|
||
- **OpenStack context**
|
||
- **Task result**
|
||
- **Execution info**
|
||
- **Environment**
|
||
|
||
OpenStack context
|
||
^^^^^^^^^^^^^^^^^
|
||
|
||
OpenStack context is available by **$.openstack**. It contains **auth_token**,
|
||
**project_id**, **user_id**, **service_catalog**, **user_name**,
|
||
**project_name**, **roles**, **is_admin** properties.
|
||
|
||
|
||
Builtin functions in expressions
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
In addition to the current context (i.e. $ in YAQL and _ in Jinja2) expressions
|
||
have access to a set of predefined functions.
|
||
|
||
|
||
The expression languages come with their own individual included functions and
|
||
operations. Mistral adds the following functions that are available in all the
|
||
supported languages.
|
||
|
||
This section will describe builtin functions added by Mistral.
|
||
|
||
Tasks function
|
||
''''''''''''''
|
||
|
||
Signature:
|
||
**tasks(workflow_execution_id=null, recursive=false, state=null, flat=false)**
|
||
|
||
Description:
|
||
|
||
This function allows users to filter all tasks by workflow execution id
|
||
and/or state. In addition, it is possible to get task executions recursively
|
||
and flatten the task executions list.
|
||
|
||
Parameters:
|
||
|
||
#. **workflow_execution_id** - If provided the tasks function will return
|
||
task executions for a specific workflow execution (either the current
|
||
execution or a different one). Otherwise it will return all task
|
||
executions that match the other parameters. *Optional.*
|
||
#. **recursive** - This parameter is a boolean value, if it is true then all
|
||
task executions within nested workflow executions will be returned. This
|
||
is usually used in combination with a specific workflow_execution_id
|
||
where you still want to see nested workflow's task executions. *Optional.*
|
||
False by default.
|
||
#. **state** - If provided, the task executions will be filtered by their
|
||
current state. If state isn't provided, all task executions that match the
|
||
other parameters will be returned . *Optional.*
|
||
#. **flat** - if true, only list the task executions that match at least one
|
||
of the next conditions:
|
||
|
||
* task executions of type action
|
||
* task executions of type workflow that have a different state from the
|
||
workflow execution they triggered. For example, if used with a
|
||
specific workflow_execution_id and the state ERROR it will return
|
||
tasks that erred despite the workflow succeeding. This can mean that
|
||
there was an error in the task itself, like an invalid expression in
|
||
publish.
|
||
|
||
*Optional.* False by default.
|
||
|
||
Example:
|
||
|
||
Workflow definition:
|
||
|
||
::
|
||
|
||
---
|
||
version: "v2.0"
|
||
wf:
|
||
tasks:
|
||
task:
|
||
action: std.noop
|
||
publish:
|
||
all_tasks_in_this_wf_yaql: <% tasks(execution().id) %>
|
||
all_tasks_in_this_wf_jinja: "{{ tasks(execution().id) }}"
|
||
|
||
all_tasks_in_error_yaql: <% tasks(null, false, ERROR) %>
|
||
all_tasks_in_error_jinja: "{{ tasks(None, false, 'ERROR') }}"
|
||
all_tasks_in_error_yaql_with_kw: <% tasks(state => ERROR) %>
|
||
all_tasks_in_error_jinja_with_kw: "{{ tasks(state='ERROR') }}"
|
||
|
||
all_tasks_yaql_option1: <% tasks() %>
|
||
all_tasks_yaql_option2: <% tasks(null, false, null, false) %>
|
||
all_tasks_jinja_option1: "{{ tasks() }}"
|
||
all_tasks_jinja_option2: "{{ tasks(None, false, None, false) }}"
|
||
|
||
Task publish result (partial to keep the documentation short):
|
||
|
||
.. code-block:: json
|
||
|
||
{
|
||
"all_tasks_in_error_yaql": [
|
||
{
|
||
"id": "3d363d4b-8c19-48fa-a9a0-8721dc5469f2",
|
||
"name": "fail_task",
|
||
"type": "ACTION",
|
||
"workflow_execution_id": "c0a4d2ff-0127-4826-8370-0570ef8cad80",
|
||
"state": "ERROR",
|
||
"state_info": "Failed to run action [action_ex_id=bcb04b28-6d50-458e-9b7e-a45a5ff1ca01, action_cls='<class 'mistral.actions.std_actions.FailAction'>', attributes='{}', params='{}']\n Fail action expected exception.",
|
||
"result": "Failed to run action [action_ex_id=bcb04b28-6d50-458e-9b7e-a45a5ff1ca01, action_cls='<class 'mistral.actions.std_actions.FailAction'>', attributes='{}', params='{}']\n Fail action expected exception.",
|
||
"published": {},
|
||
"spec": {
|
||
"action": "std.fail",
|
||
"version": "2.0",
|
||
"type": "direct",
|
||
"name": "fail_task"
|
||
}
|
||
}
|
||
],
|
||
"all_tasks_in_this_wf_jinja": [
|
||
{
|
||
"id": "83a34bfe-268c-46f5-9e5c-c16900540084",
|
||
"name": "task",
|
||
"type": "ACTION",
|
||
"workflow_execution_id": "899a3318-b5c0-4860-82b4-a5bd147a4643",
|
||
"state": "SUCCESS",
|
||
"state_info": null,
|
||
"result": null,
|
||
"published": {},
|
||
"spec": {
|
||
"action": "std.noop",
|
||
"version": "2.0",
|
||
"type": "direct",
|
||
"name": "task",
|
||
"publish": {
|
||
"all_tasks_in_error_yaql": "<% tasks(null, false, ERROR) %>",
|
||
"all_tasks_in_error_jinja": "{{ tasks(None, false, 'ERROR') }}",
|
||
"all_tasks_yaql_option2": "<% tasks(null, false, false, false) %>",
|
||
"all_tasks_yaql_option1": "<% tasks() %>",
|
||
"all_tasks_jinja_option1": "{{ tasks() }}",
|
||
"all_tasks_in_error_jinja_with_kw": "{{ tasks(state='ERROR') }}",
|
||
"all_tasks_jinja_option2": "{{ tasks(None, false, None, false) }}",
|
||
"all_tasks_in_this_wf_jinja": "{{ tasks(execution().id) }}",
|
||
"all_tasks_in_this_wf_yaql": "<% tasks(execution().id) %>"
|
||
}
|
||
}
|
||
}
|
||
],
|
||
"_comment": "other fields were dropped to keep docs short"
|
||
}
|
||
|
||
|
||
Task result
|
||
'''''''''''
|
||
|
||
Task result is available by **task(<task_name>).result**. It contains task
|
||
result and directly depends on action output structure. Note that the
|
||
*task(<task_name>)* function itself returns more than only task result. It
|
||
returns the following fields of task executions:
|
||
|
||
* **id** - task execution UUID.
|
||
* **name** - task execution name.
|
||
* **spec** - task execution spec dict (loaded from Mistral Workflow Language).
|
||
* **state** - task execution state.
|
||
* **state_info** - task execution state info.
|
||
* **result** - task execution result. In case of a non 'with-items' task it's
|
||
simply a result of the task's action/sub-workflow execution. For a
|
||
'with-items' task it will be a list of results of corresponding
|
||
action/sub-workflow execution.
|
||
* **published** - task execution published variables.
|
||
|
||
|
||
Execution info
|
||
^^^^^^^^^^^^^^
|
||
|
||
Execution info is available by **execution()**. It contains
|
||
information about execution itself such as **id**, **wf_spec**,
|
||
**input**, **start_params**, and **root_execution_id** .
|
||
|
||
Executions function
|
||
'''''''''''''''''''
|
||
|
||
Signature:
|
||
**executions(id=null, root_execution_id=null, state=null,
|
||
from_time=null, to_time=null)**
|
||
|
||
Description:
|
||
|
||
This function allows users to filter all executions by execution id,
|
||
root_execution_id ,state and/or created_at time.
|
||
|
||
Parameters:
|
||
|
||
#. **id** - If provided will return a list of executions with that id.
|
||
Otherwise it will return all executions that match the other
|
||
parameters. *Optional.*
|
||
#. **root_execution_id** - Similar to id above, if provided will return
|
||
a list of executions with that root_execution_id. Otherwise it will
|
||
return all executions that match the other parameters. *Optional.*
|
||
False by default.
|
||
#. **state** - If provided, the executions will be filtered by their
|
||
current state. If state isn't provided, all executions that match the
|
||
other parameters will be returned . *Optional.*
|
||
#. **from_time** - If provided, the executions will be filtered by their
|
||
created_at time being greater or equal to the from_time parameter.
|
||
If from_time isn't provided, all executions that match the
|
||
other parameters will be returned. from_time parameter can be provided
|
||
in the format *YYYY-MM-DD hh:mm:ss*
|
||
*Optional.*
|
||
#. **to_time** - If provided, the executions will be filtered by their
|
||
created_at time being less than to the from_time parameter (less than but
|
||
not less than equal as the from_time parameter does)
|
||
If to_time isn't provided, all executions that match the
|
||
other parameters will be returned. to_time parameter can be provided
|
||
in the format *YYYY-MM-DD hh:mm:ss*
|
||
*Optional.*
|
||
|
||
Example:
|
||
|
||
Workflow definition:
|
||
|
||
::
|
||
|
||
---
|
||
version: "v2.0"
|
||
wf:
|
||
tasks:
|
||
task:
|
||
action: std.noop
|
||
publish:
|
||
all_executions_yaql: <% executions() %>
|
||
all_child_executions_of_this_execution: "{{ executions(root_execution_id=execution().id) }}"
|
||
|
||
all_executions_in_error_yaql: <% executions(null, null, ERROR) %>
|
||
all_executions_in_error_jinja: "{{ executions(None, None, 'ERROR') }}"
|
||
all_executions_in_error_yaql_with_kw: <% executions(state => ERROR) %>
|
||
all_executions_in_error_jinja_with_kw: "{{ executions(state='ERROR') }}"
|
||
|
||
all_executions_filtered_date_jinja: "{{ executions(to_time="2016-12-01 15:01:00") }}"
|
||
|
||
|
||
Environment
|
||
^^^^^^^^^^^
|
||
|
||
Environment info is available by **env()**. It is passed when user submits
|
||
workflow execution. It contains variables specified by user.
|
||
|
||
|
||
Global
|
||
^^^^^^
|
||
|
||
Global variables are available by **global(variable_name)**. If the
|
||
variable doesn't exist than None will be returned.
|