diff --git a/README.md b/README.md index dc582b608..a20e3f379 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,11 @@ It gets even better! Moto isn't just S3. Here's the status of the other AWS serv |------------------------------------------------------------------------------| | RDS | @mock_rds | core endpoints done | |------------------------------------------------------------------------------| +| RDS2 | @mock_rds2 | core endpoints done | +| - Database | | core endpoints done | +| - Security Group | | not done | +| - Option Group | | core endpoints done | +|------------------------------------------------------------------------------| | Route53 | @mock_route53 | core endpoints done | |------------------------------------------------------------------------------| | S3 | @mock_s3 | core endpoints done | diff --git a/moto/rds2/models.py b/moto/rds2/models.py index 846d070ab..228593dcc 100644 --- a/moto/rds2/models.py +++ b/moto/rds2/models.py @@ -5,7 +5,7 @@ import copy import boto.rds2 import json from jinja2 import Template - +from re import compile as re_compile from moto.cloudformation.exceptions import UnformattedGetAttTemplateException from moto.core import BaseBackend from moto.core.utils import get_random_hex @@ -82,7 +82,7 @@ class Database(object): if not self.option_group_name and self.engine in self.default_option_groups: self.option_group_name = self.default_option_groups[self.engine] self.character_set_name = kwargs.get('character_set_name', None) - self.tags = kwargs.get('tags', None) + self.tags = kwargs.get('tags', []) @property def address(self): @@ -242,6 +242,9 @@ class Database(object): }""") return template.render(database=self) + def get_tags(self): + return self.tags + class SecurityGroup(object): def __init__(self, group_name, description): @@ -384,6 +387,7 @@ class SubnetGroup(object): class RDS2Backend(BaseBackend): def __init__(self): + self.arn_regex = re_compile(r'^arn:aws:rds:.*:[0-9]*:db:.*$') self.databases = {} self.security_groups = {} self.subnet_groups = {} @@ -419,6 +423,10 @@ class RDS2Backend(BaseBackend): database.update(db_kwargs) return database + def reboot_db_instance(self, db_instance_identifier): + database = self.describe_databases(db_instance_identifier)[0] + return database + def delete_database(self, db_instance_identifier): if db_instance_identifier in self.databases: database = self.databases.pop(db_instance_identifier) @@ -579,6 +587,17 @@ class RDS2Backend(BaseBackend): self.option_groups[option_group_name].add_options(options_to_include) return self.option_groups[option_group_name] + def list_tags_for_resource(self, arn): + if self.arn_regex.match(arn): + arn_breakdown = arn.split(':') + db_instance_name = arn_breakdown[len(arn_breakdown)-1] + if db_instance_name in self.databases: + return self.databases[db_instance_name].get_tags() + else: + return [] + else: + raise RDSClientError('InvalidParameterValue', + 'Invalid resource name: {}'.format(arn)) class OptionGroup(object): def __init__(self, name, engine_name, major_engine_version, description=None): diff --git a/moto/rds2/responses.py b/moto/rds2/responses.py index f013be0f4..8b483844f 100644 --- a/moto/rds2/responses.py +++ b/moto/rds2/responses.py @@ -4,6 +4,7 @@ from moto.core.responses import BaseResponse from moto.ec2.models import ec2_backends from .models import rds2_backends import json +import re class RDS2Response(BaseResponse): @@ -73,8 +74,10 @@ class RDS2Response(BaseResponse): result = template.render(database=database) return result - # TODO: Update function to new method def create_dbinstance_read_replica(self): + return self.create_db_instance_read_replica() + + def create_db_instance_read_replica(self): db_kwargs = self._get_db_replica_kwargs() database = self.backend.create_database_replica(db_kwargs) @@ -84,7 +87,7 @@ class RDS2Response(BaseResponse): def describe_dbinstances(self): return self.describe_db_instances() - def describe_dbinstances(self): + def describe_db_instances(self): db_instance_identifier = self._get_param('DBInstanceIdentifier') databases = self.backend.describe_databases(db_instance_identifier) template = self.response_template(DESCRIBE_DATABASES_TEMPLATE) @@ -109,6 +112,21 @@ class RDS2Response(BaseResponse): template = self.response_template(DELETE_DATABASE_TEMPLATE) return template.render(database=database) + def reboot_dbinstance(self): + return self.reboot_db_instance() + + def reboot_db_instance(self): + db_instance_identifier = self._get_param('DBInstanceIdentifier') + database = self.backend.reboot_db_instance(db_instance_identifier) + template = self.response_template(REBOOT_DATABASE_TEMPLATE) + return template.render(database=database) + + def list_tags_for_resource(self): + arn = self._get_param('ResourceName') + template = self.response_template(LIST_TAGS_FOR_RESOURCE_TEMPLATE) + tags = self.backend.list_tags_for_resource(arn) + return template.render(tags=tags) + # TODO: Update function to new method def create_dbsecurity_group(self): group_name = self._get_param('DBSecurityGroupName') @@ -255,14 +273,24 @@ MODIFY_DATABASE_TEMPLATE = """{"ModifyDBInstanceResponse": { } }""" -# TODO: update delete DB TEMPLATE -DELETE_DATABASE_TEMPLATE = """{ - "DeleteDBInstanceResponse": { - "DeleteDBInstanceResult": { - }, - "ResponseMetadata": { "RequestId": "523e3218-afc7-11c3-90f5-f90431260ab4" } +REBOOT_DATABASE_TEMPLATE = """{"RebootDBInstanceResponse": { + "RebootDBInstanceResult": { + {{ database.to_json() }}, + "ResponseMetadata": { + "RequestId": "d55711cb-a1ab-11e4-99cf-55e92d4bbada" + } } -}""" +}}""" + +# TODO: update delete DB TEMPLATE +DELETE_DATABASE_TEMPLATE = """{ "DeleteDBInstanceResponse": { + "DeleteDBInstanceResult": { + {{ database.to_json() }}, + "ResponseMetadata": { + "RequestId": "523e3218-afc7-11c3-90f5-f90431260ab4" + } + } +}}""" CREATE_SECURITY_GROUP_TEMPLATE = """ @@ -379,3 +407,20 @@ MODIFY_OPTION_GROUP_TEMPLATE = \ {{ option_group.to_json() }} } }""" + +LIST_TAGS_FOR_RESOURCE_TEMPLATE = \ + """{"ListTagsForResourceResponse": + {"ListTagsForResourceResult": + {"TagList": [ + {%- for tag in tags -%} + {%- if loop.index != 1 -%},{%- endif -%} + {%- for key in tag -%} + {"Value": "{{ tag[key] }}", "Key": "{{ key }}"} + {%- endfor -%} + {%- endfor -%} + ]}, + "ResponseMetadata": { + "RequestId": "8c21ba39-a598-11e4-b688-194eaf8658fa" + } + } + }""" diff --git a/tests/test_rds2/test_rds2.py b/tests/test_rds2/test_rds2.py index 40e3d24b2..8fa6c91d0 100644 --- a/tests/test_rds2/test_rds2.py +++ b/tests/test_rds2/test_rds2.py @@ -83,6 +83,36 @@ def test_modify_db_instance(): instances['DescribeDBInstanceResponse']['DescribeDBInstanceResult'][0]['DBInstance']['AllocatedStorage'].should.equal('20') +@disable_on_py3() +@mock_rds2 +def test_modify_non_existant_database(): + conn = boto.rds2.connect_to_region("us-west-2") + conn.modify_db_instance.when.called_with(db_instance_identifier='not-a-db', + allocated_storage=20, + apply_immediately=True).should.throw(BotoServerError) + +@disable_on_py3() +@mock_rds2 +def test_reboot_db_instance(): + conn = boto.rds2.connect_to_region("us-west-2") + conn.create_db_instance(db_instance_identifier='db-master-1', + allocated_storage=10, + engine='postgres', + db_instance_class='db.m1.small', + master_username='root', + master_user_password='hunter2', + db_security_groups=["my_sg"]) + database = conn.reboot_db_instance('db-master-1') + database['RebootDBInstanceResponse']['RebootDBInstanceResult']['DBInstance']['DBInstanceIdentifier'].should.equal("db-master-1") + + +@disable_on_py3() +@mock_rds2 +def test_reboot_non_existant_database(): + conn = boto.rds2.connect_to_region("us-west-2") + conn.reboot_db_instance.when.called_with("not-a-db").should.throw(BotoServerError) + + @disable_on_py3() @mock_rds2 def test_delete_database(): @@ -104,6 +134,13 @@ def test_delete_database(): list(instances['DescribeDBInstanceResponse']['DescribeDBInstanceResult']).should.have.length_of(0) +@disable_on_py3() +@mock_rds2 +def test_delete_non_existant_database(): + conn = boto.rds2.connect_to_region("us-west-2") + conn.delete_db_instance.when.called_with("not-a-db").should.throw(BotoServerError) + + @disable_on_py3() @mock_rds2 def test_create_option_group(): @@ -227,6 +264,29 @@ def test_delete_non_existant_database(): conn.delete_db_instance.when.called_with("not-a-db").should.throw(BotoServerError) +@disable_on_py3() +@mock_rds2 +def test_list_tags_invalid_arn(): + conn = boto.rds2.connect_to_region("us-west-2") + conn.list_tags_for_resource.when.called_with('arn:aws:rds:bad-arn').should.throw(BotoServerError) + + +@disable_on_py3() +@mock_rds2 +def test_list_tags(): + conn = boto.rds2.connect_to_region("us-west-2") + result = conn.list_tags_for_resource('arn:aws:rds:us-west-2:1234567890:db:foo') + result['ListTagsForResourceResponse']['ListTagsForResourceResult']['TagList'].should.equal([]) + conn.create_db_instance(db_instance_identifier='db-master-1', + allocated_storage=10, + engine='postgres', + db_instance_class='db.m1.small', + master_username='root', + master_user_password='hunter2', + db_security_groups=["my_sg"], + tags=[{'Key': 'foo', 'Value': 'bar'}]) + result = conn.list_tags_for_resource('arn:aws:rds:us-west-2:1234567890:db:db-master-1') + #@disable_on_py3() #@mock_rds2 #def test_create_database_security_group():