Various RDS, RDS/Cloudformation, RDS/KMS improvements. (#789)

We need to mock out deploying RDS instances with full disk encryption
and detailed tagging. We also need to be able do deploy these instances
with Cloudformation, and then access them with both boto and boto3.

* Join RDS and RDS2 backends - this makes RDS resources created via
  either of the two boto RDS APIs visible to both, more closely
  mirroring how AWS works
* Fix RDS responses that were returning JSON but should be returning XML
* Add mocking of RDS Cloudformation calls
* Add mocking of RDS full disk encryption with KMS
* Add mocking of RDS DBParameterGroups
* Fix mocking of RDS DBSecurityGroupIngress rules
* Make mocking of RDS OptionGroupOptions more accurate
* Fix mocking of RDS cross-region DB replication
* Add RDS tag support to:
  * DBs
  * DBSubnetGroups
  * DBSecurityGroups

Signed-off-by: Andrew Garrett <andrew.garrett@getbraintree.com>
This commit is contained in:
Michael Nussbaum 2017-01-11 18:02:51 -08:00 committed by Steve Pulec
parent 201efd5773
commit 74bbd9c8e5
11 changed files with 1868 additions and 721 deletions

View File

@ -14,6 +14,7 @@ from moto.elb import models as elb_models
from moto.iam import models as iam_models from moto.iam import models as iam_models
from moto.kms import models as kms_models from moto.kms import models as kms_models
from moto.rds import models as rds_models from moto.rds import models as rds_models
from moto.rds2 import models as rds2_models
from moto.redshift import models as redshift_models from moto.redshift import models as redshift_models
from moto.route53 import models as route53_models from moto.route53 import models as route53_models
from moto.s3 import models as s3_models from moto.s3 import models as s3_models
@ -56,6 +57,7 @@ MODEL_MAP = {
"AWS::RDS::DBInstance": rds_models.Database, "AWS::RDS::DBInstance": rds_models.Database,
"AWS::RDS::DBSecurityGroup": rds_models.SecurityGroup, "AWS::RDS::DBSecurityGroup": rds_models.SecurityGroup,
"AWS::RDS::DBSubnetGroup": rds_models.SubnetGroup, "AWS::RDS::DBSubnetGroup": rds_models.SubnetGroup,
"AWS::RDS::DBParameterGroup": rds2_models.DBParameterGroup,
"AWS::Redshift::Cluster": redshift_models.Cluster, "AWS::Redshift::Cluster": redshift_models.Cluster,
"AWS::Redshift::ClusterParameterGroup": redshift_models.ParameterGroup, "AWS::Redshift::ClusterParameterGroup": redshift_models.ParameterGroup,
"AWS::Redshift::ClusterSubnetGroup": redshift_models.SubnetGroup, "AWS::Redshift::ClusterSubnetGroup": redshift_models.SubnetGroup,
@ -311,7 +313,8 @@ class ResourceMap(collections.Mapping):
if not resource_json: if not resource_json:
raise KeyError(resource_logical_id) raise KeyError(resource_logical_id)
new_resource = parse_and_create_resource(resource_logical_id, resource_json, self, self._region_name) new_resource = parse_and_create_resource(resource_logical_id, resource_json, self, self._region_name)
self._parsed_resources[resource_logical_id] = new_resource if new_resource is not None:
self._parsed_resources[resource_logical_id] = new_resource
return new_resource return new_resource
def __iter__(self): def __iter__(self):

View File

@ -10,6 +10,7 @@ from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
from moto.core import BaseBackend from moto.core import BaseBackend
from moto.core.utils import get_random_hex from moto.core.utils import get_random_hex
from moto.ec2.models import ec2_backends from moto.ec2.models import ec2_backends
from moto.rds2.models import rds2_backends
from .exceptions import DBInstanceNotFoundError, DBSecurityGroupNotFoundError, DBSubnetGroupNotFoundError from .exceptions import DBInstanceNotFoundError, DBSecurityGroupNotFoundError, DBSubnetGroupNotFoundError
@ -26,6 +27,11 @@ class Database(object):
if self.engine_version is None: if self.engine_version is None:
self.engine_version = "5.6.21" self.engine_version = "5.6.21"
self.iops = kwargs.get("iops") self.iops = kwargs.get("iops")
self.storage_encrypted = kwargs.get("storage_encrypted", False)
if self.storage_encrypted:
self.kms_key_id = kwargs.get("kms_key_id", "default_kms_key_id")
else:
self.kms_key_id = kwargs.get("kms_key_id")
self.storage_type = kwargs.get("storage_type") self.storage_type = kwargs.get("storage_type")
self.master_username = kwargs.get('master_username') self.master_username = kwargs.get('master_username')
self.master_password = kwargs.get('master_password') self.master_password = kwargs.get('master_password')
@ -119,6 +125,7 @@ class Database(object):
"engine": properties.get("Engine"), "engine": properties.get("Engine"),
"engine_version": properties.get("EngineVersion"), "engine_version": properties.get("EngineVersion"),
"iops": properties.get("Iops"), "iops": properties.get("Iops"),
"kms_key_id": properties.get("KmsKeyId"),
"master_password": properties.get('MasterUserPassword'), "master_password": properties.get('MasterUserPassword'),
"master_username": properties.get('MasterUsername'), "master_username": properties.get('MasterUsername'),
"multi_az": properties.get("MultiAZ"), "multi_az": properties.get("MultiAZ"),
@ -126,7 +133,9 @@ class Database(object):
"publicly_accessible": properties.get("PubliclyAccessible"), "publicly_accessible": properties.get("PubliclyAccessible"),
"region": region_name, "region": region_name,
"security_groups": security_groups, "security_groups": security_groups,
"storage_encrypted": properties.get("StorageEncrypted"),
"storage_type": properties.get("StorageType"), "storage_type": properties.get("StorageType"),
"tags": properties.get("Tags"),
} }
rds_backend = rds_backends[region_name] rds_backend = rds_backends[region_name]
@ -204,6 +213,10 @@ class Database(object):
<PubliclyAccessible>{{ database.publicly_accessible }}</PubliclyAccessible> <PubliclyAccessible>{{ database.publicly_accessible }}</PubliclyAccessible>
<AutoMinorVersionUpgrade>{{ database.auto_minor_version_upgrade }}</AutoMinorVersionUpgrade> <AutoMinorVersionUpgrade>{{ database.auto_minor_version_upgrade }}</AutoMinorVersionUpgrade>
<AllocatedStorage>{{ database.allocated_storage }}</AllocatedStorage> <AllocatedStorage>{{ database.allocated_storage }}</AllocatedStorage>
<StorageEncrypted>{{ database.storage_encrypted }}</StorageEncrypted>
{% if database.kms_key_id %}
<KmsKeyId>{{ database.kms_key_id }}</KmsKeyId>
{% endif %}
{% if database.iops %} {% if database.iops %}
<Iops>{{ database.iops }}</Iops> <Iops>{{ database.iops }}</Iops>
<StorageType>io1</StorageType> <StorageType>io1</StorageType>
@ -220,6 +233,10 @@ class Database(object):
</DBInstance>""") </DBInstance>""")
return template.render(database=self) return template.render(database=self)
def delete(self, region_name):
backend = rds_backends[region_name]
backend.delete_database(self.db_instance_identifier)
class SecurityGroup(object): class SecurityGroup(object):
def __init__(self, group_name, description): def __init__(self, group_name, description):
@ -267,25 +284,33 @@ class SecurityGroup(object):
properties = cloudformation_json['Properties'] properties = cloudformation_json['Properties']
group_name = resource_name.lower() + get_random_hex(12) group_name = resource_name.lower() + get_random_hex(12)
description = properties['GroupDescription'] description = properties['GroupDescription']
security_group_ingress = properties['DBSecurityGroupIngress'] security_group_ingress_rules = properties.get('DBSecurityGroupIngress', [])
tags = properties.get('Tags')
ec2_backend = ec2_backends[region_name] ec2_backend = ec2_backends[region_name]
rds_backend = rds_backends[region_name] rds_backend = rds_backends[region_name]
security_group = rds_backend.create_security_group( security_group = rds_backend.create_security_group(
group_name, group_name,
description, description,
tags,
) )
for ingress_type, ingress_value in security_group_ingress.items():
if ingress_type == "CIDRIP": for security_group_ingress in security_group_ingress_rules:
security_group.authorize_cidr(ingress_value) for ingress_type, ingress_value in security_group_ingress.items():
elif ingress_type == "EC2SecurityGroupName": if ingress_type == "CIDRIP":
subnet = ec2_backend.get_security_group_from_name(ingress_value) security_group.authorize_cidr(ingress_value)
security_group.authorize_security_group(subnet) elif ingress_type == "EC2SecurityGroupName":
elif ingress_type == "EC2SecurityGroupId": subnet = ec2_backend.get_security_group_from_name(ingress_value)
subnet = ec2_backend.get_security_group_from_id(ingress_value) security_group.authorize_security_group(subnet)
security_group.authorize_security_group(subnet) elif ingress_type == "EC2SecurityGroupId":
subnet = ec2_backend.get_security_group_from_id(ingress_value)
security_group.authorize_security_group(subnet)
return security_group return security_group
def delete(self, region_name):
backend = rds_backends[region_name]
backend.delete_security_group(self.group_name)
class SubnetGroup(object): class SubnetGroup(object):
def __init__(self, subnet_name, description, subnets): def __init__(self, subnet_name, description, subnets):
@ -324,6 +349,7 @@ class SubnetGroup(object):
subnet_name = resource_name.lower() + get_random_hex(12) subnet_name = resource_name.lower() + get_random_hex(12)
description = properties['DBSubnetGroupDescription'] description = properties['DBSubnetGroupDescription']
subnet_ids = properties['SubnetIds'] subnet_ids = properties['SubnetIds']
tags = properties.get('Tags')
ec2_backend = ec2_backends[region_name] ec2_backend = ec2_backends[region_name]
subnets = [ec2_backend.get_subnet(subnet_id) for subnet_id in subnet_ids] subnets = [ec2_backend.get_subnet(subnet_id) for subnet_id in subnet_ids]
@ -332,102 +358,31 @@ class SubnetGroup(object):
subnet_name, subnet_name,
description, description,
subnets, subnets,
tags,
) )
return subnet_group return subnet_group
def delete(self, region_name):
backend = rds_backends[region_name]
backend.delete_subnet_group(self.subnet_name)
class RDSBackend(BaseBackend): class RDSBackend(BaseBackend):
def __init__(self): def __init__(self, region):
self.databases = {} self.region = region
self.security_groups = {}
self.subnet_groups = {}
def create_database(self, db_kwargs): def __getattr__(self, attr):
database_id = db_kwargs['db_instance_identifier'] return self.rds2_backend().__getattribute__(attr)
database = Database(**db_kwargs)
self.databases[database_id] = database
return database
def create_database_replica(self, db_kwargs): def reset(self):
database_id = db_kwargs['db_instance_identifier'] # preserve region
source_database_id = db_kwargs['source_db_identifier'] region = self.region
primary = self.describe_databases(source_database_id)[0] self.rds2_backend().reset()
replica = copy.deepcopy(primary) self.__dict__ = {}
replica.update(db_kwargs) self.__init__(region)
replica.set_as_replica()
self.databases[database_id] = replica
primary.add_replica(replica)
return replica
def describe_databases(self, db_instance_identifier=None): def rds2_backend(self):
if db_instance_identifier: return rds2_backends[self.region]
if db_instance_identifier in self.databases:
return [self.databases[db_instance_identifier]]
else:
raise DBInstanceNotFoundError(db_instance_identifier)
return self.databases.values()
def modify_database(self, db_instance_identifier, db_kwargs): rds_backends = dict((region.name, RDSBackend(region.name)) for region in boto.rds.regions())
database = self.describe_databases(db_instance_identifier)[0]
database.update(db_kwargs)
return database
def delete_database(self, db_instance_identifier):
if db_instance_identifier in self.databases:
database = self.databases.pop(db_instance_identifier)
if database.is_replica:
primary = self.describe_databases(database.source_db_identifier)[0]
primary.remove_replica(database)
database.status = 'deleting'
return database
else:
raise DBInstanceNotFoundError(db_instance_identifier)
def create_security_group(self, group_name, description):
security_group = SecurityGroup(group_name, description)
self.security_groups[group_name] = security_group
return security_group
def describe_security_groups(self, security_group_name):
if security_group_name:
if security_group_name in self.security_groups:
return [self.security_groups[security_group_name]]
else:
raise DBSecurityGroupNotFoundError(security_group_name)
return self.security_groups.values()
def delete_security_group(self, security_group_name):
if security_group_name in self.security_groups:
return self.security_groups.pop(security_group_name)
else:
raise DBSecurityGroupNotFoundError(security_group_name)
def authorize_security_group(self, security_group_name, cidr_ip):
security_group = self.describe_security_groups(security_group_name)[0]
security_group.authorize_cidr(cidr_ip)
return security_group
def create_subnet_group(self, subnet_name, description, subnets):
subnet_group = SubnetGroup(subnet_name, description, subnets)
self.subnet_groups[subnet_name] = subnet_group
return subnet_group
def describe_subnet_groups(self, subnet_group_name):
if subnet_group_name:
if subnet_group_name in self.subnet_groups:
return [self.subnet_groups[subnet_group_name]]
else:
raise DBSubnetGroupNotFoundError(subnet_group_name)
return self.subnet_groups.values()
def delete_subnet_group(self, subnet_name):
if subnet_name in self.subnet_groups:
return self.subnet_groups.pop(subnet_name)
else:
raise DBSubnetGroupNotFoundError(subnet_name)
rds_backends = {}
for region in boto.rds.regions():
rds_backends[region.name] = RDSBackend()

