Refactor get_task_spec using mechanism of polymorphic DSL entities
* Changing the way of getting task spec using polymorphic mechanism, which is the motivation of this patch. * Moving the type injection in TaskSpec to WorkflowSpec, the 'type' param is already there when TaskSpec initializing, and we could also continue to use that for polymorphic mechanism of TaskSpec, and the subclasses iteration mechanism can also be removed as well. * Improving the schema validation for TaskSpec, replace the code logic of prohibition of co-existance between action and workflow in task spec with jsonschema restriction. Change-Id: Ia88ce390298d54afc1ee72ebfb67de9056d9f444
This commit is contained in:
parent
ffe6524573
commit
7a12e30d07
@ -356,8 +356,8 @@ class ExecutionTestsV2(base.TestCase):
|
|||||||
self.client.delete_obj('workflows', wf)
|
self.client.delete_obj('workflows', wf)
|
||||||
self.client.workflows = []
|
self.client.workflows = []
|
||||||
|
|
||||||
for wf in self.client.executions:
|
for ex in self.client.executions:
|
||||||
self.client.delete_obj('executions', wf)
|
self.client.delete_obj('executions', ex)
|
||||||
self.client.executions = []
|
self.client.executions = []
|
||||||
|
|
||||||
super(ExecutionTestsV2, self).tearDown()
|
super(ExecutionTestsV2, self).tearDown()
|
||||||
|
@ -146,8 +146,7 @@ class TestWorkbooksController(base.FunctionalTest):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(resp.status_int, 400)
|
self.assertEqual(resp.status_int, 400)
|
||||||
self.assertIn("Task properties 'action' and 'workflow' "
|
self.assertIn("Invalid DSL", resp.body)
|
||||||
"can't be specified both", resp.body)
|
|
||||||
|
|
||||||
@mock.patch.object(workbooks, "create_workbook_v2", MOCK_WORKBOOK)
|
@mock.patch.object(workbooks, "create_workbook_v2", MOCK_WORKBOOK)
|
||||||
def test_post(self):
|
def test_post(self):
|
||||||
@ -180,8 +179,7 @@ class TestWorkbooksController(base.FunctionalTest):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(resp.status_int, 400)
|
self.assertEqual(resp.status_int, 400)
|
||||||
self.assertIn("Task properties 'action' and 'workflow' "
|
self.assertIn("Invalid DSL", resp.body)
|
||||||
"can't be specified both", resp.body)
|
|
||||||
|
|
||||||
@mock.patch.object(db_api, "delete_workbook", MOCK_DELETE)
|
@mock.patch.object(db_api, "delete_workbook", MOCK_DELETE)
|
||||||
def test_delete(self):
|
def test_delete(self):
|
||||||
@ -232,8 +230,7 @@ class TestWorkbooksController(base.FunctionalTest):
|
|||||||
|
|
||||||
self.assertEqual(resp.status_int, 200)
|
self.assertEqual(resp.status_int, 200)
|
||||||
self.assertFalse(resp.json['valid'])
|
self.assertFalse(resp.json['valid'])
|
||||||
self.assertIn("Task properties 'action' and 'workflow' "
|
self.assertIn("Invalid DSL", resp.json['error'])
|
||||||
"can't be specified both", resp.json['error'])
|
|
||||||
|
|
||||||
def test_validate_dsl_parse_exception(self):
|
def test_validate_dsl_parse_exception(self):
|
||||||
resp = self.app.post(
|
resp = self.app.post(
|
||||||
|
@ -221,8 +221,7 @@ class TestWorkflowsController(base.FunctionalTest):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(resp.status_int, 400)
|
self.assertEqual(resp.status_int, 400)
|
||||||
self.assertIn("Task properties 'action' and 'workflow' "
|
self.assertIn("Invalid DSL", resp.body)
|
||||||
"can't be specified both", resp.body)
|
|
||||||
|
|
||||||
@mock.patch.object(db_api, "create_workflow_definition")
|
@mock.patch.object(db_api, "create_workflow_definition")
|
||||||
def test_post(self, mock_mtd):
|
def test_post(self, mock_mtd):
|
||||||
@ -264,8 +263,7 @@ class TestWorkflowsController(base.FunctionalTest):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(resp.status_int, 400)
|
self.assertEqual(resp.status_int, 400)
|
||||||
self.assertIn("Task properties 'action' and 'workflow' "
|
self.assertIn("Invalid DSL", resp.body)
|
||||||
"can't be specified both", resp.body)
|
|
||||||
|
|
||||||
@mock.patch.object(db_api, "delete_workflow_definition", MOCK_DELETE)
|
@mock.patch.object(db_api, "delete_workflow_definition", MOCK_DELETE)
|
||||||
def test_delete(self):
|
def test_delete(self):
|
||||||
@ -416,8 +414,7 @@ class TestWorkflowsController(base.FunctionalTest):
|
|||||||
|
|
||||||
self.assertEqual(resp.status_int, 200)
|
self.assertEqual(resp.status_int, 200)
|
||||||
self.assertFalse(resp.json['valid'])
|
self.assertFalse(resp.json['valid'])
|
||||||
self.assertIn("Task properties 'action' and 'workflow' "
|
self.assertIn("Invalid DSL", resp.json['error'])
|
||||||
"can't be specified both", resp.json['error'])
|
|
||||||
|
|
||||||
def test_validate_dsl_parse_exception(self):
|
def test_validate_dsl_parse_exception(self):
|
||||||
resp = self.app.post(
|
resp = self.app.post(
|
||||||
|
@ -15,33 +15,14 @@
|
|||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from mistral import exceptions
|
|
||||||
from mistral.tests import base
|
|
||||||
from mistral.tests.unit.workbook.v2 import base as v2_base
|
from mistral.tests.unit.workbook.v2 import base as v2_base
|
||||||
from mistral import utils
|
from mistral import utils
|
||||||
from mistral.workbook.v2 import tasks
|
|
||||||
from mistral.workbook.v2 import workflows
|
from mistral.workbook.v2 import workflows
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TaskSpecListTest(base.BaseTest):
|
|
||||||
def test_get_class(self):
|
|
||||||
spec_list_cls = tasks.TaskSpecList.get_class('direct')
|
|
||||||
|
|
||||||
self.assertIs(spec_list_cls, tasks.DirectWfTaskSpecList)
|
|
||||||
|
|
||||||
def test_get_class_notfound(self):
|
|
||||||
exc = self.assertRaises(
|
|
||||||
exceptions.NotFoundException,
|
|
||||||
tasks.TaskSpecList.get_class,
|
|
||||||
"invalid"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertIn("Can not find task list specification", str(exc))
|
|
||||||
|
|
||||||
|
|
||||||
class TaskSpecValidation(v2_base.WorkflowSpecValidationTestCase):
|
class TaskSpecValidation(v2_base.WorkflowSpecValidationTestCase):
|
||||||
def test_type_injection(self):
|
def test_type_injection(self):
|
||||||
tests = [
|
tests = [
|
||||||
|
@ -20,7 +20,6 @@ import yaml
|
|||||||
from mistral import exceptions as exc
|
from mistral import exceptions as exc
|
||||||
from mistral.tests.unit.workbook.v2 import base
|
from mistral.tests.unit.workbook.v2 import base
|
||||||
from mistral import utils
|
from mistral import utils
|
||||||
from mistral.workbook.v2 import tasks
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -56,8 +55,6 @@ class WorkflowSpecValidation(base.WorkflowSpecValidationTestCase):
|
|||||||
self.assertEqual(1, len(wfs_spec.get_workflows()))
|
self.assertEqual(1, len(wfs_spec.get_workflows()))
|
||||||
self.assertEqual('test', wfs_spec.get_workflows()[0].get_name())
|
self.assertEqual('test', wfs_spec.get_workflows()[0].get_name())
|
||||||
self.assertEqual('direct', wfs_spec.get_workflows()[0].get_type())
|
self.assertEqual('direct', wfs_spec.get_workflows()[0].get_type())
|
||||||
self.assertIsInstance(wfs_spec.get_workflows()[0].get_tasks(),
|
|
||||||
tasks.DirectWfTaskSpecList)
|
|
||||||
|
|
||||||
def test_direct_workflow_invalid_task(self):
|
def test_direct_workflow_invalid_task(self):
|
||||||
overlay = {
|
overlay = {
|
||||||
@ -102,8 +99,6 @@ class WorkflowSpecValidation(base.WorkflowSpecValidationTestCase):
|
|||||||
self.assertEqual(1, len(wfs_spec.get_workflows()))
|
self.assertEqual(1, len(wfs_spec.get_workflows()))
|
||||||
self.assertEqual('test', wfs_spec.get_workflows()[0].get_name())
|
self.assertEqual('test', wfs_spec.get_workflows()[0].get_name())
|
||||||
self.assertEqual('reverse', wfs_spec.get_workflows()[0].get_type())
|
self.assertEqual('reverse', wfs_spec.get_workflows()[0].get_type())
|
||||||
self.assertIsInstance(wfs_spec.get_workflows()[0].get_tasks(),
|
|
||||||
tasks.ReverseWfTaskSpecList)
|
|
||||||
|
|
||||||
def test_reverse_workflow_invalid_task(self):
|
def test_reverse_workflow_invalid_task(self):
|
||||||
overlay = {'test': {'type': 'reverse', 'tasks': {}}}
|
overlay = {'test': {'type': 'reverse', 'tasks': {}}}
|
||||||
|
@ -115,13 +115,6 @@ def get_workflow_list_spec_from_yaml(text):
|
|||||||
|
|
||||||
def get_task_spec(spec_dict):
|
def get_task_spec(spec_dict):
|
||||||
if _get_spec_version(spec_dict) == V2_0:
|
if _get_spec_version(spec_dict) == V2_0:
|
||||||
workflow_type = spec_dict.get('type')
|
return base.instantiate_spec(tasks_v2.TaskSpec, spec_dict)
|
||||||
|
|
||||||
if workflow_type == 'direct':
|
|
||||||
return tasks_v2.DirectWorkflowTaskSpec(spec_dict)
|
|
||||||
elif workflow_type == 'reverse':
|
|
||||||
return tasks_v2.ReverseWorkflowTaskSpec(spec_dict)
|
|
||||||
else:
|
|
||||||
raise Exception('Unsupported workflow type "%s".' % workflow_type)
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
@ -33,7 +33,7 @@ WITH_ITEMS_PTRN = re.compile(
|
|||||||
|
|
||||||
class TaskSpec(base.BaseSpec):
|
class TaskSpec(base.BaseSpec):
|
||||||
# See http://json-schema.org
|
# See http://json-schema.org
|
||||||
_type = None
|
_polymorphic_key = ('type', 'direct')
|
||||||
|
|
||||||
_schema = {
|
_schema = {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -58,7 +58,27 @@ class TaskSpec(base.BaseSpec):
|
|||||||
"target": types.NONEMPTY_STRING,
|
"target": types.NONEMPTY_STRING,
|
||||||
"keep-result": types.YAQL_OR_BOOLEAN
|
"keep-result": types.YAQL_OR_BOOLEAN
|
||||||
},
|
},
|
||||||
"additionalProperties": False
|
"additionalProperties": False,
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"not": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["action", "workflow"]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["action"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["workflow"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
@ -83,22 +103,16 @@ class TaskSpec(base.BaseSpec):
|
|||||||
self._target = data.get('target')
|
self._target = data.get('target')
|
||||||
self._keep_result = data.get('keep-result', True)
|
self._keep_result = data.get('keep-result', True)
|
||||||
|
|
||||||
self._inject_type()
|
|
||||||
self._process_action_and_workflow()
|
self._process_action_and_workflow()
|
||||||
|
|
||||||
def validate_schema(self):
|
def validate_schema(self):
|
||||||
super(TaskSpec, self).validate_schema()
|
super(TaskSpec, self).validate_schema()
|
||||||
|
|
||||||
|
self._transform_with_items()
|
||||||
|
|
||||||
action = self._data.get('action')
|
action = self._data.get('action')
|
||||||
workflow = self._data.get('workflow')
|
workflow = self._data.get('workflow')
|
||||||
|
|
||||||
if action and workflow:
|
|
||||||
msg = ("Task properties 'action' and 'workflow' can't be"
|
|
||||||
" specified both: %s" % self._data)
|
|
||||||
raise exc.InvalidModelException(msg)
|
|
||||||
|
|
||||||
self._transform_with_items()
|
|
||||||
|
|
||||||
# Validate YAQL expressions.
|
# Validate YAQL expressions.
|
||||||
if action or workflow:
|
if action or workflow:
|
||||||
inline_params = self._parse_cmd_and_input(action or workflow)[1]
|
inline_params = self._parse_cmd_and_input(action or workflow)[1]
|
||||||
@ -146,10 +160,6 @@ class TaskSpec(base.BaseSpec):
|
|||||||
|
|
||||||
return with_items
|
return with_items
|
||||||
|
|
||||||
def _inject_type(self):
|
|
||||||
if self._type:
|
|
||||||
self._data['type'] = self._type
|
|
||||||
|
|
||||||
def _process_action_and_workflow(self):
|
def _process_action_and_workflow(self):
|
||||||
params = {}
|
params = {}
|
||||||
|
|
||||||
@ -198,7 +208,7 @@ class TaskSpec(base.BaseSpec):
|
|||||||
|
|
||||||
|
|
||||||
class DirectWorkflowTaskSpec(TaskSpec):
|
class DirectWorkflowTaskSpec(TaskSpec):
|
||||||
_type = 'direct'
|
_polymorphic_value = 'direct'
|
||||||
|
|
||||||
_on_clause_type = {
|
_on_clause_type = {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
@ -210,7 +220,7 @@ class DirectWorkflowTaskSpec(TaskSpec):
|
|||||||
_direct_workflow_schema = {
|
_direct_workflow_schema = {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {"enum": [_type]},
|
"type": {"enum": [_polymorphic_value]},
|
||||||
"join": {
|
"join": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{"enum": ["all", "one"]},
|
{"enum": ["all", "one"]},
|
||||||
@ -270,12 +280,12 @@ class DirectWorkflowTaskSpec(TaskSpec):
|
|||||||
|
|
||||||
|
|
||||||
class ReverseWorkflowTaskSpec(TaskSpec):
|
class ReverseWorkflowTaskSpec(TaskSpec):
|
||||||
_type = 'reverse'
|
_polymorphic_value = 'reverse'
|
||||||
|
|
||||||
_reverse_workflow_schema = {
|
_reverse_workflow_schema = {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {"enum": [_type]},
|
"type": {"enum": [_polymorphic_value]},
|
||||||
"requires": {
|
"requires": {
|
||||||
"oneOf": [types.NONEMPTY_STRING, types.UNIQUE_STRING_LIST]
|
"oneOf": [types.NONEMPTY_STRING, types.UNIQUE_STRING_LIST]
|
||||||
}
|
}
|
||||||
@ -299,28 +309,3 @@ class ReverseWorkflowTaskSpec(TaskSpec):
|
|||||||
|
|
||||||
class TaskSpecList(base.BaseSpecList):
|
class TaskSpecList(base.BaseSpecList):
|
||||||
item_class = TaskSpec
|
item_class = TaskSpec
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_class(wf_type):
|
|
||||||
"""Gets a task specification list class by given workflow type.
|
|
||||||
|
|
||||||
:param wf_type: Workflow type
|
|
||||||
:returns: Task specification list class
|
|
||||||
"""
|
|
||||||
for spec_list_cls in utils.iter_subclasses(TaskSpecList):
|
|
||||||
if wf_type == spec_list_cls.__type__:
|
|
||||||
return spec_list_cls
|
|
||||||
|
|
||||||
msg = ("Can not find task list specification with workflow type:"
|
|
||||||
" %s" % wf_type)
|
|
||||||
raise exc.NotFoundException(msg)
|
|
||||||
|
|
||||||
|
|
||||||
class DirectWfTaskSpecList(TaskSpecList):
|
|
||||||
__type__ = 'direct'
|
|
||||||
item_class = DirectWorkflowTaskSpec
|
|
||||||
|
|
||||||
|
|
||||||
class ReverseWfTaskSpecList(TaskSpecList):
|
|
||||||
__type__ = 'reverse'
|
|
||||||
item_class = ReverseWorkflowTaskSpec
|
|
||||||
|
@ -57,10 +57,13 @@ class WorkflowSpec(base.BaseSpec):
|
|||||||
'task-defaults',
|
'task-defaults',
|
||||||
task_defaults.TaskDefaultsSpec
|
task_defaults.TaskDefaultsSpec
|
||||||
)
|
)
|
||||||
self._tasks = self._spec_property(
|
|
||||||
'tasks',
|
# Inject 'type' here, so instantiate_spec function can recognize the
|
||||||
tasks.TaskSpecList.get_class(self._type)
|
# specific subclass of TaskSpec.
|
||||||
)
|
for task in self._data.get('tasks').itervalues():
|
||||||
|
task['type'] = self._type
|
||||||
|
|
||||||
|
self._tasks = self._spec_property('tasks', tasks.TaskSpecList)
|
||||||
|
|
||||||
def validate_schema(self):
|
def validate_schema(self):
|
||||||
super(WorkflowSpec, self).validate_schema()
|
super(WorkflowSpec, self).validate_schema()
|
||||||
|
Loading…
Reference in New Issue
Block a user