Merge "Add 'interface' field to Workflow resource"
This commit is contained in:
commit
5e90c610f8
|
@ -29,6 +29,7 @@ SCOPE_TYPES = wtypes.Enum(str, 'private', 'public')
|
|||
|
||||
class ScopedResource(object):
|
||||
"""Utilities for scoped resources"""
|
||||
|
||||
@classmethod
|
||||
def validate_scope(cls, scope):
|
||||
if scope not in SCOPE_TYPES.values:
|
||||
|
@ -60,7 +61,7 @@ class Workbook(resource.Resource, ScopedResource):
|
|||
return cls(id='123e4567-e89b-12d3-a456-426655440000',
|
||||
name='book',
|
||||
definition='HERE GOES'
|
||||
'WORKBOOK DEFINITION IN MISTRAL DSL v2',
|
||||
'WORKBOOK DEFINITION IN MISTRAL DSL v2',
|
||||
tags=['large', 'expensive'],
|
||||
scope='private',
|
||||
project_id='a7eb669e9819420ea4bd1453e672c0a7',
|
||||
|
@ -91,9 +92,10 @@ class Workflow(resource.Resource, ScopedResource):
|
|||
name = wtypes.text
|
||||
namespace = wtypes.text
|
||||
input = wtypes.text
|
||||
|
||||
interface = types.jsontype
|
||||
"input and output of the workflow"
|
||||
definition = wtypes.text
|
||||
"Workflow definition in Mistral v2 DSL"
|
||||
"workflow text written in Mistral v2 language"
|
||||
tags = [wtypes.text]
|
||||
scope = SCOPE_TYPES
|
||||
"'private' or 'public'"
|
||||
|
@ -108,16 +110,23 @@ class Workflow(resource.Resource, ScopedResource):
|
|||
name='flow',
|
||||
input='param1, param2',
|
||||
definition='HERE GOES'
|
||||
'WORKFLOW DEFINITION IN MISTRAL DSL v2',
|
||||
'WORKFLOW DEFINITION IN MISTRAL DSL v2',
|
||||
tags=['large', 'expensive'],
|
||||
scope='private',
|
||||
project_id='a7eb669e9819420ea4bd1453e672c0a7',
|
||||
created_at='1970-01-01T00:00:00.000000',
|
||||
updated_at='1970-01-01T00:00:00.000000',
|
||||
namespace='')
|
||||
namespace='',
|
||||
interface={"input": ["param1", {"param2": 2}],
|
||||
"output": []}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _set_input(cls, obj, wf_spec):
|
||||
def _set_attributes_from_spec(self, wf_spec):
|
||||
# Sets input and interface fields for the Workflow resource.
|
||||
self._set_input(wf_spec)
|
||||
self._set_interface(wf_spec)
|
||||
|
||||
def _set_input(self, wf_spec):
|
||||
input_list = []
|
||||
|
||||
if wf_spec:
|
||||
|
@ -130,35 +139,45 @@ class Workflow(resource.Resource, ScopedResource):
|
|||
else:
|
||||
input_list.append(param)
|
||||
|
||||
setattr(obj, 'input', ", ".join(input_list) if input_list else '')
|
||||
self.input = ", ".join(input_list) if input_list else ''
|
||||
|
||||
return obj
|
||||
def _set_interface(self, wf_spec):
|
||||
self.interface = {}
|
||||
|
||||
if wf_spec:
|
||||
self.interface['input'] = wf_spec.get('input', [])
|
||||
self.interface['output'] = [output for output
|
||||
in wf_spec.get('output', {})]
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d):
|
||||
obj = super(Workflow, cls).from_dict(d)
|
||||
obj._set_attributes_from_spec(d.get('spec'))
|
||||
|
||||
return cls._set_input(obj, d.get('spec'))
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def from_db_model(cls, db_model):
|
||||
obj = super(Workflow, cls).from_db_model(db_model)
|
||||
obj._set_attributes_from_spec(db_model.get('spec'))
|
||||
|
||||
return cls._set_input(obj, db_model.spec)
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def from_tuples(cls, tuple_iterator):
|
||||
obj = cls()
|
||||
spec = None
|
||||
|
||||
for col_name, col_val in tuple_iterator:
|
||||
if hasattr(obj, col_name):
|
||||
# Convert all datetime values to strings.
|
||||
setattr(obj, col_name, utils.datetime_to_str(col_val))
|
||||
|
||||
if col_name == 'spec':
|
||||
spec = col_val
|
||||
|
||||
if spec:
|
||||
obj = cls._set_input(obj, spec)
|
||||
obj._set_attributes_from_spec(spec)
|
||||
|
||||
return obj
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import datetime
|
|||
|
||||
from mistral.api.controllers.v2 import resources
|
||||
from mistral.db.v2 import api as db_api
|
||||
from mistral.services import workflows as wf_service
|
||||
from mistral.tests.unit import base
|
||||
from mistral_lib import utils
|
||||
|
||||
|
@ -39,6 +40,37 @@ WF_EXEC = {
|
|||
'some_invalid_field': "foobar"
|
||||
}
|
||||
|
||||
WF = """---
|
||||
version: '2.0'
|
||||
|
||||
WF:
|
||||
type: direct
|
||||
|
||||
input:
|
||||
- no_default_value_input
|
||||
- string_param: "string"
|
||||
- json_param: {
|
||||
string_param: "string"
|
||||
}
|
||||
|
||||
output:
|
||||
output1: 'output'
|
||||
|
||||
tasks:
|
||||
task1:
|
||||
action: std.noop
|
||||
"""
|
||||
WF_NO_PARAMS = """---
|
||||
version: '2.0'
|
||||
|
||||
WF_NO_PARAMS:
|
||||
type: direct
|
||||
|
||||
tasks:
|
||||
task1:
|
||||
action: std.noop
|
||||
"""
|
||||
|
||||
|
||||
class TestRestResource(base.DbTestCase):
|
||||
def test_from_db_model(self):
|
||||
|
@ -57,6 +89,38 @@ class TestRestResource(base.DbTestCase):
|
|||
|
||||
self.assertDictEqual(expected, wf_ex.to_dict())
|
||||
|
||||
def test_from_db_model_workflow_resource(self):
|
||||
expected_interface = {
|
||||
'output': ['output1'],
|
||||
'input': [
|
||||
'no_default_value_input',
|
||||
{'string_param': 'string'},
|
||||
{
|
||||
'json_param': {'string_param': 'string'}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
workflows_list = wf_service.create_workflows(WF)
|
||||
self.assertEqual(1, len(workflows_list))
|
||||
|
||||
wf_resource = resources.Workflow.from_db_model(workflows_list[0])
|
||||
|
||||
self.assertDictEqual(expected_interface, wf_resource.interface)
|
||||
|
||||
def test_from_db_model_workflow_resource_no_params(self):
|
||||
expected_interface = {
|
||||
'input': [],
|
||||
'output': []
|
||||
}
|
||||
|
||||
workflows_list = wf_service.create_workflows(WF_NO_PARAMS)
|
||||
self.assertEqual(1, len(workflows_list))
|
||||
|
||||
wf_resource = resources.Workflow.from_db_model(workflows_list[0])
|
||||
|
||||
self.assertDictEqual(expected_interface, wf_resource.interface)
|
||||
|
||||
def test_from_dict(self):
|
||||
wf_ex = db_api.create_workflow_execution(WF_EXEC)
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
# limitations under the License.
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import mock
|
||||
import pecan
|
||||
import pecan.testing
|
||||
|
@ -70,7 +71,8 @@ WF = {
|
|||
'definition': WF_DEFINITION,
|
||||
'created_at': '1970-01-01 00:00:00',
|
||||
'updated_at': '1970-01-01 00:00:00',
|
||||
'input': 'param1'
|
||||
'input': 'param1',
|
||||
'interface': {"input": ["param1"], "output": []}
|
||||
}
|
||||
|
||||
|
||||
|
@ -296,8 +298,11 @@ class TestKeyCloakOIDCAuthScenarios(base.DbTestCase):
|
|||
with mock.patch("jwt.decode", return_value=token):
|
||||
resp = self.app.get('/v2/workflows/123', headers=headers)
|
||||
|
||||
resp_json = resp.json
|
||||
resp_json['interface'] = json.loads(resp_json['interface'])
|
||||
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertDictEqual(WF, resp.json)
|
||||
self.assertDictEqual(WF, resp_json)
|
||||
|
||||
@mock.patch("requests.get")
|
||||
@mock.patch.object(db_api, 'get_workflow_definition', MOCK_WF)
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
import copy
|
||||
import datetime
|
||||
|
||||
import json
|
||||
import mock
|
||||
import sqlalchemy as sa
|
||||
import yaml
|
||||
|
@ -59,7 +60,8 @@ WF = {
|
|||
'definition': WF_DEFINITION,
|
||||
'created_at': '1970-01-01 00:00:00',
|
||||
'updated_at': '1970-01-01 00:00:00',
|
||||
'input': 'param1'
|
||||
'input': 'param1',
|
||||
'interface': {"output": [], "input": ["param1"]}
|
||||
}
|
||||
|
||||
WF_DB_WITHIN_ABC_NAMESPACE = models.WorkflowDefinition(
|
||||
|
@ -79,7 +81,8 @@ WF_WITH_NAMESPACE = {
|
|||
'definition': WF_DEFINITION,
|
||||
'created_at': '1970-01-01 00:00:00',
|
||||
'updated_at': '1970-01-01 00:00:00',
|
||||
'input': 'param1'
|
||||
'input': 'param1',
|
||||
'interface': {'input': ['param1'], 'output': []}
|
||||
}
|
||||
|
||||
WF_DEFINITION_WITH_INPUT = """
|
||||
|
@ -110,7 +113,11 @@ WF_WITH_DEFAULT_INPUT = {
|
|||
'definition': WF_DEFINITION_WITH_INPUT,
|
||||
'created_at': '1970-01-01 00:00:00',
|
||||
'updated_at': '1970-01-01 00:00:00',
|
||||
'input': 'param1, param2=2'
|
||||
'input': 'param1, param2=2',
|
||||
'interface': {
|
||||
"input": ["param1", {"param2": 2}],
|
||||
"output": []
|
||||
}
|
||||
}
|
||||
|
||||
WF_DB_PROJECT_ID = WF_DB.get_clone()
|
||||
|
@ -250,8 +257,11 @@ class TestWorkflowsController(base.APITest):
|
|||
def test_get(self):
|
||||
resp = self.app.get('/v2/workflows/123')
|
||||
|
||||
resp_json = resp.json
|
||||
resp_json['interface'] = json.loads(resp_json['interface'])
|
||||
|
||||
self.assertEqual(200, resp.status_int)
|
||||
self.assertDictEqual(WF, resp.json)
|
||||
self.assertDictEqual(WF, resp_json)
|
||||
|
||||
@mock.patch.object(db_api, 'get_workflow_definition')
|
||||
def test_get_operational_error(self, mocked_get):
|
||||
|
@ -263,8 +273,11 @@ class TestWorkflowsController(base.APITest):
|
|||
|
||||
resp = self.app.get('/v2/workflows/123')
|
||||
|
||||
resp_json = resp.json
|
||||
resp_json['interface'] = json.loads(resp_json['interface'])
|
||||
|
||||
self.assertEqual(200, resp.status_int)
|
||||
self.assertDictEqual(WF, resp.json)
|
||||
self.assertDictEqual(WF, resp_json)
|
||||
|
||||
@mock.patch.object(db_api, "get_workflow_definition", MOCK_WF_WITH_INPUT)
|
||||
def test_get_with_input(self):
|
||||
|
@ -272,8 +285,11 @@ class TestWorkflowsController(base.APITest):
|
|||
|
||||
self.maxDiff = None
|
||||
|
||||
resp_json = resp.json
|
||||
resp_json['interface'] = json.loads(resp_json['interface'])
|
||||
|
||||
self.assertEqual(200, resp.status_int)
|
||||
self.assertDictEqual(WF_WITH_DEFAULT_INPUT, resp.json)
|
||||
self.assertDictEqual(WF_WITH_DEFAULT_INPUT, resp_json)
|
||||
|
||||
@mock.patch.object(db_api, "get_workflow_definition", MOCK_NOT_FOUND)
|
||||
def test_get_not_found(self):
|
||||
|
@ -416,6 +432,9 @@ class TestWorkflowsController(base.APITest):
|
|||
|
||||
@mock.patch.object(db_api, "update_workflow_definition")
|
||||
def test_put_multiple(self, mock_mtd):
|
||||
spec_mock = mock_mtd.return_value.get.return_value
|
||||
spec_mock.get.return_value = {}
|
||||
|
||||
self.app.put(
|
||||
'/v2/workflows',
|
||||
WFS_DEFINITION,
|
||||
|
@ -499,6 +518,9 @@ class TestWorkflowsController(base.APITest):
|
|||
|
||||
@mock.patch.object(db_api, "create_workflow_definition")
|
||||
def test_post_multiple(self, mock_mtd):
|
||||
spec_mock = mock_mtd.return_value.get.return_value
|
||||
spec_mock.get.return_value = {}
|
||||
|
||||
self.app.post(
|
||||
'/v2/workflows',
|
||||
WFS_DEFINITION,
|
||||
|
@ -564,8 +586,11 @@ class TestWorkflowsController(base.APITest):
|
|||
|
||||
self.assertEqual(200, resp.status_int)
|
||||
|
||||
resp_json = resp.json['workflows'][0]
|
||||
resp_json['interface'] = json.loads(resp_json['interface'])
|
||||
|
||||
self.assertEqual(1, len(resp.json['workflows']))
|
||||
self.assertDictEqual(WF, resp.json['workflows'][0])
|
||||
self.assertDictEqual(WF, resp_json)
|
||||
|
||||
@mock.patch.object(db_api, 'get_workflow_definitions')
|
||||
def test_get_all_operational_error(self, mocked_get_all):
|
||||
|
@ -577,10 +602,13 @@ class TestWorkflowsController(base.APITest):
|
|||
|
||||
resp = self.app.get('/v2/workflows')
|
||||
|
||||
self.assertEqual(200, resp.status_int)
|
||||
resp_workflow_json = resp.json['workflows'][0]
|
||||
resp_workflow_json['interface'] = \
|
||||
json.loads(resp_workflow_json['interface'])
|
||||
|
||||
self.assertEqual(200, resp.status_int)
|
||||
self.assertEqual(1, len(resp.json['workflows']))
|
||||
self.assertDictEqual(WF, resp.json['workflows'][0])
|
||||
self.assertDictEqual(WF, resp_workflow_json)
|
||||
|
||||
@mock.patch.object(db_api, "get_workflow_definitions", MOCK_EMPTY)
|
||||
def test_get_all_empty(self):
|
||||
|
@ -619,8 +647,12 @@ class TestWorkflowsController(base.APITest):
|
|||
|
||||
self.assertIn('next', resp.json)
|
||||
|
||||
resp_workflow_json = resp.json['workflows'][0]
|
||||
resp_workflow_json['interface'] = \
|
||||
json.loads(resp_workflow_json['interface'])
|
||||
|
||||
self.assertEqual(1, len(resp.json['workflows']))
|
||||
self.assertDictEqual(WF, resp.json['workflows'][0])
|
||||
self.assertDictEqual(WF, resp_workflow_json)
|
||||
|
||||
param_dict = utils.get_dict_from_string(
|
||||
resp.json['next'].split('?')[1],
|
||||
|
@ -699,6 +731,17 @@ class TestWorkflowsController(base.APITest):
|
|||
|
||||
@mock.patch('mistral.db.v2.api.get_workflow_definitions')
|
||||
def test_get_all_with_fields_input_filter(self, mock_get_db_wfs):
|
||||
expected_dict = {
|
||||
'id': '65df1f59-938f-4c17-bc2a-562524ef5e40',
|
||||
'input': 'param1, param2=2',
|
||||
'interface': {
|
||||
"output": [],
|
||||
"input": ["param1",
|
||||
{"param2": 2}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def mock_get_defintions(fields=None, session=None, **kwargs):
|
||||
if fields and 'input' in fields:
|
||||
fields.remove('input')
|
||||
|
@ -716,12 +759,11 @@ class TestWorkflowsController(base.APITest):
|
|||
self.assertEqual(200, resp.status_int)
|
||||
self.assertEqual(1, len(resp.json['workflows']))
|
||||
|
||||
expected_dict = {
|
||||
'id': '65df1f59-938f-4c17-bc2a-562524ef5e40',
|
||||
'input': 'param1, param2=2'
|
||||
}
|
||||
resp_workflow_json = resp.json['workflows'][0]
|
||||
resp_workflow_json['interface'] = \
|
||||
json.loads(resp_workflow_json['interface'])
|
||||
|
||||
self.assertDictEqual(expected_dict, resp.json['workflows'][0])
|
||||
self.assertDictEqual(expected_dict, resp_workflow_json)
|
||||
|
||||
def test_get_all_with_invalid_field(self):
|
||||
resp = self.app.get(
|
||||
|
|
|
@ -580,7 +580,7 @@ class WorkflowDefinitionTest(SQLAlchemyTest):
|
|||
self.assertEqual(1, len(fetched))
|
||||
self.assertEqual(created, fetched[0])
|
||||
|
||||
def test_filter_workflow_definition_by_not_equal_valiue(self):
|
||||
def test_filter_workflow_definition_by_not_equal_value(self):
|
||||
created0 = db_api.create_workflow_definition(WF_DEFINITIONS[0])
|
||||
created1 = db_api.create_workflow_definition(WF_DEFINITIONS[1])
|
||||
|
||||
|
|
|
@ -82,6 +82,9 @@ class TestWorkflowPolicy(base.APITest):
|
|||
|
||||
@mock.patch.object(db_api, "create_workflow_definition")
|
||||
def test_workflow_create_allowed(self, mock_obj):
|
||||
spec_mock = mock_obj.return_value.get.return_value
|
||||
spec_mock.get.return_value = {}
|
||||
|
||||
self.policy.change_policy_definition(
|
||||
{"workflows:create": "role:FAKE or rule:admin_or_owner"}
|
||||
)
|
||||
|
@ -111,6 +114,9 @@ class TestWorkflowPolicy(base.APITest):
|
|||
|
||||
@mock.patch.object(db_api, "create_workflow_definition")
|
||||
def test_workflow_create_public_allowed(self, mock_obj):
|
||||
spec_mock = mock_obj.return_value.get.return_value
|
||||
spec_mock.get.return_value = {}
|
||||
|
||||
self.policy.change_policy_definition({
|
||||
"workflows:create": "role:FAKE or rule:admin_or_owner",
|
||||
"workflows:publicize": "role:FAKE or rule:admin_or_owner"
|
||||
|
@ -236,6 +242,9 @@ class TestWorkflowPolicy(base.APITest):
|
|||
|
||||
@mock.patch.object(db_api, "update_workflow_definition")
|
||||
def test_workflow_update_allowed(self, mock_obj):
|
||||
spec_mock = mock_obj.return_value.get.return_value
|
||||
spec_mock.get.return_value = {}
|
||||
|
||||
self.policy.change_policy_definition(
|
||||
{"workflows:update": "role:FAKE or rule:admin_or_owner"}
|
||||
)
|
||||
|
@ -265,6 +274,9 @@ class TestWorkflowPolicy(base.APITest):
|
|||
|
||||
@mock.patch.object(db_api, "update_workflow_definition")
|
||||
def test_workflow_update_public_allowed(self, mock_obj):
|
||||
spec_mock = mock_obj.return_value.get.return_value
|
||||
spec_mock.get.return_value = {}
|
||||
|
||||
self.policy.change_policy_definition({
|
||||
"workflows:update": "role:FAKE or rule:admin_or_owner",
|
||||
"workflows:publicize": "role:FAKE or rule:admin_or_owner"
|
||||
|
|
Loading…
Reference in New Issue