View File

@ -12,7 +12,7 @@ class RDSResponse(BaseResponse):
return rds_backends[self.region] return rds_backends[self.region]
def _get_db_kwargs(self): def _get_db_kwargs(self):
return { args = {
"auto_minor_version_upgrade": self._get_param('AutoMinorVersionUpgrade'), "auto_minor_version_upgrade": self._get_param('AutoMinorVersionUpgrade'),
"allocated_storage": self._get_int_param('AllocatedStorage'), "allocated_storage": self._get_int_param('AllocatedStorage'),
"availability_zone": self._get_param("AvailabilityZone"), "availability_zone": self._get_param("AvailabilityZone"),
@ -25,6 +25,7 @@ class RDSResponse(BaseResponse):
"engine": self._get_param("Engine"), "engine": self._get_param("Engine"),
"engine_version": self._get_param("EngineVersion"), "engine_version": self._get_param("EngineVersion"),
"iops": self._get_int_param("Iops"), "iops": self._get_int_param("Iops"),
"kms_key_id": self._get_param("KmsKeyId"),
"master_password": self._get_param('MasterUserPassword'), "master_password": self._get_param('MasterUserPassword'),
"master_username": self._get_param('MasterUsername'), "master_username": self._get_param('MasterUsername'),
"multi_az": self._get_bool_param("MultiAZ"), "multi_az": self._get_bool_param("MultiAZ"),
@ -35,9 +36,13 @@ class RDSResponse(BaseResponse):
"publicly_accessible": self._get_param("PubliclyAccessible"), "publicly_accessible": self._get_param("PubliclyAccessible"),
"region": self.region, "region": self.region,
"security_groups": self._get_multi_param('DBSecurityGroups.member'), "security_groups": self._get_multi_param('DBSecurityGroups.member'),
"storage_encrypted": self._get_param("StorageEncrypted"),
"storage_type": self._get_param("StorageType"), "storage_type": self._get_param("StorageType"),
# VpcSecurityGroupIds.member.N # VpcSecurityGroupIds.member.N
"tags": list(),
} }
args['tags'] = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value'))
return args
def _get_db_replica_kwargs(self): def _get_db_replica_kwargs(self):
return { return {
@ -54,6 +59,17 @@ class RDSResponse(BaseResponse):
"storage_type": self._get_param("StorageType"), "storage_type": self._get_param("StorageType"),
} }
def unpack_complex_list_params(self, label, names):
unpacked_list = list()
count = 1
while self._get_param('{0}.{1}.{2}'.format(label, count, names[0])):
param = dict()
for i in range(len(names)):
param[names[i]] = self._get_param('{0}.{1}.{2}'.format(label, count, names[i]))
unpacked_list.append(param)
count += 1
return unpacked_list
def create_dbinstance(self): def create_dbinstance(self):
db_kwargs = self._get_db_kwargs() db_kwargs = self._get_db_kwargs()
@ -90,7 +106,8 @@ class RDSResponse(BaseResponse):
def create_dbsecurity_group(self): def create_dbsecurity_group(self):
group_name = self._get_param('DBSecurityGroupName') group_name = self._get_param('DBSecurityGroupName')
description = self._get_param('DBSecurityGroupDescription') description = self._get_param('DBSecurityGroupDescription')
security_group = self.backend.create_security_group(group_name, description) tags = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value'))
security_group = self.backend.create_security_group(group_name, description, tags)
template = self.response_template(CREATE_SECURITY_GROUP_TEMPLATE) template = self.response_template(CREATE_SECURITY_GROUP_TEMPLATE)
return template.render(security_group=security_group) return template.render(security_group=security_group)
@ -118,7 +135,8 @@ class RDSResponse(BaseResponse):
description = self._get_param('DBSubnetGroupDescription') description = self._get_param('DBSubnetGroupDescription')
subnet_ids = self._get_multi_param('SubnetIds.member') subnet_ids = self._get_multi_param('SubnetIds.member')
subnets = [ec2_backends[self.region].get_subnet(subnet_id) for subnet_id in subnet_ids] subnets = [ec2_backends[self.region].get_subnet(subnet_id) for subnet_id in subnet_ids]
subnet_group = self.backend.create_subnet_group(subnet_name, description, subnets) tags = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value'))
subnet_group = self.backend.create_subnet_group(subnet_name, description, subnets, tags)
template = self.response_template(CREATE_SUBNET_GROUP_TEMPLATE) template = self.response_template(CREATE_SUBNET_GROUP_TEMPLATE)
return template.render(subnet_group=subnet_group) return template.render(subnet_group=subnet_group)

