Add SWF endpoint: StartWorkflowExecution
This commit is contained in:
		
							parent
							
								
									fbcdd5f2bd
								
							
						
					
					
						commit
						92cf64c2ad
					
				| @ -56,3 +56,10 @@ class SWFTypeDeprecatedFault(SWFClientError): | |||||||
|             "{}=[name={}, version={}]".format(_type.__class__.__name__, _type.name, _type.version), |             "{}=[name={}, version={}]".format(_type.__class__.__name__, _type.name, _type.version), | ||||||
|             "com.amazonaws.swf.base.model#TypeDeprecatedFault") |             "com.amazonaws.swf.base.model#TypeDeprecatedFault") | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | class SWFWorkflowExecutionAlreadyStartedFault(JSONResponseError): | ||||||
|  |     def __init__(self): | ||||||
|  |         super(SWFWorkflowExecutionAlreadyStartedFault, self).__init__( | ||||||
|  |             400, "Bad Request", | ||||||
|  |             body={"__type": "com.amazonaws.swf.base.model#WorkflowExecutionAlreadyStartedFault"} | ||||||
|  |         ) | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
| from collections import defaultdict | from collections import defaultdict | ||||||
|  | import uuid | ||||||
| 
 | 
 | ||||||
| import boto.swf | import boto.swf | ||||||
| 
 | 
 | ||||||
| @ -13,6 +14,7 @@ from .exceptions import ( | |||||||
|     SWFSerializationException, |     SWFSerializationException, | ||||||
|     SWFTypeAlreadyExistsFault, |     SWFTypeAlreadyExistsFault, | ||||||
|     SWFTypeDeprecatedFault, |     SWFTypeDeprecatedFault, | ||||||
|  |     SWFWorkflowExecutionAlreadyStartedFault, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -26,6 +28,12 @@ class Domain(object): | |||||||
|             "activity": defaultdict(dict), |             "activity": defaultdict(dict), | ||||||
|             "workflow": defaultdict(dict), |             "workflow": defaultdict(dict), | ||||||
|         } |         } | ||||||
|  |         # Workflow executions have an id, which unicity is guaranteed | ||||||
|  |         # at domain level (not super clear in the docs, but I checked | ||||||
|  |         # that against SWF API) ; hence the storage method as a dict | ||||||
|  |         # of "workflow_id (client determined)" => WorkflowExecution() | ||||||
|  |         # here. | ||||||
|  |         self.workflow_executions = {} | ||||||
| 
 | 
 | ||||||
|     def __repr__(self): |     def __repr__(self): | ||||||
|         return "Domain(name: %(name)s, status: %(status)s)" % self.__dict__ |         return "Domain(name: %(name)s, status: %(status)s)" % self.__dict__ | ||||||
| @ -62,6 +70,11 @@ class Domain(object): | |||||||
|                     _all.append(_type) |                     _all.append(_type) | ||||||
|         return _all |         return _all | ||||||
| 
 | 
 | ||||||
|  |     def add_workflow_execution(self, workflow_execution_id, workflow_execution): | ||||||
|  |         if self.workflow_executions.get(workflow_execution_id): | ||||||
|  |             raise SWFWorkflowExecutionAlreadyStartedFault() | ||||||
|  |         self.workflow_executions[workflow_execution_id] = workflow_execution | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class GenericType(object): | class GenericType(object): | ||||||
|     def __init__(self, name, version, **kwargs): |     def __init__(self, name, version, **kwargs): | ||||||
| @ -120,6 +133,7 @@ class GenericType(object): | |||||||
|             hsh["configuration"][key] = getattr(self, attr) |             hsh["configuration"][key] = getattr(self, attr) | ||||||
|         return hsh |         return hsh | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class ActivityType(GenericType): | class ActivityType(GenericType): | ||||||
|     @property |     @property | ||||||
|     def _configuration_keys(self): |     def _configuration_keys(self): | ||||||
| @ -149,6 +163,17 @@ class WorkflowType(GenericType): | |||||||
|         return "workflow" |         return "workflow" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class WorkflowExecution(object): | ||||||
|  |     def __init__(self, workflow_type, **kwargs): | ||||||
|  |         self.workflow_type = workflow_type | ||||||
|  |         self.run_id = uuid.uuid4().hex | ||||||
|  |         for key, value in kwargs.iteritems(): | ||||||
|  |             self.__setattr__(key, value) | ||||||
|  | 
 | ||||||
