deb-mistral/mistral/utils/expression_utils.py
Michal Gershenzon eb6c0513c6 Yaql Tasks Function
This new function will allow user to get a list of tasks matching certain
filter. For example only task in state ERROR from the current execution.

It is very useful for debugging, but also very expensive, since it might
require multiple DB queries. In addition it is important to remember a lot
of data can return from this function, so it should be used carefully

Change-Id: I452175bfb60636ed8de9b2b1ceab615359765964
Implements: blueprint yaql-tasks-function
Implements: blueprint yaql-errors-function
2016-11-30 15:49:08 +00:00

257 lines
7.4 KiB
Python

# Copyright 2015 - Mirantis, 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.
from functools import partial
from oslo_serialization import jsonutils
from stevedore import extension
import yaql
from mistral.db.v2 import api as db_api
from mistral import utils
ROOT_YAQL_CONTEXT = None
def get_yaql_context(data_context):
global ROOT_YAQL_CONTEXT
if not ROOT_YAQL_CONTEXT:
ROOT_YAQL_CONTEXT = yaql.create_context()
_register_yaql_functions(ROOT_YAQL_CONTEXT)
new_ctx = ROOT_YAQL_CONTEXT.create_child_context()
new_ctx['$'] = data_context
if isinstance(data_context, dict):
new_ctx['__env'] = data_context.get('__env')
new_ctx['__execution'] = data_context.get('__execution')
new_ctx['__task_execution'] = data_context.get('__task_execution')
return new_ctx
def get_jinja_context(data_context):
new_ctx = {
'_': data_context
}
_register_jinja_functions(new_ctx)
if isinstance(data_context, dict):
new_ctx['__env'] = data_context.get('__env')
new_ctx['__execution'] = data_context.get('__execution')
new_ctx['__task_execution'] = data_context.get('__task_execution')
return new_ctx
def get_custom_functions():
"""Get custom functions
Retreives the list of custom evaluation functions
"""
functions = dict()
mgr = extension.ExtensionManager(
namespace='mistral.expression.functions',
invoke_on_load=False
)
for name in mgr.names():
functions[name] = mgr[name].plugin
return functions
def _register_yaql_functions(yaql_ctx):
functions = get_custom_functions()
for name in functions:
yaql_ctx.register_function(functions[name], name=name)
def _register_jinja_functions(jinja_ctx):
functions = get_custom_functions()
for name in functions:
jinja_ctx[name] = partial(functions[name], jinja_ctx['_'])
# Additional YAQL functions needed by Mistral.
# If a function name ends with underscore then it doesn't need to pass
# the name of the function when context registers it.
def env_(context):
return context['__env']
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
}
def json_pp_(context, data=None):
return jsonutils.dumps(
data or context,
indent=4
).replace("\\n", "\n").replace(" \n", "\n")
def task_(context, task_name):
# This section may not exist in a context if it's calculated not in
# task scope.
cur_task = context['__task_execution']
if cur_task and 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:
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
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
}
def uuid_(context=None):
return utils.generate_unicode_uuid()