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:
parent
201efd5773
commit
74bbd9c8e5
11 changed files with 1877 additions and 730 deletions
|
|
@ -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" }]]}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -82,7 +82,6 @@ template = {
|
|||
},
|
||||
|
||||
"Resources" : {
|
||||
|
||||
"DBEC2SecurityGroup": {
|
||||
"Type": "AWS::EC2::SecurityGroup",
|
||||
"Condition" : "Is-EC2-VPC",
|
||||
|
|
@ -101,9 +100,9 @@ template = {
|
|||
"Type": "AWS::RDS::DBSecurityGroup",
|
||||
"Condition" : "Is-EC2-Classic",
|
||||
"Properties": {
|
||||
"DBSecurityGroupIngress": {
|
||||
"DBSecurityGroupIngress": [{
|
||||
"EC2SecurityGroupName": { "Ref": "EC2SecurityGroup" }
|
||||
},
|
||||
}],
|
||||
"GroupDescription": "database access"
|
||||
}
|
||||
},
|
||||
|
|
@ -188,4 +187,4 @@ template = {
|
|||
{ "Ref": "DBName" }]]}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ from moto import (
|
|||
mock_kms,
|
||||
mock_lambda,
|
||||
mock_rds,
|
||||
mock_rds2,
|
||||
mock_redshift,
|
||||
mock_route53,
|
||||
mock_sns,
|
||||
|
|
@ -36,6 +37,7 @@ from moto import (
|
|||
from .fixtures import (
|
||||
ec2_classic_eip,
|
||||
fn_join,
|
||||
rds_mysql_with_db_parameter_group,
|
||||
rds_mysql_with_read_replica,
|
||||
redshift,
|
||||
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.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_ec2()
|
||||
|
|
|
|||
|
|
@ -238,6 +238,32 @@ def test_create_database_replica():
|
|||
primary = conn.get_all_dbinstances("db-master-1")[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()
|
||||
@mock_rds
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue