Working on Data Flow (step 2)

* Added ExpressionEvaluator interface and YAQL implementation
* Removed yaql_utils, evaluator is now a more flexible replacement
* Fixed the places where yaql_utils was used
* Adjusted test cases

Change-Id: I7de70e678c211df34788763047b8361de0d3902a
This commit is contained in:
Renat Akhmerov 2014-02-20 17:22:04 +07:00
parent 58407b6f94
commit b3c23b7e1b
5 changed files with 81 additions and 40 deletions

View File

@ -64,7 +64,7 @@ def get_rest_action(task):
# input_yaql = task.get('input')
# TODO(nmakhotkin) extract input from context within the YAQL expression
task_input = {} # yaql_utils.evaluate(input_yaql, ctx)
task_input = {} # expressions.evaluate(input_expr, ctx)
task_data = {}
if method.upper() == "GET":

View File

@ -17,7 +17,7 @@
from mistral.engine.actions import action_types as a_t
from mistral import exceptions as exc
from mistral.engine import states
from mistral.utils import yaql_utils
from mistral.engine import expressions as expr
def get_action_type(task):
@ -30,16 +30,17 @@ def is_task_synchronous(task):
def extract_state_result(action, action_result):
# All non-Mistral tasks are sync-auto because service doesn't know
# about Mistral and we need to receive the result immediately
# about Mistral and we need to receive the result immediately.
if action.type != a_t.MISTRAL_REST_API:
if action.result_helper.get('select'):
result = yaql_utils.evaluate(action.result_helper['select'],
result = expr.evaluate(action.result_helper['select'],
action_result)
# TODO(nmakhotkin) get state for other actions
state = states.get_state_by_http_status_code(action.status)
else:
raise exc.InvalidActionException("Cannot get the result of sync "
"task without YAQL expression")
return state, result
raise exc.InvalidActionException("Error. Wrong type of action to "
"retrieve the result")

View File

@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
#
# Copyright 2013 - Mirantis, 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 abc
import yaql
from mistral.openstack.common import log as logging
LOG = logging.getLogger(__name__)
class Evaluator(object):
"""Expression evaluator interface.
Having this interface gives the flexibility to change the actual expression
language used in Mistral DSL for conditions, output calculation etc.
"""
@classmethod
@abc.abstractmethod
def evaluate(cls, expression, context):
"""Evaluates the expression against the given data context.
:param expression: Expression string
:param context: Data context
:return: Expression result
"""
pass
class YAQLEvaluator(Evaluator):
@classmethod
def evaluate(cls, expression, context):
LOG.debug("Evaluating YAQL expression [expression='%s', context=%s]")
return yaql.parse(expression).evaluate(context)
# TODO(rakhmerov): Make it configurable.
_EVALUATOR = YAQLEvaluator()
def evaluate(expression, context):
_EVALUATOR.evaluate(expression, context)

View File

@ -16,7 +16,7 @@
import unittest2
from mistral.utils import yaql_utils
from mistral.engine import expressions as expr
DATA = {
@ -44,33 +44,37 @@ SERVERS = {
}
class YaqlTest(unittest2.TestCase):
class YaqlEvaluatorTest(unittest2.TestCase):
def setUp(self):
super(YaqlEvaluatorTest, self).setUp()
self._evaluator = expr.YAQLEvaluator()
def test_expression_result(self):
res = yaql_utils.evaluate("$.server", DATA)
res = self._evaluator.evaluate('$.server', DATA)
self.assertEqual(res, {
"id": "03ea824a-aa24-4105-9131-66c48ae54acf",
"name": "cloud-fedora",
"status": "ACTIVE"
'id': "03ea824a-aa24-4105-9131-66c48ae54acf",
'name': 'cloud-fedora',
'status': 'ACTIVE'
})
res = yaql_utils.evaluate("$.server.id", DATA)
self.assertEqual(res, "03ea824a-aa24-4105-9131-66c48ae54acf")
res = self._evaluator.evaluate('$.server.id', DATA)
self.assertEqual(res, '03ea824a-aa24-4105-9131-66c48ae54acf')
res = yaql_utils.evaluate("$.server.status = 'ACTIVE'", DATA)
res = self._evaluator.evaluate("$.server.status = 'ACTIVE'", DATA)
self.assertTrue(res)
def test_wrong_expression(self):
res = yaql_utils.evaluate("$.status = 'Invalid value'", DATA)
res = self._evaluator.evaluate("$.status = 'Invalid value'", DATA)
self.assertFalse(res)
res = yaql_utils.evaluate("$.wrong_key", DATA)
res = self._evaluator.evaluate('$.wrong_key', DATA)
self.assertIsNone(res)
expression_str = "invalid_expression_string"
res = yaql_utils.evaluate(expression_str, DATA)
expression_str = 'invalid_expression_string'
res = self._evaluator.evaluate(expression_str, DATA)
self.assertEqual(res, expression_str)
def test_select_result(self):
res = yaql_utils.evaluate("$.servers[$.name = ubuntu]", SERVERS)
res = self._evaluator.evaluate('$.servers[$.name = ubuntu]', SERVERS)
item = list(res)[0]
self.assertEqual(item, {'name': 'ubuntu'})

View File

@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright 2013 - Mirantis, 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 yaql
def evaluate(expression_str, data):
return yaql.parse(expression_str).evaluate(data)