View File

@ -1,20 +1,22 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import json from jinja2 import Template
from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequest
class RDSClientError(BadRequest): class RDSClientError(BadRequest):
def __init__(self, code, message): def __init__(self, code, message):
super(RDSClientError, self).__init__() super(RDSClientError, self).__init__()
self.description = json.dumps({ template = Template("""
"Error": { <RDSClientError>
"Code": code, <Error>
"Message": message, <Code>{{ code }}</Code>
'Type': 'Sender', <Message>{{ message }}</Message>
}, <Type>Sender</Type>
'RequestId': '6876f774-7273-11e4-85dc-39e55ca848d1', </Error>
}) <RequestId>6876f774-7273-11e4-85dc-39e55ca848d1</RequestId>
</RDSClientError>""")
self.description = template.render(code=code, message=message)
class DBInstanceNotFoundError(RDSClientError): class DBInstanceNotFoundError(RDSClientError):
@ -37,3 +39,8 @@ class DBSubnetGroupNotFoundError(RDSClientError):
'DBSubnetGroupNotFound', 'DBSubnetGroupNotFound',
"Subnet Group {0} not found.".format(subnet_group_name)) "Subnet Group {0} not found.".format(subnet_group_name))
class DBParameterGroupNotFoundError(RDSClientError):
def __init__(self, db_parameter_group_name):
super(DBParameterGroupNotFoundError, self).__init__(
'DBParameterGroupNotFound',
'DB Parameter Group {0} not found.'.format(db_parameter_group_name))

File diff suppressed because one or more lines are too long

View File

@ -1,8 +1,10 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from collections import defaultdict
from moto.core.responses import BaseResponse from moto.core.responses import BaseResponse
from moto.ec2.models import ec2_backends from moto.ec2.models import ec2_backends
from .models import rds2_backends from .models import rds2_backends
from .exceptions import DBParameterGroupNotFoundError
import json import json
import re import re
@ -22,11 +24,12 @@ class RDS2Response(BaseResponse):
"db_instance_class": self._get_param('DBInstanceClass'), "db_instance_class": self._get_param('DBInstanceClass'),
"db_instance_identifier": self._get_param('DBInstanceIdentifier'), "db_instance_identifier": self._get_param('DBInstanceIdentifier'),
"db_name": self._get_param("DBName"), "db_name": self._get_param("DBName"),
# DBParameterGroupName "db_parameter_group_name": self._get_param("DBParameterGroupName"),
"db_subnet_group_name": self._get_param("DBSubnetGroupName"), "db_subnet_group_name": self._get_param("DBSubnetGroupName"),
"engine": self._get_param("Engine"), "engine": self._get_param("Engine"),
"engine_version": self._get_param("EngineVersion"), "engine_version": self._get_param("EngineVersion"),
"iops": self._get_int_param("Iops"), "iops": self._get_int_param("Iops"),
"kms_key_id": self._get_param("KmsKeyId"),
"master_user_password": self._get_param('MasterUserPassword'), "master_user_password": self._get_param('MasterUserPassword'),
"master_username": self._get_param('MasterUsername'), "master_username": self._get_param('MasterUsername'),
"multi_az": self._get_bool_param("MultiAZ"), "multi_az": self._get_bool_param("MultiAZ"),
@ -36,12 +39,13 @@ class RDS2Response(BaseResponse):
# PreferredMaintenanceWindow # PreferredMaintenanceWindow
"publicly_accessible": self._get_param("PubliclyAccessible"), "publicly_accessible": self._get_param("PubliclyAccessible"),
"region": self.region, "region": self.region,
"security_groups": self._get_multi_param('DBSecurityGroups.member'), "security_groups": self._get_multi_param('DBSecurityGroups.DBSecurityGroupName'),
"storage_encrypted": self._get_param("StorageEncrypted"),
"storage_type": self._get_param("StorageType"), "storage_type": self._get_param("StorageType"),
# VpcSecurityGroupIds.member.N # VpcSecurityGroupIds.member.N
"tags": list() "tags": list(),
} }
args['tags'] = self.unpack_complex_list_params('Tags.member', ('Key', 'Value')) args['tags'] = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value'))
return args return args
def _get_db_replica_kwargs(self): def _get_db_replica_kwargs(self):
@ -67,6 +71,14 @@ class RDS2Response(BaseResponse):
'name': self._get_param('OptionGroupName') 'name': self._get_param('OptionGroupName')
} }
def _get_db_parameter_group_kwargs(self):
return {
'description': self._get_param('Description'),
'family': self._get_param('DBParameterGroupFamily'),
'name': self._get_param('DBParameterGroupName'),
'tags': self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value')),
}
def unpack_complex_list_params(self, label, names): def unpack_complex_list_params(self, label, names):
unpacked_list = list() unpacked_list = list()
count = 1 count = 1
@ -150,7 +162,7 @@ class RDS2Response(BaseResponse):
def add_tags_to_resource(self): def add_tags_to_resource(self):
arn = self._get_param('ResourceName') arn = self._get_param('ResourceName')
tags = self.unpack_complex_list_params('Tags.member', ('Key', 'Value')) tags = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value'))
tags = self.backend.add_tags_to_resource(arn, tags) tags = self.backend.add_tags_to_resource(arn, tags)
template = self.response_template(ADD_TAGS_TO_RESOURCE_TEMPLATE) template = self.response_template(ADD_TAGS_TO_RESOURCE_TEMPLATE)
return template.render(tags=tags) return template.render(tags=tags)
@ -168,7 +180,8 @@ class RDS2Response(BaseResponse):
def create_db_security_group(self): def create_db_security_group(self):
group_name = self._get_param('DBSecurityGroupName') group_name = self._get_param('DBSecurityGroupName')
description = self._get_param('DBSecurityGroupDescription') description = self._get_param('DBSecurityGroupDescription')
security_group = self.backend.create_security_group(group_name, description) tags = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value'))
security_group = self.backend.create_security_group(group_name, description, tags)
template = self.response_template(CREATE_SECURITY_GROUP_TEMPLATE) template = self.response_template(CREATE_SECURITY_GROUP_TEMPLATE)
return template.render(security_group=security_group) return template.render(security_group=security_group)
@ -206,9 +219,10 @@ class RDS2Response(BaseResponse):
def create_db_subnet_group(self): def create_db_subnet_group(self):
subnet_name = self._get_param('DBSubnetGroupName') subnet_name = self._get_param('DBSubnetGroupName')
description = self._get_param('DBSubnetGroupDescription') description = self._get_param('DBSubnetGroupDescription')
subnet_ids = self._get_multi_param('SubnetIds.member') subnet_ids = self._get_multi_param('SubnetIds.SubnetIdentifier')
tags = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value'))
subnets = [ec2_backends[self.region].get_subnet(subnet_id) for subnet_id in subnet_ids] subnets = [ec2_backends[self.region].get_subnet(subnet_id) for subnet_id in subnet_ids]
subnet_group = self.backend.create_subnet_group(subnet_name, description, subnets) subnet_group = self.backend.create_subnet_group(subnet_name, description, subnets, tags)
template = self.response_template(CREATE_SUBNET_GROUP_TEMPLATE) template = self.response_template(CREATE_SUBNET_GROUP_TEMPLATE)
return template.render(subnet_group=subnet_group) return template.render(subnet_group=subnet_group)
@ -283,225 +297,326 @@ class RDS2Response(BaseResponse):
template = self.response_template(MODIFY_OPTION_GROUP_TEMPLATE) template = self.response_template(MODIFY_OPTION_GROUP_TEMPLATE)
return template.render(option_group=option_group) return template.render(option_group=option_group)
def create_dbparameter_group(self):
return self.create_db_parameter_group()
CREATE_DATABASE_TEMPLATE = """{ def create_db_parameter_group(self):
"CreateDBInstanceResponse": { kwargs = self._get_db_parameter_group_kwargs()
"CreateDBInstanceResult": { db_parameter_group = self.backend.create_db_parameter_group(kwargs)
"DBInstance": {{ database.to_json() }} template = self.response_template(CREATE_DB_PARAMETER_GROUP_TEMPLATE)
}, return template.render(db_parameter_group=db_parameter_group)
"ResponseMetadata": { "RequestId": "523e3218-afc7-11c3-90f5-f90431260ab4" }
}
}"""
CREATE_DATABASE_REPLICA_TEMPLATE = """{"CreateDBInstanceReadReplicaResponse": { def describe_dbparameter_groups(self):
"ResponseMetadata": { return self.describe_db_parameter_groups()
"RequestId": "5e60c46d-a844-11e4-bb68-17f36418e58f"
},
"CreateDBInstanceReadReplicaResult": {
"DBInstance": {{ database.to_json() }}
}
}}"""
DESCRIBE_DATABASES_TEMPLATE = """{ def describe_db_parameter_groups(self):
"DescribeDBInstancesResponse": { kwargs = self._get_db_parameter_group_kwargs()
"DescribeDBInstancesResult": { kwargs['max_records'] = self._get_param('MaxRecords')
"DBInstances": [ kwargs['marker'] = self._get_param('Marker')
{%- for database in databases -%} db_parameter_groups = self.backend.describe_db_parameter_groups(kwargs)
{%- if loop.index != 1 -%},{%- endif -%} template = self.response_template(DESCRIBE_DB_PARAMETER_GROUPS_TEMPLATE)
{{ database.to_json() }} return template.render(db_parameter_groups=db_parameter_groups)
def modify_dbparameter_group(self):
return self.modify_db_parameter_group()
def modify_db_parameter_group(self):
db_parameter_group_name = self._get_param('DBParameterGroupName')
db_parameter_group_parameters = self._get_db_parameter_group_paramters()
db_parameter_group = self.backend.modify_db_parameter_group(db_parameter_group_name,
db_parameter_group_parameters)
template = self.response_template(MODIFY_DB_PARAMETER_GROUP_TEMPLATE)
return template.render(db_parameter_group=db_parameter_group)
def _get_db_parameter_group_paramters(self):
parameter_group_parameters = defaultdict(dict)
for param_name, value in self.querystring.items():
if not param_name.startswith('Parameters.Parameter'):
continue
split_param_name = param_name.split('.')
param_id = split_param_name[2]
param_setting = split_param_name[3]
parameter_group_parameters[param_id][param_setting] = value[0]
return parameter_group_parameters.values()
def describe_dbparameters(self):
return self.describe_db_parameters()
def describe_db_parameters(self):
db_parameter_group_name = self._get_param('DBParameterGroupName')
db_parameter_groups = self.backend.describe_db_parameter_groups({'name': db_parameter_group_name})
if not db_parameter_groups:
raise DBParameterGroupNotFoundError(db_parameter_group_name)
template = self.response_template(DESCRIBE_DB_PARAMETERS_TEMPLATE)
return template.render(db_parameter_group=db_parameter_groups[0])
def delete_dbparameter_group(self):
return self.delete_db_parameter_group()
def delete_db_parameter_group(self):
kwargs = self._get_db_parameter_group_kwargs()
db_parameter_group = self.backend.delete_db_parameter_group(kwargs['name'])
template = self.response_template(DELETE_DB_PARAMETER_GROUP_TEMPLATE)
return template.render(db_parameter_group=db_parameter_group)
CREATE_DATABASE_TEMPLATE = """<CreateDBInstanceResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<CreateDBInstanceResult>
{{ database.to_xml() }}
</CreateDBInstanceResult>
<ResponseMetadata>
<RequestId>523e3218-afc7-11c3-90f5-f90431260ab4</RequestId>
</ResponseMetadata>
</CreateDBInstanceResponse>"""
CREATE_DATABASE_REPLICA_TEMPLATE = """<CreateDBInstanceReadReplicaResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<CreateDBInstanceReadReplicaResult>
{{ database.to_xml() }}
</CreateDBInstanceReadReplicaResult>
<ResponseMetadata>
<RequestId>5e60c46d-a844-11e4-bb68-17f36418e58f</RequestId>
</ResponseMetadata>
</CreateDBInstanceReadReplicaResponse>"""
DESCRIBE_DATABASES_TEMPLATE = """<DescribeDBInstancesResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<DescribeDBInstancesResult>
<DBInstances>
{%- for database in databases -%}
{{ database.to_xml() }}
{%- endfor -%}
</DBInstances>
</DescribeDBInstancesResult>
<ResponseMetadata>
<RequestId>523e3218-afc7-11c3-90f5-f90431260ab4</RequestId>
</ResponseMetadata>
</DescribeDBInstancesResponse>"""
MODIFY_DATABASE_TEMPLATE = """<ModifyDBInstanceResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<ModifyDBInstanceResult>
{{ database.to_xml() }}
</ModifyDBInstanceResult>
<ResponseMetadata>
<RequestId>bb58476c-a1a8-11e4-99cf-55e92d4bbada</RequestId>
</ResponseMetadata>
</ModifyDBInstanceResponse>"""
REBOOT_DATABASE_TEMPLATE = """<RebootDBInstanceResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<RebootDBInstanceResult>
{{ database.to_xml() }}
</RebootDBInstanceResult>
<ResponseMetadata>
<RequestId>d55711cb-a1ab-11e4-99cf-55e92d4bbada</RequestId>
</ResponseMetadata>
</RebootDBInstanceResponse>"""
DELETE_DATABASE_TEMPLATE = """<DeleteDBInstanceResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<DeleteDBInstanceResult>
{{ database.to_xml() }}
</DeleteDBInstanceResult>
<ResponseMetadata>
<RequestId>7369556f-b70d-11c3-faca-6ba18376ea1b</RequestId>
</ResponseMetadata>
</DeleteDBInstanceResponse>"""
CREATE_SECURITY_GROUP_TEMPLATE = """<CreateDBSecurityGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<CreateDBSecurityGroupResult>
{{ security_group.to_xml() }}
</CreateDBSecurityGroupResult>
<ResponseMetadata>
<RequestId>462165d0-a77a-11e4-a5fa-75b30c556f97</RequestId>
</ResponseMetadata>
</CreateDBSecurityGroupResponse>"""
DESCRIBE_SECURITY_GROUPS_TEMPLATE = """<DescribeDBSecurityGroupsResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<DescribeDBSecurityGroupsResult>
<DBSecurityGroups>
{% for security_group in security_groups %}
{{ security_group.to_xml() }}
{% endfor %}
</DBSecurityGroups>
</DescribeDBSecurityGroupsResult>
<ResponseMetadata>
<RequestId>5df2014e-a779-11e4-bdb0-594def064d0c</RequestId>
</ResponseMetadata>
</DescribeDBSecurityGroupsResponse>"""
DELETE_SECURITY_GROUP_TEMPLATE = """<DeleteDBSecurityGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<ResponseMetadata>
<RequestId>97e846bd-a77d-11e4-ac58-91351c0f3426</RequestId>
</ResponseMetadata>
</DeleteDBSecurityGroupResponse>"""
AUTHORIZE_SECURITY_GROUP_TEMPLATE = """<AuthorizeDBSecurityGroupIngressResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<AuthorizeDBSecurityGroupIngressResult>
{{ security_group.to_xml() }}
</AuthorizeDBSecurityGroupIngressResult>
<ResponseMetadata>
<RequestId>75d32fd5-a77e-11e4-8892-b10432f7a87d</RequestId>
</ResponseMetadata>
</AuthorizeDBSecurityGroupIngressResponse>"""
CREATE_SUBNET_GROUP_TEMPLATE = """<CreateDBSubnetGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<CreateDBSubnetGroupResult>
{{ subnet_group.to_xml() }}
</CreateDBSubnetGroupResult>
<ResponseMetadata>
<RequestId>3a401b3f-bb9e-11d3-f4c6-37db295f7674</RequestId>
</ResponseMetadata>
</CreateDBSubnetGroupResponse>"""
DESCRIBE_SUBNET_GROUPS_TEMPLATE = """<DescribeDBSubnetGroupsResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<DescribeDBSubnetGroupsResult>
<DBSubnetGroups>
{% for subnet_group in subnet_groups %}
{{ subnet_group.to_xml() }}
{% endfor %}
</DBSubnetGroups>
</DescribeDBSubnetGroupsResult>
<ResponseMetadata>
<RequestId>b783db3b-b98c-11d3-fbc7-5c0aad74da7c</RequestId>
</ResponseMetadata>
</DescribeDBSubnetGroupsResponse>"""
DELETE_SUBNET_GROUP_TEMPLATE = """<DeleteDBSubnetGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<ResponseMetadata>
<RequestId>13785dd5-a7fc-11e4-bb9c-7f371d0859b0</RequestId>
</ResponseMetadata>
</DeleteDBSubnetGroupResponse>"""
CREATE_OPTION_GROUP_TEMPLATE = """<CreateOptionGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<CreateOptionGroupResult>
{{ option_group.to_xml() }}
</CreateOptionGroupResult>
<ResponseMetadata>
<RequestId>1e38dad4-9f50-11e4-87ea-a31c60ed2e36</RequestId>
</ResponseMetadata>
</CreateOptionGroupResponse>"""
DELETE_OPTION_GROUP_TEMPLATE = """<DeleteOptionGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<ResponseMetadata>
<RequestId>e2590367-9fa2-11e4-99cf-55e92d41c60e</RequestId>
</ResponseMetadata>
</DeleteOptionGroupResponse>"""
DESCRIBE_OPTION_GROUP_TEMPLATE = """<DescribeOptionGroupsResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<DescribeOptionGroupsResult>
<OptionGroupsList>
{%- for option_group in option_groups -%}
{{ option_group.to_xml() }}
{%- endfor -%}
</OptionGroupsList>
</DescribeOptionGroupsResult>
<ResponseMetadata>
<RequestId>4caf445d-9fbc-11e4-87ea-a31c60ed2e36</RequestId>
</ResponseMetadata>
</DescribeOptionGroupsResponse>"""
DESCRIBE_OPTION_GROUP_OPTIONS_TEMPLATE = """<DescribeOptionGroupOptionsResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<DescribeOptionGroupOptionsResult>
<OptionGroupOptions>
{%- for option_group_option in option_group_options -%}
{{ option_group_option.to_xml() }}
{%- endfor -%}
</OptionGroupOptions>
</DescribeOptionGroupOptionsResult>
<ResponseMetadata>
<RequestId>457f7bb8-9fbf-11e4-9084-5754f80d5144</RequestId>
</ResponseMetadata>
</DescribeOptionGroupOptionsResponse>"""
MODIFY_OPTION_GROUP_TEMPLATE = """<ModifyOptionGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<ModifyOptionGroupResult>
{{ option_group.to_xml() }}
</ModifyOptionGroupResult>
<ResponseMetadata>
<RequestId>ce9284a5-a0de-11e4-b984-a11a53e1f328</RequestId>
</ResponseMetadata>
</ModifyOptionGroupResponse>"""
CREATE_DB_PARAMETER_GROUP_TEMPLATE = """<CreateDBParameterGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<CreateDBParameterGroupResult>
{{ db_parameter_group.to_xml() }}
</CreateDBParameterGroupResult>
<ResponseMetadata>
<RequestId>7805c127-af22-11c3-96ac-6999cc5f7e72</RequestId>
</ResponseMetadata>
</CreateDBParameterGroupResponse>"""
DESCRIBE_DB_PARAMETER_GROUPS_TEMPLATE = """<DescribeDBParameterGroupsResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<DescribeDBParameterGroupsResult>
<DBParameterGroups>
{%- for db_parameter_group in db_parameter_groups -%}
{{ db_parameter_group.to_xml() }}
{%- endfor -%}
</DBParameterGroups>
</DescribeDBParameterGroupsResult>
<ResponseMetadata>
<RequestId>b75d527a-b98c-11d3-f272-7cd6cce12cc5</RequestId>
</ResponseMetadata>
</DescribeDBParameterGroupsResponse>"""
MODIFY_DB_PARAMETER_GROUP_TEMPLATE = """<ModifyDBParameterGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<ModifyDBParameterGroupResult>
<DBParameterGroupName>{{ db_parameter_group.name }}</DBParameterGroupName>
</ModifyDBParameterGroupResult>
<ResponseMetadata>
<RequestId>12d7435e-bba0-11d3-fe11-33d33a9bb7e3</RequestId>
</ResponseMetadata>
</ModifyDBParameterGroupResponse>"""
DELETE_DB_PARAMETER_GROUP_TEMPLATE = """<DeleteDBParameterGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<ResponseMetadata>
<RequestId>cad6c267-ba25-11d3-fe11-33d33a9bb7e3</RequestId>
</ResponseMetadata>
</DeleteDBParameterGroupResponse>"""
DESCRIBE_DB_PARAMETERS_TEMPLATE = """<DescribeDBParametersResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<DescribeDBParametersResult>
<Parameters>
{%- for db_parameter_name, db_parameter in db_parameter_group.parameters.items() -%}
<Parameter>
{%- for parameter_name, parameter_value in db_parameter.items() -%}
<{{ parameter_name }}>{{ parameter_value }}</{{ parameter_name }}>
{%- endfor -%} {%- endfor -%}
] </Parameter>
}, {%- endfor -%}
"ResponseMetadata": { "RequestId": "523e3218-afc7-11c3-90f5-f90431260ab4" } </Parameters>
} </DescribeDBParametersResult>
}""" <ResponseMetadata>
<RequestId>8c40488f-b9ff-11d3-a15e-7ac49293f4fa</RequestId>
</ResponseMetadata>
</DescribeDBParametersResponse>
"""
MODIFY_DATABASE_TEMPLATE = """{"ModifyDBInstanceResponse": { LIST_TAGS_FOR_RESOURCE_TEMPLATE = """<ListTagsForResourceResponse xmlns="http://rds.amazonaws.com/doc/2014-10-31/">
"ModifyDBInstanceResult": { <ListTagsForResourceResult>
"DBInstance": {{ database.to_json() }}, <TagList>
"ResponseMetadata": { {%- for tag in tags -%}
"RequestId": "bb58476c-a1a8-11e4-99cf-55e92d4bbada" <Tag>
} <Key>{{ tag['Key'] }}</Key>
} <Value>{{ tag['Value'] }}</Value>
} </Tag>
}""" {%- endfor -%}
</TagList>
</ListTagsForResourceResult>
<ResponseMetadata>
<RequestId>8c21ba39-a598-11e4-b688-194eaf8658fa</RequestId>
</ResponseMetadata>
</ListTagsForResourceResponse>"""
REBOOT_DATABASE_TEMPLATE = """{"RebootDBInstanceResponse": { ADD_TAGS_TO_RESOURCE_TEMPLATE = """<AddTagsToResourceResponse xmlns="http://rds.amazonaws.com/doc/2014-10-31/">
"RebootDBInstanceResult": { <ResponseMetadata>
"DBInstance": {{ database.to_json() }}, <RequestId>b194d9ca-a664-11e4-b688-194eaf8658fa</RequestId>
"ResponseMetadata": { </ResponseMetadata>
"RequestId": "d55711cb-a1ab-11e4-99cf-55e92d4bbada" </AddTagsToResourceResponse>"""
}
}
}
}"""
REMOVE_TAGS_FROM_RESOURCE_TEMPLATE = """<RemoveTagsFromResourceResponse xmlns="http://rds.amazonaws.com/doc/2014-10-31/">
DELETE_DATABASE_TEMPLATE = """{ "DeleteDBInstanceResponse": { <ResponseMetadata>
"DeleteDBInstanceResult": { <RequestId>b194d9ca-a664-11e4-b688-194eaf8658fa</RequestId>
"DBInstance": {{ database.to_json() }} </ResponseMetadata>
}, </RemoveTagsFromResourceResponse>"""
"ResponseMetadata": {
"RequestId": "523e3218-afc7-11c3-90f5-f90431260ab4"
}
}
}"""
CREATE_SECURITY_GROUP_TEMPLATE = """{"CreateDBSecurityGroupResponse": {
"CreateDBSecurityGroupResult": {
"DBSecurityGroup":
{{ security_group.to_json() }},
"ResponseMetadata": {
"RequestId": "462165d0-a77a-11e4-a5fa-75b30c556f97"
}}
}
}"""
DESCRIBE_SECURITY_GROUPS_TEMPLATE = """{
"DescribeDBSecurityGroupsResponse": {
"ResponseMetadata": {
"RequestId": "5df2014e-a779-11e4-bdb0-594def064d0c"
},
"DescribeDBSecurityGroupsResult": {
"Marker": "null",
"DBSecurityGroups": [
{% for security_group in security_groups %}
{%- if loop.index != 1 -%},{%- endif -%}
{{ security_group.to_json() }}
{% endfor %}
]
}
}
}"""
DELETE_SECURITY_GROUP_TEMPLATE = """{"DeleteDBSecurityGroupResponse": {
"ResponseMetadata": {
"RequestId": "97e846bd-a77d-11e4-ac58-91351c0f3426"
}
}}"""
AUTHORIZE_SECURITY_GROUP_TEMPLATE = """{
"AuthorizeDBSecurityGroupIngressResponse": {
"AuthorizeDBSecurityGroupIngressResult": {
"DBSecurityGroup": {{ security_group.to_json() }}
},
"ResponseMetadata": {
"RequestId": "75d32fd5-a77e-11e4-8892-b10432f7a87d"
}
}
}"""
CREATE_SUBNET_GROUP_TEMPLATE = """{
"CreateDBSubnetGroupResponse": {
"CreateDBSubnetGroupResult":
{ {{ subnet_group.to_json() }} },
"ResponseMetadata": { "RequestId": "3a401b3f-bb9e-11d3-f4c6-37db295f7674" }
}
}"""
DESCRIBE_SUBNET_GROUPS_TEMPLATE = """{
"DescribeDBSubnetGroupsResponse": {
"DescribeDBSubnetGroupsResult": {
"DBSubnetGroups": [
{% for subnet_group in subnet_groups %}
{ {{ subnet_group.to_json() }} }{%- if not loop.last -%},{%- endif -%}
{% endfor %}
],
"Marker": null
},
"ResponseMetadata": { "RequestId": "b783db3b-b98c-11d3-fbc7-5c0aad74da7c" }
}
}"""
DELETE_SUBNET_GROUP_TEMPLATE = """{"DeleteDBSubnetGroupResponse": {"ResponseMetadata": {"RequestId": "13785dd5-a7fc-11e4-bb9c-7f371d0859b0"}}}"""
CREATE_OPTION_GROUP_TEMPLATE = """{
"CreateOptionGroupResponse": {
"CreateOptionGroupResult": {
"OptionGroup": {{ option_group.to_json() }}
},
"ResponseMetadata": {
"RequestId": "1e38dad4-9f50-11e4-87ea-a31c60ed2e36"
}
}
}"""
DELETE_OPTION_GROUP_TEMPLATE = \
"""{"DeleteOptionGroupResponse": {"ResponseMetadata": {"RequestId": "e2590367-9fa2-11e4-99cf-55e92d41c60e"}}}"""
DESCRIBE_OPTION_GROUP_TEMPLATE = \
"""{"DescribeOptionGroupsResponse": {
"DescribeOptionGroupsResult": {
"Marker": null,
"OptionGroupsList": [
{%- for option_group in option_groups -%}
{%- if loop.index != 1 -%},{%- endif -%}
{{ option_group.to_json() }}
{%- endfor -%}
]},
"ResponseMetadata": {"RequestId": "4caf445d-9fbc-11e4-87ea-a31c60ed2e36"}
}}"""
DESCRIBE_OPTION_GROUP_OPTIONS_TEMPLATE = \
"""{"DescribeOptionGroupOptionsResponse": {
"DescribeOptionGroupOptionsResult": {
"Marker": null,
"OptionGroupOptions": [
{%- for option_group_option in option_group_options -%}
{%- if loop.index != 1 -%},{%- endif -%}
{{ option_group_option.to_json() }}
{%- endfor -%}
]},
"ResponseMetadata": {"RequestId": "457f7bb8-9fbf-11e4-9084-5754f80d5144"}
}}"""
MODIFY_OPTION_GROUP_TEMPLATE = \
"""{"ModifyOptionGroupResponse": {
"ResponseMetadata": {
"RequestId": "ce9284a5-a0de-11e4-b984-a11a53e1f328"
},
"ModifyOptionGroupResult":
{{ option_group.to_json() }}
}
}"""
LIST_TAGS_FOR_RESOURCE_TEMPLATE = \
"""{"ListTagsForResourceResponse":
{"ListTagsForResourceResult":
{"TagList": [
{%- for tag in tags -%}
{%- if loop.index != 1 -%},{%- endif -%}
{
"Key": "{{ tag['Key'] }}",
"Value": "{{ tag['Value'] }}"
}
{%- endfor -%}
]},
"ResponseMetadata": {
"RequestId": "8c21ba39-a598-11e4-b688-194eaf8658fa"
}
}
}"""
ADD_TAGS_TO_RESOURCE_TEMPLATE = \
"""{"ListTagsForResourceResponse": {
"ListTagsForResourceResult": {
"TagList": [
{%- for tag in tags -%}
{%- if loop.index != 1 -%},{%- endif -%}
{
"Key": "{{ tag['Key'] }}",
"Value": "{{ tag['Value'] }}"
}
{%- endfor -%}
]},
"ResponseMetadata": {
"RequestId": "b194d9ca-a664-11e4-b688-194eaf8658fa"
}
}
}"""
REMOVE_TAGS_FROM_RESOURCE_TEMPLATE = \
"""{"RemoveTagsFromResourceResponse": {"ResponseMetadata": {"RequestId": "c6499a01-a664-11e4-8069-fb454b71a80e"}}}
"""

View File

@ -0,0 +1,201 @@
from __future__ import unicode_literals
template = {
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "AWS CloudFormation Sample Template RDS_MySQL_With_Read_Replica: Sample template showing how to create a highly-available, RDS DBInstance with a read replica. **WARNING** This template creates an Amazon Relational Database Service database instance and Amazon CloudWatch alarms. You will be billed for the AWS resources used if you create a stack from this template.",
"Parameters": {
"DBName": {
"Default": "MyDatabase",
"Description" : "The database name",
"Type": "String",
"MinLength": "1",
"MaxLength": "64",
"AllowedPattern" : "[a-zA-Z][a-zA-Z0-9]*",
"ConstraintDescription" : "must begin with a letter and contain only alphanumeric characters."
},
"DBInstanceIdentifier": {
"Type": "String"
},
"DBUser": {
"NoEcho": "true",
"Description" : "The database admin account username",
"Type": "String",
"MinLength": "1",
"MaxLength": "16",
"AllowedPattern" : "[a-zA-Z][a-zA-Z0-9]*",
"ConstraintDescription" : "must begin with a letter and contain only alphanumeric characters."
},
"DBPassword": {
"NoEcho": "true",
"Description" : "The database admin account password",
"Type": "String",
"MinLength": "1",
"MaxLength": "41",
"AllowedPattern" : "[a-zA-Z0-9]+",
"ConstraintDescription" : "must contain only alphanumeric characters."
},
"DBAllocatedStorage": {
"Default": "5",
"Description" : "The size of the database (Gb)",
"Type": "Number",
"MinValue": "5",
"MaxValue": "1024",
"ConstraintDescription" : "must be between 5 and 1024Gb."
},
"DBInstanceClass": {
"Description" : "The database instance type",
"Type": "String",
"Default": "db.m1.small",
"AllowedValues" : [ "db.t1.micro", "db.m1.small", "db.m1.medium", "db.m1.large", "db.m1.xlarge", "db.m2.xlarge", "db.m2.2xlarge", "db.m2.4xlarge", "db.m3.medium", "db.m3.large", "db.m3.xlarge", "db.m3.2xlarge", "db.r3.large", "db.r3.xlarge", "db.r3.2xlarge", "db.r3.4xlarge", "db.r3.8xlarge", "db.m2.xlarge", "db.m2.2xlarge", "db.m2.4xlarge", "db.cr1.8xlarge"]
,
"ConstraintDescription" : "must select a valid database instance type."
},
"EC2SecurityGroup": {
"Description" : "The EC2 security group that contains instances that need access to the database",
"Default": "default",
"Type": "String",
"AllowedPattern" : "[a-zA-Z0-9\\-]+",
"ConstraintDescription" : "must be a valid security group name."
},
"MultiAZ" : {
"Description" : "Multi-AZ master database",
"Type" : "String",
"Default" : "false",
"AllowedValues" : [ "true", "false" ],
"ConstraintDescription" : "must be true or false."
}
},
"Conditions" : {
"Is-EC2-VPC" : { "Fn::Or" : [ {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "eu-central-1" ]},
{"Fn::Equals" : [{"Ref" : "AWS::Region"}, "cn-north-1" ]}]},
"Is-EC2-Classic" : { "Fn::Not" : [{ "Condition" : "Is-EC2-VPC"}]}
},
"Resources" : {
"DBParameterGroup": {
"Type": "AWS::RDS::DBParameterGroup",
"Properties" : {
"Description": "DB Parameter Goup",
"Family" : "MySQL5.1",
"Parameters": {
"BACKLOG_QUEUE_LIMIT": "2048"
}
}
},
"DBEC2SecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Condition" : "Is-EC2-VPC",
"Properties" : {
"GroupDescription": "Open database for access",
"SecurityGroupIngress" : [{
"IpProtocol" : "tcp",
"FromPort" : "3306",
"ToPort" : "3306",
"SourceSecurityGroupName" : { "Ref" : "EC2SecurityGroup" }
}]
}
},
"DBSecurityGroup": {
"Type": "AWS::RDS::DBSecurityGroup",
"Condition" : "Is-EC2-Classic",
"Properties": {
"DBSecurityGroupIngress": [{
"EC2SecurityGroupName": { "Ref": "EC2SecurityGroup" }
}],
"GroupDescription": "database access"
}
},
"my_vpc": {
"Type" : "AWS::EC2::VPC",
"Properties" : {
"CidrBlock" : "10.0.0.0/16",
}
},
"EC2Subnet": {
"Type" : "AWS::EC2::Subnet",
"Condition" : "Is-EC2-VPC",
"Properties" : {
"AvailabilityZone" : "eu-central-1a",
"CidrBlock" : "10.0.1.0/24",
"VpcId" : { "Ref" : "my_vpc" }
}
},
"DBSubnet": {
"Type": "AWS::RDS::DBSubnetGroup",
"Condition" : "Is-EC2-VPC",
"Properties": {
"DBSubnetGroupDescription": "my db subnet group",
"SubnetIds" : [ { "Ref": "EC2Subnet" } ],
}
},
"MasterDB" : {
"Type" : "AWS::RDS::DBInstance",
"Properties" : {
"DBInstanceIdentifier": { "Ref": "DBInstanceIdentifier" },
"DBName" : { "Ref" : "DBName" },
"AllocatedStorage" : { "Ref" : "DBAllocatedStorage" },
"DBInstanceClass" : { "Ref" : "DBInstanceClass" },
"Engine" : "MySQL",
"DBSubnetGroupName": {"Fn::If": ["Is-EC2-VPC", { "Ref": "DBSubnet" }, { "Ref": "AWS::NoValue" }]},
"MasterUsername" : { "Ref" : "DBUser" },
"MasterUserPassword" : { "Ref" : "DBPassword" },
"MultiAZ" : { "Ref" : "MultiAZ" },
"Tags" : [{ "Key" : "Name", "Value" : "Master Database" }],
"VPCSecurityGroups": { "Fn::If" : [ "Is-EC2-VPC", [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], { "Ref" : "AWS::NoValue"}]},
"DBSecurityGroups": { "Fn::If" : [ "Is-EC2-Classic", [ { "Ref": "DBSecurityGroup" } ], { "Ref" : "AWS::NoValue"}]}
},
"DeletionPolicy" : "Snapshot"
},
"ReplicaDB" : {
"Type" : "AWS::RDS::DBInstance",
"Properties" : {
"SourceDBInstanceIdentifier" : { "Ref" : "MasterDB" },
"DBInstanceClass" : { "Ref" : "DBInstanceClass" },
"Tags" : [{ "Key" : "Name", "Value" : "Read Replica Database" }]
}
}
},
"Outputs" : {
"EC2Platform" : {
"Description" : "Platform in which this stack is deployed",
"Value" : { "Fn::If" : [ "Is-EC2-VPC", "EC2-VPC", "EC2-Classic" ]}
},
"MasterJDBCConnectionString": {
"Description" : "JDBC connection string for the master database",
"Value" : { "Fn::Join": [ "", [ "jdbc:mysql://",
{ "Fn::GetAtt": [ "MasterDB", "Endpoint.Address" ] },
":",
{ "Fn::GetAtt": [ "MasterDB", "Endpoint.Port" ] },
"/",
{ "Ref": "DBName" }]]}
},
"ReplicaJDBCConnectionString": {
"Description" : "JDBC connection string for the replica database",
"Value" : { "Fn::Join": [ "", [ "jdbc:mysql://",
{ "Fn::GetAtt": [ "ReplicaDB", "Endpoint.Address" ] },
":",
{ "Fn::GetAtt": [ "ReplicaDB", "Endpoint.Port" ] },
"/",
{ "Ref": "DBName" }]]}
}
}
}

View File

@ -82,7 +82,6 @@ template = {
}, },
"Resources" : { "Resources" : {
"DBEC2SecurityGroup": { "DBEC2SecurityGroup": {
"Type": "AWS::EC2::SecurityGroup", "Type": "AWS::EC2::SecurityGroup",
"Condition" : "Is-EC2-VPC", "Condition" : "Is-EC2-VPC",
@ -101,9 +100,9 @@ template = {
"Type": "AWS::RDS::DBSecurityGroup", "Type": "AWS::RDS::DBSecurityGroup",
"Condition" : "Is-EC2-Classic", "Condition" : "Is-EC2-Classic",
"Properties": { "Properties": {
"DBSecurityGroupIngress": { "DBSecurityGroupIngress": [{
"EC2SecurityGroupName": { "Ref": "EC2SecurityGroup" } "EC2SecurityGroupName": { "Ref": "EC2SecurityGroup" }
}, }],
"GroupDescription": "database access" "GroupDescription": "database access"
} }
}, },
@ -188,4 +187,4 @@ template = {
{ "Ref": "DBName" }]]} { "Ref": "DBName" }]]}
} }
} }
} }

