Step Functions - Execution methods

This commit is contained in:
Bert Blommers 2019-09-04 15:42:42 +01:00
parent af4082f38e
commit 78254cc4f2
5 changed files with 255 additions and 8 deletions

View File

@ -6054,20 +6054,20 @@
- [ ] delete_activity
- [X] delete_state_machine
- [ ] describe_activity
- [ ] describe_execution
- [X] describe_execution
- [X] describe_state_machine
- [ ] describe_state_machine_for_execution
- [x] describe_state_machine_for_execution
- [ ] get_activity_task
- [ ] get_execution_history
- [ ] list_activities
- [ ] list_executions
- [X] list_executions
- [X] list_state_machines
- [X] list_tags_for_resource
- [ ] send_task_failure
- [ ] send_task_heartbeat
- [ ] send_task_success
- [ ] start_execution
- [ ] stop_execution
- [X] start_execution
- [X] stop_execution
- [ ] tag_resource
- [ ] untag_resource
- [ ] update_state_machine

View File

@ -20,6 +20,11 @@ class AccessDeniedException(AWSError):
STATUS = 400
class ExecutionDoesNotExist(AWSError):
CODE = 'ExecutionDoesNotExist'
STATUS = 400
class InvalidArn(AWSError):
CODE = 'InvalidArn'
STATUS = 400

View File

@ -4,7 +4,8 @@ import re
from datetime import datetime
from moto.core import BaseBackend
from moto.core.utils import iso_8601_datetime_without_milliseconds
from .exceptions import AccessDeniedException, InvalidArn, InvalidName, StateMachineDoesNotExist
from uuid import uuid4
from .exceptions import AccessDeniedException, ExecutionDoesNotExist, InvalidArn, InvalidName, StateMachineDoesNotExist
class StateMachine():
@ -17,6 +18,22 @@ class StateMachine():
self.tags = tags
class Execution():
def __init__(self, region_name, account_id, state_machine_name, execution_name, state_machine_arn):
execution_arn = 'arn:aws:states:{}:{}:execution:{}:{}'
execution_arn = execution_arn.format(region_name, account_id, state_machine_name, execution_name)
self.execution_arn = execution_arn
self.name = execution_name
self.start_date = iso_8601_datetime_without_milliseconds(datetime.now())
self.state_machine_arn = state_machine_arn
self.status = 'RUNNING'
self.stop_date = None
def stop(self):
self.status = 'SUCCEEDED'
self.stop_date = iso_8601_datetime_without_milliseconds(datetime.now())
class StepFunctionBackend(BaseBackend):
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/stepfunctions.html#SFN.Client.create_state_machine
@ -44,9 +61,11 @@ class StepFunctionBackend(BaseBackend):
u'\u009A', u'\u009B', u'\u009C', u'\u009D', u'\u009E', u'\u009F']
accepted_role_arn_format = re.compile('arn:aws:iam:(?P<account_id>[0-9]{12}):role/.+')
accepted_mchn_arn_format = re.compile('arn:aws:states:[-0-9a-zA-Z]+:(?P<account_id>[0-9]{12}):stateMachine:.+')
accepted_exec_arn_format = re.compile('arn:aws:states:[-0-9a-zA-Z]+:(?P<account_id>[0-9]{12}):execution:.+')
def __init__(self, region_name):
self.state_machines = []
self.executions = []
self.region_name = region_name
self._account_id = None
@ -77,6 +96,29 @@ class StepFunctionBackend(BaseBackend):
if sm:
self.state_machines.remove(sm)
def start_execution(self, state_machine_arn):
state_machine_name = self.describe_state_machine(state_machine_arn).name
execution = Execution(region_name=self.region_name, account_id=self._get_account_id(), state_machine_name=state_machine_name, execution_name=str(uuid4()), state_machine_arn=state_machine_arn)
self.executions.append(execution)
return execution
def stop_execution(self, execution_arn):
execution = next((x for x in self.executions if x.execution_arn == execution_arn), None)
if not execution:
raise ExecutionDoesNotExist("Execution Does Not Exist: '" + execution_arn + "'")
execution.stop()
return execution
def list_executions(self, state_machine_arn):
return [execution for execution in self.executions if execution.state_machine_arn == state_machine_arn]
def describe_execution(self, arn):
self._validate_execution_arn(arn)
exctn = next((x for x in self.executions if x.execution_arn == arn), None)
if not exctn:
raise ExecutionDoesNotExist("Execution Does Not Exist: '" + arn + "'")
return exctn
def reset(self):
region_name = self.region_name
self.__dict__ = {}
@ -101,6 +143,12 @@ class StepFunctionBackend(BaseBackend):
invalid_msg="Invalid Role Arn: '" + machine_arn + "'",
access_denied_msg='User moto is not authorized to access this resource')
def _validate_execution_arn(self, execution_arn):
self._validate_arn(arn=execution_arn,
regex=self.accepted_exec_arn_format,
invalid_msg="Execution Does Not Exist: '" + execution_arn + "'",
access_denied_msg='User moto is not authorized to access this resource')
def _validate_arn(self, arn, regex, invalid_msg, access_denied_msg):
match = regex.match(arn)
if not arn or not match:

View File

@ -45,8 +45,12 @@ class StepFunctionResponse(BaseResponse):
@amzn_request_id
def describe_state_machine(self):
arn = self._get_param('stateMachineArn')
return self._describe_state_machine(arn)
@amzn_request_id
def _describe_state_machine(self, state_machine_arn):
try:
state_machine = self.stepfunction_backend.describe_state_machine(arn)
state_machine = self.stepfunction_backend.describe_state_machine(state_machine_arn)
response = {
'creationDate': state_machine.creation_date,
'stateMachineArn': state_machine.arn,
@ -78,3 +82,57 @@ class StepFunctionResponse(BaseResponse):
tags = []
response = {'tags': tags}
return 200, {}, json.dumps(response)
@amzn_request_id
def start_execution(self):
arn = self._get_param('stateMachineArn')
execution = self.stepfunction_backend.start_execution(arn)
response = {'executionArn': execution.execution_arn,
'startDate': execution.start_date}
return 200, {}, json.dumps(response)
@amzn_request_id
def list_executions(self):
arn = self._get_param('stateMachineArn')
state_machine = self.stepfunction_backend.describe_state_machine(arn)
executions = self.stepfunction_backend.list_executions(arn)
executions = [{'executionArn': execution.execution_arn,
'name': execution.name,
'startDate': execution.start_date,
'stateMachineArn': state_machine.arn,
'status': execution.status} for execution in executions]
return 200, {}, json.dumps({'executions': executions})
@amzn_request_id
def describe_execution(self):
arn = self._get_param('executionArn')
try:
execution = self.stepfunction_backend.describe_execution(arn)
response = {
'executionArn': arn,
'input': '{}',
'name': execution.name,
'startDate': execution.start_date,
'stateMachineArn': execution.state_machine_arn,
'status': execution.status,
'stopDate': execution.stop_date
}
return 200, {}, json.dumps(response)
except AWSError as err:
return err.response()
@amzn_request_id
def describe_state_machine_for_execution(self):
arn = self._get_param('executionArn')
try:
execution = self.stepfunction_backend.describe_execution(arn)
return self._describe_state_machine(execution.state_machine_arn)
except AWSError as err:
return err.response()
@amzn_request_id
def stop_execution(self):
arn = self._get_param('executionArn')
execution = self.stepfunction_backend.stop_execution(arn)
response = {'stopDate': execution.stop_date}
return 200, {}, json.dumps(response)

View File

@ -157,7 +157,7 @@ def test_state_machine_creation_is_idempotent_by_name():
@mock_stepfunctions
@mock_sts
def test_state_machine_creation_can_be_described_by_name():
def test_state_machine_creation_can_be_described():
client = boto3.client('stepfunctions', region_name=region)
#
sm = client.create_state_machine(name='name', definition=str(simple_definition), roleArn=default_stepfunction_role)
@ -274,3 +274,139 @@ def test_state_machine_list_tags_for_nonexisting_machine():
response = client.list_tags_for_resource(resourceArn=non_existing_state_machine)
tags = response['tags']
tags.should.have.length_of(0)
@mock_stepfunctions
@mock_sts
def test_state_machine_start_execution():
client = boto3.client('stepfunctions', region_name=region)
#
sm = client.create_state_machine(name='name', definition=str(simple_definition), roleArn=default_stepfunction_role)
execution = client.start_execution(stateMachineArn=sm['stateMachineArn'])
#
execution['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
expected_exec_name = 'arn:aws:states:' + region + ':' + str(DEFAULT_ACCOUNT_ID) + ':execution:name:[a-zA-Z0-9-]+'
execution['executionArn'].should.match(expected_exec_name)
execution['startDate'].should.be.a(datetime)
@mock_stepfunctions
@mock_sts
def test_state_machine_list_executions():
client = boto3.client('stepfunctions', region_name=region)
#
sm = client.create_state_machine(name='name', definition=str(simple_definition), roleArn=default_stepfunction_role)
execution = client.start_execution(stateMachineArn=sm['stateMachineArn'])
execution_arn = execution['executionArn']
execution_name = execution_arn[execution_arn.rindex(':')+1:]
executions = client.list_executions(stateMachineArn=sm['stateMachineArn'])
#
executions['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
executions['executions'].should.have.length_of(1)
executions['executions'][0]['executionArn'].should.equal(execution_arn)
executions['executions'][0]['name'].should.equal(execution_name)
executions['executions'][0]['startDate'].should.equal(execution['startDate'])
executions['executions'][0]['stateMachineArn'].should.equal(sm['stateMachineArn'])
executions['executions'][0]['status'].should.equal('RUNNING')
executions['executions'][0].shouldnt.have('stopDate')
@mock_stepfunctions
@mock_sts
def test_state_machine_list_executions_when_none_exist():
client = boto3.client('stepfunctions', region_name=region)
#
sm = client.create_state_machine(name='name', definition=str(simple_definition), roleArn=default_stepfunction_role)
executions = client.list_executions(stateMachineArn=sm['stateMachineArn'])
#
executions['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
executions['executions'].should.have.length_of(0)
@mock_stepfunctions
@mock_sts
def test_state_machine_describe_execution():
client = boto3.client('stepfunctions', region_name=region)
#
sm = client.create_state_machine(name='name', definition=str(simple_definition), roleArn=default_stepfunction_role)
execution = client.start_execution(stateMachineArn=sm['stateMachineArn'])
description = client.describe_execution(executionArn=execution['executionArn'])
#
description['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
description['executionArn'].should.equal(execution['executionArn'])
description['input'].should.equal("{}")
description['name'].shouldnt.be.empty
description['startDate'].should.equal(execution['startDate'])
description['stateMachineArn'].should.equal(sm['stateMachineArn'])
description['status'].should.equal('RUNNING')
description.shouldnt.have('stopDate')
@mock_stepfunctions
@mock_sts
def test_state_machine_throws_error_when_describing_unknown_machine():
client = boto3.client('stepfunctions', region_name=region)
#
with assert_raises(ClientError) as exc:
unknown_execution = 'arn:aws:states:' + region + ':' + str(DEFAULT_ACCOUNT_ID) + ':execution:unknown'
client.describe_execution(executionArn=unknown_execution)
exc.exception.response['Error']['Code'].should.equal('ExecutionDoesNotExist')
exc.exception.response['Error']['Message'].should.equal("Execution Does Not Exist: '" + unknown_execution + "'")
exc.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400)
@mock_stepfunctions
@mock_sts
def test_state_machine_can_be_described_by_execution():
client = boto3.client('stepfunctions', region_name=region)
#
sm = client.create_state_machine(name='name', definition=str(simple_definition), roleArn=default_stepfunction_role)
execution = client.start_execution(stateMachineArn=sm['stateMachineArn'])
desc = client.describe_state_machine_for_execution(executionArn=execution['executionArn'])
desc['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
desc['definition'].should.equal(str(simple_definition))
desc['name'].should.equal('name')
desc['roleArn'].should.equal(default_stepfunction_role)
desc['stateMachineArn'].should.equal(sm['stateMachineArn'])
@mock_stepfunctions
@mock_sts
def test_state_machine_throws_error_when_describing_unknown_execution():
client = boto3.client('stepfunctions', region_name=region)
#
with assert_raises(ClientError) as exc:
unknown_execution = 'arn:aws:states:' + region + ':' + str(DEFAULT_ACCOUNT_ID) + ':execution:unknown'
client.describe_state_machine_for_execution(executionArn=unknown_execution)
exc.exception.response['Error']['Code'].should.equal('ExecutionDoesNotExist')
exc.exception.response['Error']['Message'].should.equal("Execution Does Not Exist: '" + unknown_execution + "'")
exc.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400)
@mock_stepfunctions
@mock_sts
def test_state_machine_stop_execution():
client = boto3.client('stepfunctions', region_name=region)
#
sm = client.create_state_machine(name='name', definition=str(simple_definition), roleArn=default_stepfunction_role)
start = client.start_execution(stateMachineArn=sm['stateMachineArn'])
stop = client.stop_execution(executionArn=start['executionArn'])
print(stop)
#
stop['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
stop['stopDate'].should.be.a(datetime)
@mock_stepfunctions
@mock_sts
def test_state_machine_describe_execution_after_stoppage():
client = boto3.client('stepfunctions', region_name=region)
#
sm = client.create_state_machine(name='name', definition=str(simple_definition), roleArn=default_stepfunction_role)
execution = client.start_execution(stateMachineArn=sm['stateMachineArn'])
client.stop_execution(executionArn=execution['executionArn'])
description = client.describe_execution(executionArn=execution['executionArn'])
#
description['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
description['status'].should.equal('SUCCEEDED')
description['stopDate'].should.be.a(datetime)