|  |     def __repr__(self): | ||||||
|  |         return "WorkflowExecution(run_id: {})".format(self.run_id) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class SWFBackend(BaseBackend): | class SWFBackend(BaseBackend): | ||||||
|     def __init__(self, region_name): |     def __init__(self, region_name): | ||||||
|         self.region_name = region_name |         self.region_name = region_name | ||||||
| @ -168,10 +193,25 @@ class SWFBackend(BaseBackend): | |||||||
|             return matching[0] |             return matching[0] | ||||||
|         return None |         return None | ||||||
| 
 | 
 | ||||||
|  |     def _check_none_or_string(self, parameter): | ||||||
|  |         if parameter is not None: | ||||||
|  |             self._check_string(parameter) | ||||||
|  | 
 | ||||||
|     def _check_string(self, parameter): |     def _check_string(self, parameter): | ||||||
|         if not isinstance(parameter, basestring): |         if not isinstance(parameter, basestring): | ||||||
|             raise SWFSerializationException(parameter) |             raise SWFSerializationException(parameter) | ||||||
| 
 | 
 | ||||||
|  |     def _check_none_or_list_of_strings(self, parameter): | ||||||
|  |         if parameter is not None: | ||||||
|  |             self._check_list_of_strings(parameter) | ||||||
|  | 
 | ||||||
|  |     def _check_list_of_strings(self, parameter): | ||||||
|  |         if not isinstance(parameter, list): | ||||||
|  |             raise SWFSerializationException(parameter) | ||||||
|  |         for i in parameter: | ||||||
|  |             if not isinstance(i, basestring): | ||||||
|  |                 raise SWFSerializationException(parameter) | ||||||
|  | 
 | ||||||
|     def list_domains(self, status, reverse_order=None): |     def list_domains(self, status, reverse_order=None): | ||||||
|         self._check_string(status) |         self._check_string(status) | ||||||
|         domains = [domain for domain in self.domains |         domains = [domain for domain in self.domains | ||||||
| @ -185,8 +225,7 @@ class SWFBackend(BaseBackend): | |||||||
|                         description=None): |                         description=None): | ||||||
|         self._check_string(name) |         self._check_string(name) | ||||||
|         self._check_string(workflow_execution_retention_period_in_days) |         self._check_string(workflow_execution_retention_period_in_days) | ||||||
|         if description: |         self._check_none_or_string(description) | ||||||
|             self._check_string(description) |  | ||||||
|         if self._get_domain(name, ignore_empty=True): |         if self._get_domain(name, ignore_empty=True): | ||||||
|             raise SWFDomainAlreadyExistsFault(name) |             raise SWFDomainAlreadyExistsFault(name) | ||||||
|         domain = Domain(name, workflow_execution_retention_period_in_days, |         domain = Domain(name, workflow_execution_retention_period_in_days, | ||||||
| @ -219,10 +258,7 @@ class SWFBackend(BaseBackend): | |||||||
|         self._check_string(name) |         self._check_string(name) | ||||||
|         self._check_string(version) |         self._check_string(version) | ||||||
|         for _, value in kwargs.iteritems(): |         for _, value in kwargs.iteritems(): | ||||||
|             if value == (None,): |             self._check_none_or_string(value) | ||||||
|                 print _ |  | ||||||
|             if value is not None: |  | ||||||
|                 self._check_string(value) |  | ||||||
|         domain = self._get_domain(domain_name) |         domain = self._get_domain(domain_name) | ||||||
|         _type = domain.get_type(kind, name, version, ignore_empty=True) |         _type = domain.get_type(kind, name, version, ignore_empty=True) | ||||||
|         if _type: |         if _type: | ||||||
| @ -248,6 +284,29 @@ class SWFBackend(BaseBackend): | |||||||
|         domain = self._get_domain(domain_name) |         domain = self._get_domain(domain_name) | ||||||
|         return domain.get_type(kind, name, version) |         return domain.get_type(kind, name, version) | ||||||
| 
 | 
 | ||||||
|  |     # TODO: find what triggers a "DefaultUndefinedFault" and implement it | ||||||
|  |     # (didn't found in boto source code, nor in the docs, nor on a Google search) | ||||||
|  |     # (will try to reach support) | ||||||
|  |     def start_workflow_execution(self, domain_name, workflow_execution_id, | ||||||
|  |                                  workflow_name, workflow_version, | ||||||
|  |                                  tag_list=None, **kwargs): | ||||||
|  |         self._check_string(domain_name) | ||||||
|  |         self._check_string(workflow_execution_id) | ||||||
|  |         self._check_string(workflow_name) | ||||||
|  |         self._check_string(workflow_version) | ||||||
|  |         self._check_none_or_list_of_strings(tag_list) | ||||||
|  |         for _, value in kwargs.iteritems(): | ||||||
|  |             self._check_none_or_string(value) | ||||||
|  | 
 | ||||||
|  |         domain = self._get_domain(domain_name) | ||||||
|  |         wf_type = domain.get_type("workflow", workflow_name, workflow_version) | ||||||
|  |         if wf_type.status == "DEPRECATED": | ||||||
|  |             raise SWFTypeDeprecatedFault(wf_type) | ||||||
|  |         wfe = WorkflowExecution(wf_type, tag_list=tag_list, **kwargs) | ||||||
|  |         domain.add_workflow_execution(workflow_execution_id, wfe) | ||||||
|  | 
 | ||||||
|  |         return wfe | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| swf_backends = {} | swf_backends = {} | ||||||
| for region in boto.swf.regions(): | for region in boto.swf.regions(): | ||||||
|  | |||||||
| @ -175,3 +175,32 @@ class SWFResponse(BaseResponse): | |||||||
| 
 | 
 | ||||||
|     def describe_workflow_type(self): |     def describe_workflow_type(self): | ||||||
|         return self._describe_type("workflow") |         return self._describe_type("workflow") | ||||||
|  | 
 | ||||||
|  |     def start_workflow_execution(self): | ||||||
|  |         domain = self._params["domain"] | ||||||
|  |         workflow_id = self._params["workflowId"] | ||||||
|  |         _workflow_type = self._params["workflowType"] | ||||||
|  |         workflow_name = _workflow_type["name"] | ||||||
|  |         workflow_version = _workflow_type["version"] | ||||||
|  |         _default_task_list = self._params.get("defaultTaskList") | ||||||
|  |         if _default_task_list: | ||||||
|  |             task_list = _default_task_list.get("name") | ||||||
|  |         else: | ||||||
|  |             task_list = None | ||||||
|  |         child_policy = self._params.get("childPolicy") | ||||||
|  |         execution_start_to_close_timeout = self._params.get("executionStartToCloseTimeout") | ||||||
|  |         input_ = self._params.get("input") | ||||||
|  |         tag_list = self._params.get("tagList") | ||||||
|  |         task_start_to_close_timeout = self._params.get("taskStartToCloseTimeout") | ||||||
|  | 
 | ||||||
|  |         wfe = self.swf_backend.start_workflow_execution( | ||||||
|  |             domain, workflow_id, workflow_name, workflow_version, | ||||||
|  |             task_list=task_list, child_policy=child_policy, | ||||||
|  |             execution_start_to_close_timeout=execution_start_to_close_timeout, | ||||||
|  |             input=input_, tag_list=tag_list, | ||||||
|  |             task_start_to_close_timeout=task_start_to_close_timeout | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         return json.dumps({ | ||||||
|  |             "runId": wfe.run_id | ||||||
|  |         }) | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ from sure import expect | |||||||
| from moto.swf.models import ( | from moto.swf.models import ( | ||||||
|     Domain, |     Domain, | ||||||
|     GenericType, |     GenericType, | ||||||
|  |     WorkflowExecution, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -65,3 +66,19 @@ def test_type_full_dict_representation(): | |||||||
| def test_type_string_representation(): | def test_type_string_representation(): | ||||||
|     _type = FooType("test-foo", "v1.0") |     _type = FooType("test-foo", "v1.0") | ||||||
|     str(_type).should.equal("FooType(name: test-foo, version: v1.0, status: REGISTERED)") |     str(_type).should.equal("FooType(name: test-foo, version: v1.0, status: REGISTERED)") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # WorkflowExecution | ||||||
|  | def test_workflow_execution_creation(): | ||||||
|  |     wfe = WorkflowExecution("workflow_type_whatever", child_policy="TERMINATE") | ||||||
|  |     wfe.workflow_type.should.equal("workflow_type_whatever") | ||||||
|  |     wfe.child_policy.should.equal("TERMINATE") | ||||||
|  | 
 | ||||||
|  | def test_workflow_execution_string_representation(): | ||||||
|  |     wfe = WorkflowExecution("workflow_type_whatever", child_policy="TERMINATE") | ||||||
|  |     str(wfe).should.match(r"^WorkflowExecution\(run_id: .*\)") | ||||||
|  | 
 | ||||||
|  | def test_workflow_execution_generates_a_random_run_id(): | ||||||
|  |     wfe1 = WorkflowExecution("workflow_type_whatever") | ||||||
|  |     wfe2 = WorkflowExecution("workflow_type_whatever") | ||||||
|  |     wfe1.run_id.should_not.equal(wfe2.run_id) | ||||||
|  | |||||||
							
								
								
									
										59
									
								
								tests/test_swf/test_workflow_executions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								tests/test_swf/test_workflow_executions.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | |||||||
|  | import boto | ||||||
|  | from nose.tools import assert_raises | ||||||
|  | from sure import expect | ||||||
|  | 
 | ||||||
|  | from moto import mock_swf | ||||||
|  | from moto.swf.exceptions import ( | ||||||
|  |     SWFWorkflowExecutionAlreadyStartedFault, | ||||||
|  |     SWFTypeDeprecatedFault, | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Utils | ||||||
|  | @mock_swf | ||||||
|  | def setup_swf_environment(): | ||||||
|  |     conn = boto.connect_swf("the_key", "the_secret") | ||||||
|  |     conn.register_domain("test-domain", "60", description="A test domain") | ||||||
|  |     conn.register_workflow_type("test-domain", "test-workflow", "v1.0") | ||||||
|  |     conn.register_activity_type("test-domain", "test-activity", "v1.1") | ||||||
|  |     return conn | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # StartWorkflowExecution endpoint | ||||||
|  | @mock_swf | ||||||
|  | def test_start_workflow_execution(): | ||||||
|  |     conn = setup_swf_environment() | ||||||
|  | 
 | ||||||
|  |     wf = conn.start_workflow_execution("test-domain", "uid-abcd1234", "test-workflow", "v1.0") | ||||||
|  |     wf.should.contain("runId") | ||||||
|  | 
 | ||||||
|  | @mock_swf | ||||||
|  | def test_start_already_started_workflow_execution(): | ||||||
|  |     conn = setup_swf_environment() | ||||||
|  |     conn.start_workflow_execution("test-domain", "uid-abcd1234", "test-workflow", "v1.0") | ||||||
|  | 
 | ||||||
|  |     with assert_raises(SWFWorkflowExecutionAlreadyStartedFault) as err: | ||||||
|  |         conn.start_workflow_execution("test-domain", "uid-abcd1234", "test-workflow", "v1.0") | ||||||
|  | 
 | ||||||
|  |     ex = err.exception | ||||||
|  |     ex.status.should.equal(400) | ||||||
|  |     ex.error_code.should.equal("WorkflowExecutionAlreadyStartedFault") | ||||||
|  |     ex.body.should.equal({ | ||||||
|  |         "__type": "com.amazonaws.swf.base.model#WorkflowExecutionAlreadyStartedFault", | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|  | @mock_swf | ||||||
|  | def test_start_workflow_execution_on_deprecated_type(): | ||||||
|  |     conn = setup_swf_environment() | ||||||
|  |     conn.deprecate_workflow_type("test-domain", "test-workflow", "v1.0") | ||||||
|  | 
 | ||||||
|  |     with assert_raises(SWFTypeDeprecatedFault) as err: | ||||||
|  |         conn.start_workflow_execution("test-domain", "uid-abcd1234", "test-workflow", "v1.0") | ||||||
|  | 
 | ||||||
|  |     ex = err.exception | ||||||
|  |     ex.status.should.equal(400) | ||||||
|  |     ex.error_code.should.equal("TypeDeprecatedFault") | ||||||
|  |     ex.body.should.equal({ | ||||||
|  |         "__type": "com.amazonaws.swf.base.model#TypeDeprecatedFault", | ||||||
|  |         "message": "WorkflowType=[name=test-workflow, version=v1.0]" | ||||||
|  |     }) | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user