View File

@ -27,6 +27,7 @@ from moto import (
mock_kms, mock_kms,
mock_lambda, mock_lambda,
mock_rds, mock_rds,
mock_rds2,
mock_redshift, mock_redshift,
mock_route53, mock_route53,
mock_sns, mock_sns,
@ -36,6 +37,7 @@ from moto import (
from .fixtures import ( from .fixtures import (
ec2_classic_eip, ec2_classic_eip,
fn_join, fn_join,
rds_mysql_with_db_parameter_group,
rds_mysql_with_read_replica, rds_mysql_with_read_replica,
redshift, redshift,
route53_ec2_instance_with_public_ip, route53_ec2_instance_with_public_ip,
@ -693,6 +695,44 @@ def test_vpc_single_instance_in_subnet():
eip_resource = [resource for resource in resources if resource.resource_type == 'AWS::EC2::EIP'][0] eip_resource = [resource for resource in resources if resource.resource_type == 'AWS::EC2::EIP'][0]
eip_resource.physical_resource_id.should.equal(eip.allocation_id) eip_resource.physical_resource_id.should.equal(eip.allocation_id)
@mock_cloudformation()
@mock_ec2()
@mock_rds2()
def test_rds_db_parameter_groups():
ec2_conn = boto.ec2.connect_to_region("us-west-1")
ec2_conn.create_security_group('application', 'Our Application Group')
template_json = json.dumps(rds_mysql_with_db_parameter_group.template)
conn = boto.cloudformation.connect_to_region("us-west-1")
conn.create_stack(
"test_stack",
template_body=template_json,
parameters=[
("DBInstanceIdentifier", "master_db"),
("DBName", "my_db"),
("DBUser", "my_user"),
("DBPassword", "my_password"),
("DBAllocatedStorage", "20"),
("DBInstanceClass", "db.m1.medium"),
("EC2SecurityGroup", "application"),
("MultiAZ", "true"),
],
)
rds_conn = boto3.client('rds', region_name="us-west-1")
db_parameter_groups = rds_conn.describe_db_parameter_groups()
len(db_parameter_groups['DBParameterGroups']).should.equal(1)
db_parameter_group_name = db_parameter_groups['DBParameterGroups'][0]['DBParameterGroupName']
found_cloudformation_set_parameter = False
for db_parameter in rds_conn.describe_db_parameters(DBParameterGroupName=db_parameter_group_name)['Parameters']:
if db_parameter['ParameterName'] == 'BACKLOG_QUEUE_LIMIT' and db_parameter['ParameterValue'] == '2048':
found_cloudformation_set_parameter = True
found_cloudformation_set_parameter.should.equal(True)
@mock_cloudformation() @mock_cloudformation()
@mock_ec2() @mock_ec2()

View File

@ -238,6 +238,32 @@ def test_create_database_replica():
primary = conn.get_all_dbinstances("db-master-1")[0] primary = conn.get_all_dbinstances("db-master-1")[0]
list(primary.read_replica_dbinstance_identifiers).should.have.length_of(0) list(primary.read_replica_dbinstance_identifiers).should.have.length_of(0)
@disable_on_py3()
@mock_rds
def test_create_cross_region_database_replica():
west_1_conn = boto.rds.connect_to_region("us-west-1")
west_2_conn = boto.rds.connect_to_region("us-west-2")
primary = west_1_conn.create_dbinstance("db-master-1", 10, 'db.m1.small', 'root', 'hunter2')
primary_arn = "arn:aws:rds:us-west-1:1234567890:db:db-master-1"
replica = west_2_conn.create_dbinstance_read_replica(
"replica",
primary_arn,
"db.m1.small",
)
primary = west_1_conn.get_all_dbinstances("db-master-1")[0]
primary.read_replica_dbinstance_identifiers[0].should.equal("replica")
replica = west_2_conn.get_all_dbinstances("replica")[0]
replica.instance_class.should.equal("db.m1.small")
west_2_conn.delete_dbinstance("replica")
primary = west_1_conn.get_all_dbinstances("db-master-1")[0]
list(primary.read_replica_dbinstance_identifiers).should.have.length_of(0)
@disable_on_py3() @disable_on_py3()
@mock_rds @mock_rds

File diff suppressed because it is too large Load Diff