From b680b2ec3ce023fec170e299a82b940974ae691b Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Barth Date: Wed, 30 Sep 2015 15:24:49 +0200 Subject: [PATCH] Add SWF endpoints: RegisterActivityType, DeprecateActivityType, ListActivityType, DescribeActivityType --- moto/swf/exceptions.py | 20 +++- moto/swf/models.py | 81 ++++++++++++- moto/swf/responses.py | 93 +++++++++++++++ tests/test_swf/test_activity_types.py | 161 ++++++++++++++++++++++++++ tests/test_swf/test_domains.py | 5 +- 5 files changed, 353 insertions(+), 7 deletions(-) create mode 100644 tests/test_swf/test_activity_types.py diff --git a/moto/swf/exceptions.py b/moto/swf/exceptions.py index f58c0360a..61066d294 100644 --- a/moto/swf/exceptions.py +++ b/moto/swf/exceptions.py @@ -33,10 +33,26 @@ class SWFDomainDeprecatedFault(SWFClientError): class SWFSerializationException(JSONResponseError): - def __init__(self): - message = "class java.lang.Foo can not be converted to an String (not a real SWF exception)" + def __init__(self, value): + message = "class java.lang.Foo can not be converted to an String " + message += " (not a real SWF exception ; happened on: {})".format(value) __type = "com.amazonaws.swf.base.model#SerializationException" super(SWFSerializationException, self).__init__( 400, "Bad Request", body={"Message": message, "__type": __type} ) + + +class SWFTypeAlreadyExistsFault(SWFClientError): + def __init__(self, name, version): + super(SWFTypeAlreadyExistsFault, self).__init__( + "ActivityType=[name={}, version={}]".format(name, version), + "com.amazonaws.swf.base.model#TypeAlreadyExistsFault") + + +class SWFTypeDeprecatedFault(SWFClientError): + def __init__(self, name, version): + super(SWFTypeDeprecatedFault, self).__init__( + "ActivityType=[name={}, version={}]".format(name, version), + "com.amazonaws.swf.base.model#TypeDeprecatedFault") + diff --git a/moto/swf/models.py b/moto/swf/models.py index db1ca94fb..2a2980177 100644 --- a/moto/swf/models.py +++ b/moto/swf/models.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals +from collections import defaultdict import boto.swf @@ -8,6 +9,8 @@ from .exceptions import ( SWFDomainAlreadyExistsFault, SWFDomainDeprecatedFault, SWFSerializationException, + SWFTypeAlreadyExistsFault, + SWFTypeDeprecatedFault, ) @@ -17,10 +20,44 @@ class Domain(object): self.retention = retention self.description = description self.status = "REGISTERED" + self.activity_types = defaultdict(dict) def __repr__(self): return "Domain(name: %(name)s, status: %(status)s)" % self.__dict__ + def get_activity_type(self, name, version, ignore_empty=False): + try: + return self.activity_types[name][version] + except KeyError: + if not ignore_empty: + raise SWFUnknownResourceFault( + "type", + "ActivityType=[name={}, version={}]".format(name, version) + ) + + def add_activity_type(self, actype): + self.activity_types[actype.name][actype.version] = actype + + def find_activity_types(self, status): + _all = [] + for _, family in self.activity_types.iteritems(): + for _, actype in family.iteritems(): + if actype.status == status: + _all.append(actype) + return _all + + +class ActivityType(object): + def __init__(self, name, version, **kwargs): + self.name = name + self.version = version + self.status = "REGISTERED" + for key, value in kwargs.iteritems(): + self.__setattr__(key, value) + + def __repr__(self): + return "ActivityType(name: %(name)s, version: %(version)s)" % self.__dict__ + class SWFBackend(BaseBackend): def __init__(self, region_name): @@ -43,7 +80,7 @@ class SWFBackend(BaseBackend): def _check_string(self, parameter): if not isinstance(parameter, basestring): - raise SWFSerializationException() + raise SWFSerializationException(parameter) def list_domains(self, status, reverse_order=None): self._check_string(status) @@ -77,6 +114,48 @@ class SWFBackend(BaseBackend): self._check_string(name) return self._get_domain(name) + def list_activity_types(self, domain_name, status, reverse_order=None): + self._check_string(domain_name) + self._check_string(status) + domain = self._get_domain(domain_name) + actypes = domain.find_activity_types(status) + actypes = sorted(actypes, key=lambda domain: domain.name) + if reverse_order: + actypes = reversed(actypes) + return actypes + + def register_activity_type(self, domain_name, name, version, **kwargs): + self._check_string(domain_name) + self._check_string(name) + self._check_string(version) + for _, value in kwargs.iteritems(): + if value == (None,): + print _ + if value is not None: + self._check_string(value) + domain = self._get_domain(domain_name) + if domain.get_activity_type(name, version, ignore_empty=True): + raise SWFTypeAlreadyExistsFault(name, version) + activity_type = ActivityType(name, version, **kwargs) + domain.add_activity_type(activity_type) + + def deprecate_activity_type(self, domain_name, name, version): + self._check_string(domain_name) + self._check_string(name) + self._check_string(version) + domain = self._get_domain(domain_name) + actype = domain.get_activity_type(name, version) + if actype.status == "DEPRECATED": + raise SWFTypeDeprecatedFault(name, version) + actype.status = "DEPRECATED" + + def describe_activity_type(self, domain_name, name, version): + self._check_string(domain_name) + self._check_string(name) + self._check_string(version) + domain = self._get_domain(domain_name) + return domain.get_activity_type(name, version) + swf_backends = {} for region in boto.swf.regions(): diff --git a/moto/swf/responses.py b/moto/swf/responses.py index 8e2dae712..f27d34211 100644 --- a/moto/swf/responses.py +++ b/moto/swf/responses.py @@ -80,6 +80,60 @@ class SWFResponse(BaseResponse): template = self.response_template(DESCRIBE_DOMAIN_TEMPLATE) return template.render(domain=domain) + # TODO: implement pagination + def list_activity_types(self): + domain_name = self._params.get("domain") + status = self._params.get("registrationStatus") + reverse_order = self._params.get("reverseOrder", None) + actypes = self.swf_backend.list_activity_types(domain_name, status, reverse_order=reverse_order) + template = self.response_template(LIST_ACTIVITY_TYPES_TEMPLATE) + return template.render(actypes=actypes) + + def register_activity_type(self): + domain = self._params.get("domain") + name = self._params.get("name") + version = self._params.get("version") + default_task_list = self._params.get("defaultTaskList") + if default_task_list: + task_list = default_task_list.get("name") + else: + task_list = None + default_task_heartbeat_timeout = self._params.get("defaultTaskHeartbeatTimeout") + default_task_schedule_to_close_timeout = self._params.get("defaultTaskScheduleToCloseTimeout") + default_task_schedule_to_start_timeout = self._params.get("defaultTaskScheduleToStartTimeout") + default_task_start_to_close_timeout = self._params.get("defaultTaskStartToCloseTimeout") + description = self._params.get("description") + # TODO: add defaultTaskPriority when boto gets to support it + activity_type = self.swf_backend.register_activity_type( + domain, name, version, task_list=task_list, + default_task_heartbeat_timeout=default_task_heartbeat_timeout, + default_task_schedule_to_close_timeout=default_task_schedule_to_close_timeout, + default_task_schedule_to_start_timeout=default_task_schedule_to_start_timeout, + default_task_start_to_close_timeout=default_task_start_to_close_timeout, + description=description, + ) + template = self.response_template("") + return template.render() + + def deprecate_activity_type(self): + domain = self._params.get("domain") + actype = self._params.get("activityType") + name = actype["name"] + version = actype["version"] + domain = self.swf_backend.deprecate_activity_type(domain, name, version) + template = self.response_template("") + return template.render() + + def describe_activity_type(self): + domain = self._params.get("domain") + actype = self._params.get("activityType") + + name = actype["name"] + version = actype["version"] + actype = self.swf_backend.describe_activity_type(domain, name, version) + template = self.response_template(DESCRIBE_ACTIVITY_TYPE_TEMPLATE) + return template.render(actype=actype) + LIST_DOMAINS_TEMPLATE = """{ "domainInfos": [ @@ -103,3 +157,42 @@ DESCRIBE_DOMAIN_TEMPLATE = """{ "status": "{{ domain.status }}" } }""" + +LIST_ACTIVITY_TYPES_TEMPLATE = """{ + "typeInfos": [ + {%- for actype in actypes %} + { + "activityType": { + "name": "{{ actype.name }}", + "version": "{{ actype.version }}" + }, + "creationDate": 1420066800, + {% if actype.status == "DEPRECATED" %}"deprecationDate": 1422745200,{% endif %} + {% if actype.description %}"description": "{{ actype.description }}",{% endif %} + "status": "{{ actype.status }}" + }{% if not loop.last %},{% endif %} + {%- endfor %} + ] +}""" + +DESCRIBE_ACTIVITY_TYPE_TEMPLATE = """{ + "configuration": { + {% if actype.default_task_heartbeat_timeout %}"defaultTaskHeartbeatTimeout": "{{ actype.default_task_heartbeat_timeout }}",{% endif %} + {% if actype.task_list %}"defaultTaskList": { "name": "{{ actype.task_list }}" },{% endif %} + {% if actype.default_task_schedule_to_close_timeout %}"defaultTaskScheduleToCloseTimeout": "{{ actype.default_task_schedule_to_close_timeout }}",{% endif %} + {% if actype.default_task_schedule_to_start_timeout %}"defaultTaskScheduleToStartTimeout": "{{ actype.default_task_schedule_to_start_timeout }}",{% endif %} + {% if actype.default_task_start_to_close_timeout %}"defaultTaskStartToCloseTimeout": "{{ actype.default_task_start_to_close_timeout }}",{% endif %} + "__moto_placeholder": "(avoid dealing with coma in json)" + }, + "typeInfo": { + "activityType": { + "name": "{{ actype.name }}", + "version": "{{ actype.version }}" + }, + "creationDate": 1420066800, + {% if actype.status == "DEPRECATED" %}"deprecationDate": 1422745200,{% endif %} + {% if actype.description %}"description": "{{ actype.description }}",{% endif %} + "status": "{{ actype.status }}" + } +}""" + diff --git a/tests/test_swf/test_activity_types.py b/tests/test_swf/test_activity_types.py new file mode 100644 index 000000000..19f586a0b --- /dev/null +++ b/tests/test_swf/test_activity_types.py @@ -0,0 +1,161 @@ +import boto +from nose.tools import assert_raises +from sure import expect + +from moto import mock_swf +from moto.swf.exceptions import ( + SWFUnknownResourceFault, + SWFTypeAlreadyExistsFault, + SWFTypeDeprecatedFault, + SWFSerializationException, +) + + +# RegisterActivityType endpoint +@mock_swf +def test_register_activity_type(): + conn = boto.connect_swf("the_key", "the_secret") + conn.register_domain("test-domain", "60") + conn.register_activity_type("test-domain", "test-activity", "v1.0") + + types = conn.list_activity_types("test-domain", "REGISTERED") + actype = types["typeInfos"][0] + actype["activityType"]["name"].should.equal("test-activity") + actype["activityType"]["version"].should.equal("v1.0") + +@mock_swf +def test_register_already_existing_activity_type(): + conn = boto.connect_swf("the_key", "the_secret") + conn.register_domain("test-domain", "60") + conn.register_activity_type("test-domain", "test-activity", "v1.0") + + with assert_raises(SWFTypeAlreadyExistsFault) as err: + conn.register_activity_type("test-domain", "test-activity", "v1.0") + + ex = err.exception + ex.status.should.equal(400) + ex.error_code.should.equal("TypeAlreadyExistsFault") + ex.body.should.equal({ + "__type": "com.amazonaws.swf.base.model#TypeAlreadyExistsFault", + "message": "ActivityType=[name=test-activity, version=v1.0]" + }) + +@mock_swf +def test_register_with_wrong_parameter_type(): + conn = boto.connect_swf("the_key", "the_secret") + conn.register_domain("test-domain", "60") + + with assert_raises(SWFSerializationException) as err: + conn.register_activity_type("test-domain", "test-activity", 12) + + ex = err.exception + ex.status.should.equal(400) + ex.error_code.should.equal("SerializationException") + ex.body["__type"].should.equal("com.amazonaws.swf.base.model#SerializationException") + + +# ListActivityTypes endpoint +@mock_swf +def test_list_activity_types(): + conn = boto.connect_swf("the_key", "the_secret") + conn.register_domain("test-domain", "60") + conn.register_activity_type("test-domain", "b-test-activity", "v1.0") + conn.register_activity_type("test-domain", "a-test-activity", "v1.0") + conn.register_activity_type("test-domain", "c-test-activity", "v1.0") + + all_activity_types = conn.list_activity_types("test-domain", "REGISTERED") + names = [activity_type["activityType"]["name"] for activity_type in all_activity_types["typeInfos"]] + names.should.equal(["a-test-activity", "b-test-activity", "c-test-activity"]) + +@mock_swf +def test_list_activity_types_reverse_order(): + conn = boto.connect_swf("the_key", "the_secret") + conn.register_domain("test-domain", "60") + conn.register_activity_type("test-domain", "b-test-activity", "v1.0") + conn.register_activity_type("test-domain", "a-test-activity", "v1.0") + conn.register_activity_type("test-domain", "c-test-activity", "v1.0") + + all_activity_types = conn.list_activity_types("test-domain", "REGISTERED", + reverse_order=True) + names = [activity_type["activityType"]["name"] for activity_type in all_activity_types["typeInfos"]] + names.should.equal(["c-test-activity", "b-test-activity", "a-test-activity"]) + + +# DeprecateActivityType endpoint +@mock_swf +def test_deprecate_activity_type(): + conn = boto.connect_swf("the_key", "the_secret") + conn.register_domain("test-domain", "60") + conn.register_activity_type("test-domain", "test-activity", "v1.0") + conn.deprecate_activity_type("test-domain", "test-activity", "v1.0") + + actypes = conn.list_activity_types("test-domain", "DEPRECATED") + actype = actypes["typeInfos"][0] + actype["activityType"]["name"].should.equal("test-activity") + actype["activityType"]["version"].should.equal("v1.0") + +@mock_swf +def test_deprecate_already_deprecated_activity_type(): + conn = boto.connect_swf("the_key", "the_secret") + conn.register_domain("test-domain", "60") + conn.register_activity_type("test-domain", "test-activity", "v1.0") + conn.deprecate_activity_type("test-domain", "test-activity", "v1.0") + + with assert_raises(SWFTypeDeprecatedFault) as err: + conn.deprecate_activity_type("test-domain", "test-activity", "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": "ActivityType=[name=test-activity, version=v1.0]" + }) + +@mock_swf +def test_deprecate_non_existent_activity_type(): + conn = boto.connect_swf("the_key", "the_secret") + conn.register_domain("test-domain", "60") + + with assert_raises(SWFUnknownResourceFault) as err: + conn.deprecate_activity_type("test-domain", "non-existent", "v1.0") + + ex = err.exception + ex.status.should.equal(400) + ex.error_code.should.equal("UnknownResourceFault") + ex.body.should.equal({ + "__type": "com.amazonaws.swf.base.model#UnknownResourceFault", + "message": "Unknown type: ActivityType=[name=non-existent, version=v1.0]" + }) + +# DescribeActivityType endpoint +@mock_swf +def test_describe_activity_type(): + conn = boto.connect_swf("the_key", "the_secret") + conn.register_domain("test-domain", "60") + conn.register_activity_type("test-domain", "test-activity", "v1.0", + task_list="foo", default_task_heartbeat_timeout="32") + + actype = conn.describe_activity_type("test-domain", "test-activity", "v1.0") + actype["configuration"]["defaultTaskList"]["name"].should.equal("foo") + actype["configuration"].keys().should_not.contain("defaultTaskScheduleToClose") + infos = actype["typeInfo"] + infos["activityType"]["name"].should.equal("test-activity") + infos["activityType"]["version"].should.equal("v1.0") + infos["status"].should.equal("REGISTERED") + +@mock_swf +def test_describe_non_existent_activity_type(): + conn = boto.connect_swf("the_key", "the_secret") + conn.register_domain("test-domain", "60") + + with assert_raises(SWFUnknownResourceFault) as err: + conn.describe_activity_type("test-domain", "non-existent", "v1.0") + + ex = err.exception + ex.status.should.equal(400) + ex.error_code.should.equal("UnknownResourceFault") + ex.body.should.equal({ + "__type": "com.amazonaws.swf.base.model#UnknownResourceFault", + "message": "Unknown type: ActivityType=[name=non-existent, version=v1.0]" + }) diff --git a/tests/test_swf/test_domains.py b/tests/test_swf/test_domains.py index 7ab3dddc3..8f3a75569 100644 --- a/tests/test_swf/test_domains.py +++ b/tests/test_swf/test_domains.py @@ -50,10 +50,7 @@ def test_register_with_wrong_parameter_type(): ex = err.exception ex.status.should.equal(400) ex.error_code.should.equal("SerializationException") - ex.body.should.equal({ - "__type": "com.amazonaws.swf.base.model#SerializationException", - "Message": "class java.lang.Foo can not be converted to an String (not a real SWF exception)" - }) + ex.body["__type"].should.equal("com.amazonaws.swf.base.model#SerializationException") # ListDomain endpoint