283 lines
8.6 KiB
Python
283 lines
8.6 KiB
Python
# Copyright 2013 - Mirantis, Inc.
|
|
# Copyright 2015 - StackStorm, Inc.
|
|
# Copyright 2016 - Brocade Communications Systems, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import warnings
|
|
|
|
from oslo_log import log as logging
|
|
from oslo_serialization import jsonutils
|
|
import yaml
|
|
|
|
from mistral.db import utils as db_utils
|
|
from mistral.db.v2 import api as db_api
|
|
from mistral.utils import filter_utils
|
|
from mistral_lib import utils
|
|
|
|
# Additional YAQL/Jinja functions provided by Mistral out of the box.
|
|
# If a function name ends with underscore then it doesn't need to pass
|
|
# the name of the function when context registers it.
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def env_(context):
|
|
return context['__env']
|
|
|
|
|
|
@db_utils.tx_cached(ignore_args='context')
|
|
def executions_(context, id=None, root_execution_id=None, state=None,
|
|
from_time=None, to_time=None):
|
|
filter_ = {}
|
|
|
|
if id is not None:
|
|
filter_ = filter_utils.create_or_update_filter('id', id, "eq", filter_)
|
|
|
|
if root_execution_id is not None:
|
|
filter_ = filter_utils.create_or_update_filter(
|
|
'root_execution_id',
|
|
root_execution_id,
|
|
'eq',
|
|
filter_
|
|
)
|
|
|
|
if state is not None:
|
|
filter_ = filter_utils.create_or_update_filter(
|
|
'state',
|
|
state,
|
|
'eq',
|
|
filter_
|
|
)
|
|
|
|
if from_time is not None:
|
|
filter_ = filter_utils.create_or_update_filter(
|
|
'created_at',
|
|
from_time,
|
|
'gte',
|
|
filter_
|
|
)
|
|
|
|
if to_time is not None:
|
|
filter_ = filter_utils.create_or_update_filter(
|
|
'created_at',
|
|
to_time,
|
|
'lt',
|
|
filter_
|
|
)
|
|
|
|
return db_api.get_workflow_executions(**filter_)
|
|
|
|
|
|
@db_utils.tx_cached(ignore_args='context')
|
|
def execution_(context):
|
|
wf_ex = db_api.get_workflow_execution(context['__execution']['id'])
|
|
|
|
return {
|
|
'id': wf_ex.id,
|
|
'name': wf_ex.name,
|
|
'spec': wf_ex.spec,
|
|
'input': wf_ex.input,
|
|
'params': wf_ex.params,
|
|
'created_at': wf_ex.created_at.isoformat(' '),
|
|
'updated_at': wf_ex.updated_at.isoformat(' '),
|
|
'root_execution_id': wf_ex.root_execution_id
|
|
}
|
|
|
|
|
|
def json_pp_(context, data=None):
|
|
warnings.warn(
|
|
"json_pp was deprecated in Queens and will be removed in the S cycle. "
|
|
"The json_dump expression function can be used for outputting JSON",
|
|
DeprecationWarning
|
|
)
|
|
|
|
return jsonutils.dumps(
|
|
data or context,
|
|
indent=4
|
|
).replace("\\n", "\n").replace(" \n", "\n")
|
|
|
|
|
|
def json_dump_(context, data):
|
|
return jsonutils.dumps(data, indent=4)
|
|
|
|
|
|
def yaml_dump_(context, data):
|
|
return yaml.safe_dump(data, default_flow_style=False)
|
|
|
|
|
|
@db_utils.tx_cached(ignore_args='context')
|
|
def task_(context, task_name=None):
|
|
# This section may not exist in a context if it's calculated not in
|
|
# task scope.
|
|
cur_task = context['__task_execution']
|
|
|
|
# 1. If task_name is empty it's 'task()' use case, we need to get the
|
|
# current task.
|
|
# 2. if task_name is not empty but it's equal to the current task name
|
|
# we need to take exactly the current instance of this task. Otherwise
|
|
# there may be ambiguity if there are many tasks with this name.
|
|
# 3. In other case we just find a task in DB by the given name.
|
|
if cur_task and (not task_name or cur_task['name'] == task_name):
|
|
task_ex = db_api.get_task_execution(cur_task['id'])
|
|
else:
|
|
task_execs = db_api.get_task_executions(
|
|
workflow_execution_id=context['__execution']['id'],
|
|
name=task_name
|
|
)
|
|
|
|
# TODO(rakhmerov): Account for multiple executions (i.e. in case of
|
|
# cycles).
|
|
task_ex = task_execs[-1] if len(task_execs) > 0 else None
|
|
|
|
if not task_ex:
|
|
LOG.warning(
|
|
"Task '%s' not found by the task() expression function",
|
|
task_name
|
|
)
|
|
return None
|
|
|
|
# We don't use to_dict() db model method because not all fields
|
|
# make sense for user.
|
|
return _convert_to_user_model(task_ex)
|
|
|
|
|
|
def _should_pass_filter(t, state, flat):
|
|
# Start from assuming all is true, check only if needed.
|
|
state_match = True
|
|
flat_match = True
|
|
|
|
if state:
|
|
state_match = t['state'] == state
|
|
|
|
if flat:
|
|
is_action = t['type'] == utils.ACTION_TASK_TYPE
|
|
|
|
if not is_action:
|
|
nested_execs = db_api.get_workflow_executions(
|
|
task_execution_id=t.id
|
|
)
|
|
|
|
for n in nested_execs:
|
|
flat_match = flat_match and n.state != t.state
|
|
|
|
return state_match and flat_match
|
|
|
|
|
|
def _get_tasks_from_db(workflow_execution_id=None, recursive=False, state=None,
|
|
flat=False):
|
|
task_execs = []
|
|
nested_task_exs = []
|
|
|
|
kwargs = {}
|
|
|
|
if workflow_execution_id:
|
|
kwargs['workflow_execution_id'] = workflow_execution_id
|
|
|
|
# We can't add state to query if we want to filter by workflow_execution_id
|
|
# recursively. There might be a workflow_execution in one state with a
|
|
# nested workflow execution that has a task in the desired state until we
|
|
# have an optimization for queering all workflow executions under a given
|
|
# top level workflow execution, this is the way to go.
|
|
if state and not (workflow_execution_id and recursive):
|
|
kwargs['state'] = state
|
|
|
|
task_execs.extend(db_api.get_task_executions(**kwargs))
|
|
|
|
# If it is not recursive no need to check nested workflows.
|
|
# If there is no workflow execution id, we already have all we need, and
|
|
# doing more queries will just create duplication in the results.
|
|
if recursive and workflow_execution_id:
|
|
for t in task_execs:
|
|
if t.type == utils.WORKFLOW_TASK_TYPE:
|
|
# Get nested workflow execution that matches the task.
|
|
nested_workflow_executions = db_api.get_workflow_executions(
|
|
task_execution_id=t.id
|
|
)
|
|
|
|
# There might be zero nested executions.
|
|
for nested_workflow_execution in nested_workflow_executions:
|
|
nested_task_exs.extend(
|
|
_get_tasks_from_db(
|
|
nested_workflow_execution.id,
|
|
recursive,
|
|
state,
|
|
flat
|
|
)
|
|
)
|
|
|
|
if state or flat:
|
|
# Filter by state and flat.
|
|
task_execs = [
|
|
t for t in task_execs if _should_pass_filter(t, state, flat)
|
|
]
|
|
|
|
# The nested tasks were already filtered, since this is a recursion.
|
|
task_execs.extend(nested_task_exs)
|
|
|
|
return task_execs
|
|
|
|
|
|
@db_utils.tx_cached(ignore_args='context')
|
|
def tasks_(context, workflow_execution_id=None, recursive=False, state=None,
|
|
flat=False):
|
|
task_execs = _get_tasks_from_db(
|
|
workflow_execution_id,
|
|
recursive,
|
|
state,
|
|
flat
|
|
)
|
|
|
|
# Convert task_execs to user model and return.
|
|
return [_convert_to_user_model(t) for t in task_execs]
|
|
|
|
|
|
def _convert_to_user_model(task_ex):
|
|
# Importing data_flow in order to break cycle dependency between modules.
|
|
from mistral.workflow import data_flow
|
|
|
|
# We don't use to_dict() db model method because not all fields
|
|
# make sense for user.
|
|
return {
|
|
'id': task_ex.id,
|
|
'name': task_ex.name,
|
|
'spec': task_ex.spec,
|
|
'state': task_ex.state,
|
|
'state_info': task_ex.state_info,
|
|
'result': data_flow.get_task_execution_result(task_ex),
|
|
'published': task_ex.published,
|
|
'type': task_ex.type,
|
|
'workflow_execution_id': task_ex.workflow_execution_id,
|
|
'created_at': task_ex.created_at.isoformat(' '),
|
|
'updated_at': task_ex.updated_at.isoformat(' ')
|
|
if task_ex.updated_at is not None else None
|
|
}
|
|
|
|
|
|
def uuid_(context=None):
|
|
return utils.generate_unicode_uuid()
|
|
|
|
|
|
def global_(context, var_name):
|
|
wf_ex = db_api.get_workflow_execution(context['__execution']['id'])
|
|
|
|
return wf_ex.context.get(var_name)
|
|
|
|
|
|
def json_parse_(context, data):
|
|
return jsonutils.loads(data)
|
|
|
|
|
|
def yaml_parse_(context, data):
|
|
return yaml.safe_load(data)
|