Fix how Mistral prepares data for evaluating a YAQL expression
* If we use the built-in YAQL function 'str' in a workflow then it doesn't represent lists as '[item1, item3, ...]' but instead creates '(item1, item2,...). This is because the standard YAQL function 'yaql_utils.convert_input_data', which is needed to convert a initial user data into an internal YAQL format, converts all sequences (except strings) into tuples. This patch overrides this behavior for sequences that are not strings and tuples so that they now get converted into lists. YAQL uses tuples because it needs to obtain a safe immutable structure to make calculations upon. But in Mistral list is more suitable because lots of users care about string representations. Immutability is not so important because Mistral code base guarantees that the initial data context for an expression won't be changed while an expression is being evaluated by YAQL. * "str" YAQL function used to work well but it was broken in https://review.openstack.org/#/c/477816/ that added additional context preparation in order to fix the issue https://bugs.launchpad.net/mistral/+bug/1772864 Change-Id: I69d32f8772418d586d6c414842bb54aada217481 Closes-Bug: #1815710
This commit is contained in:
parent
58d6634702
commit
a39db2d3dc
@ -13,6 +13,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
import six
|
||||
|
||||
from mistral.db.v2 import api as db_api
|
||||
from mistral.services import workflows as wf_service
|
||||
@ -431,3 +432,42 @@ class YAQLFunctionsEngineTest(engine_test_base.EngineTestCase):
|
||||
self.assertIsNotNone(json_str)
|
||||
self.assertIn('"key1": "foo"', json_str)
|
||||
self.assertIn('"key2": "bar"', json_str)
|
||||
|
||||
def test_built_in_str_function(self):
|
||||
wf_text = """---
|
||||
version: '2.0'
|
||||
|
||||
wf:
|
||||
input:
|
||||
- my_list
|
||||
|
||||
tasks:
|
||||
task1:
|
||||
publish:
|
||||
val: <% str($.my_list) %>
|
||||
"""
|
||||
|
||||
wf_service.create_workflows(wf_text)
|
||||
|
||||
wf_ex = self.engine.start_workflow(
|
||||
'wf',
|
||||
wf_input={
|
||||
'my_list': [
|
||||
{
|
||||
'k1': 'v1',
|
||||
'k2': 'v2'
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
self.await_workflow_success(wf_ex.id)
|
||||
|
||||
with db_api.transaction(read_only=True):
|
||||
wf_ex = db_api.get_workflow_execution(wf_ex.id)
|
||||
|
||||
val = wf_ex.task_executions[0].published['val']
|
||||
|
||||
self.assertIsInstance(val, six.string_types)
|
||||
self.assertIn('[', val)
|
||||
self.assertIn(']', val)
|
||||
|
@ -14,6 +14,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
from functools import partial
|
||||
import six
|
||||
import warnings
|
||||
|
||||
from oslo_log import log as logging
|
||||
@ -39,6 +40,27 @@ LOG = logging.getLogger(__name__)
|
||||
ROOT_YAQL_CONTEXT = None
|
||||
|
||||
|
||||
def _convert_yaql_input_data(obj, rec=None):
|
||||
# NOTE(rakhmerov): We have to define our own wrapper function
|
||||
# around the function 'convert_input_data' from 'yaql_utils'
|
||||
# because the latter always converts all sequences (except strings)
|
||||
# into tuples, and it in turn breaks a number of things. For example,
|
||||
# if we use the built-in 'str' YAQL function with an argument of the
|
||||
# type 'list' then the result will be '(item1, item2 ..., itemN,)'
|
||||
# instead of '[item1, item2 ..., itemN]'.
|
||||
# So we override this behavior for sequences that are not strings and
|
||||
# tuples.
|
||||
if rec is None:
|
||||
rec = _convert_yaql_input_data
|
||||
|
||||
if (isinstance(obj, yaql_utils.SequenceType) and
|
||||
not isinstance(obj, six.string_types) and
|
||||
not isinstance(obj, tuple)):
|
||||
return list(rec(t, rec) for t in obj)
|
||||
else:
|
||||
return yaql_utils.convert_input_data(obj, rec)
|
||||
|
||||
|
||||
def get_yaql_context(data_context):
|
||||
global ROOT_YAQL_CONTEXT
|
||||
|
||||
@ -48,7 +70,7 @@ def get_yaql_context(data_context):
|
||||
_register_yaql_functions(ROOT_YAQL_CONTEXT)
|
||||
|
||||
new_ctx = ROOT_YAQL_CONTEXT.create_child_context()
|
||||
new_ctx['$'] = yaql_utils.convert_input_data(data_context)
|
||||
new_ctx['$'] = _convert_yaql_input_data(data_context)
|
||||
|
||||
if isinstance(data_context, dict):
|
||||
new_ctx['__env'] = data_context.get('__env')
|
||||
|
Loading…
Reference in New Issue
Block a user