diff --git a/moto/rds/models.py b/moto/rds/models.py
index b7cb8ef5a..60c009010 100644
--- a/moto/rds/models.py
+++ b/moto/rds/models.py
@@ -1,5 +1,7 @@
from __future__ import unicode_literals
+import copy
+
import boto.rds
from jinja2 import Template
@@ -11,6 +13,9 @@ class Database(object):
def __init__(self, **kwargs):
self.status = "available"
+ self.is_replica = False
+ self.replicas = []
+
self.region = kwargs.get('region')
self.engine = kwargs.get("engine")
self.engine_version = kwargs.get("engine_version")
@@ -21,6 +26,7 @@ class Database(object):
self.auto_minor_version_upgrade = kwargs.get('auto_minor_version_upgrade')
self.allocated_storage = kwargs.get('allocated_storage')
self.db_instance_identifier = kwargs.get('db_instance_identifier')
+ self.source_db_identifier = kwargs.get("source_db_identifier")
self.db_instance_class = kwargs.get('db_instance_class')
self.port = kwargs.get('port')
self.db_instance_identifier = kwargs.get('db_instance_identifier')
@@ -34,6 +40,10 @@ class Database(object):
self.availability_zone = kwargs.get("availability_zone")
self.multi_az = kwargs.get("multi_az")
self.db_subnet_group_name = kwargs.get("db_subnet_group_name")
+ if self.db_subnet_group_name:
+ self.db_subnet_group = rds_backends[self.region].describe_subnet_groups(self.db_subnet_group_name)[0]
+ else:
+ self.db_subnet_group = []
self.security_groups = kwargs.get('security_groups', [])
@@ -48,6 +58,16 @@ class Database(object):
def address(self):
return "{}.aaaaaaaaaa.{}.rds.amazonaws.com".format(self.db_instance_identifier, self.region)
+ def add_replica(self, replica):
+ self.replicas.append(replica.db_instance_identifier)
+
+ def remove_replica(self, replica):
+ self.replicas.remove(replica.db_instance_identifier)
+
+ def set_as_replica(self):
+ self.is_replica = True
+ self.replicas = []
+
def update(self, db_kwargs):
for key, value in db_kwargs.items():
if value is not None:
@@ -62,7 +82,24 @@ class Database(object):
{{ database.db_instance_identifier }}
03:50-04:20
wed:06:38-wed:07:08
-
+
+ {% for replica_id in database.replicas %}
+ {{ replica_id }}
+ {% endfor %}
+
+
+ {% if database.is_replica %}
+
+ read replication
+ replicating
+ true
+
+
+ {% endif %}
+
+ {% if database.is_replica %}
+ {{ database.source_db_identifier }}
+ {% endif %}
{{ database.engine }}
general-public-license
{{ database.engine_version }}
@@ -78,6 +115,24 @@ class Database(object):
{% endfor %}
+
+ {{ database.db_subnet_group.subnet_name }}
+ {{ database.db_subnet_group.description }}
+ {{ database.db_subnet_group.status }}
+
+ {% for subnet in database.db_subnet_group.subnets %}
+
+ Active
+ {{ subnet.id }}
+
+ {{ subnet.availability_zone }}
+ false
+
+
+ {% endfor %}
+
+ {{ database.db_subnet_group.vpc_id }}
+
{{ database.publicly_accessible }}
{{ database.auto_minor_version_upgrade }}
{{ database.allocated_storage }}
@@ -96,6 +151,7 @@ class SecurityGroup(object):
self.group_name = group_name
self.description = description
self.ip_ranges = []
+ self.status = "authorized"
def to_xml(self):
template = Template("""
@@ -123,13 +179,14 @@ class SubnetGroup(object):
self.subnet_name = subnet_name
self.description = description
self.subnets = subnets
+ self.status = "Complete"
self.vpc_id = self.subnets[0].vpc_id
def to_xml(self):
template = Template("""
{{ subnet_group.vpc_id }}
- Complete
+ {{ subnet_group.status }}
{{ subnet_group.description }}
{{ subnet_group.subnet_name }}
@@ -161,6 +218,17 @@ class RDSBackend(BaseBackend):
self.databases[database_id] = database
return database
+ def create_database_replica(self, db_kwargs):
+ database_id = db_kwargs['db_instance_identifier']
+ source_database_id = db_kwargs['source_db_identifier']
+ primary = self.describe_databases(source_database_id)[0]
+ replica = copy.deepcopy(primary)
+ replica.update(db_kwargs)
+ replica.set_as_replica()
+ self.databases[database_id] = replica
+ primary.add_replica(replica)
+ return replica
+
def describe_databases(self, db_instance_identifier=None):
if db_instance_identifier:
if db_instance_identifier in self.databases:
@@ -176,7 +244,11 @@ class RDSBackend(BaseBackend):
def delete_database(self, db_instance_identifier):
if db_instance_identifier in self.databases:
- return self.databases.pop(db_instance_identifier)
+ database = self.databases.pop(db_instance_identifier)
+ if database.is_replica:
+ primary = self.describe_databases(database.source_db_identifier)[0]
+ primary.remove_replica(database)
+ return database
else:
raise DBInstanceNotFoundError(db_instance_identifier)
diff --git a/moto/rds/responses.py b/moto/rds/responses.py
index b115b60a5..98015e7bb 100644
--- a/moto/rds/responses.py
+++ b/moto/rds/responses.py
@@ -13,34 +13,45 @@ class RDSResponse(BaseResponse):
def _get_db_kwargs(self):
return {
- "engine": self._get_param("Engine"),
- "engine_version": self._get_param("EngineVersion"),
- "region": self.region,
- "iops": self._get_int_param("Iops"),
- "storage_type": self._get_param("StorageType"),
-
- "master_username": self._get_param('MasterUsername'),
- "master_password": self._get_param('MasterUserPassword'),
"auto_minor_version_upgrade": self._get_param('AutoMinorVersionUpgrade'),
"allocated_storage": self._get_int_param('AllocatedStorage'),
+ "availability_zone": self._get_param("AvailabilityZone"),
+ "backup_retention_period": self._get_param("BackupRetentionPeriod"),
"db_instance_class": self._get_param('DBInstanceClass'),
- "port": self._get_param('Port'),
"db_instance_identifier": self._get_param('DBInstanceIdentifier'),
"db_name": self._get_param("DBName"),
- "publicly_accessible": self._get_param("PubliclyAccessible"),
-
+ # DBParameterGroupName
+ "db_subnet_group_name": self._get_param("DBSubnetGroupName"),
+ "engine": self._get_param("Engine"),
+ "engine_version": self._get_param("EngineVersion"),
+ "iops": self._get_int_param("Iops"),
+ "master_password": self._get_param('MasterUserPassword'),
+ "master_username": self._get_param('MasterUsername'),
+ "multi_az": self._get_bool_param("MultiAZ"),
+ # OptionGroupName
+ "port": self._get_param('Port'),
# PreferredBackupWindow
# PreferredMaintenanceWindow
- "backup_retention_period": self._get_param("BackupRetentionPeriod"),
-
- # OptionGroupName
- # DBParameterGroupName
+ "publicly_accessible": self._get_param("PubliclyAccessible"),
+ "region": self.region,
"security_groups": self._get_multi_param('DBSecurityGroups.member'),
+ "storage_type": self._get_param("StorageType"),
# VpcSecurityGroupIds.member.N
+ }
+ def _get_db_replica_kwargs(self):
+ return {
+ "auto_minor_version_upgrade": self._get_param('AutoMinorVersionUpgrade'),
"availability_zone": self._get_param("AvailabilityZone"),
- "multi_az": self._get_bool_param("MultiAZ"),
+ "db_instance_class": self._get_param('DBInstanceClass'),
+ "db_instance_identifier": self._get_param('DBInstanceIdentifier'),
"db_subnet_group_name": self._get_param("DBSubnetGroupName"),
+ "iops": self._get_int_param("Iops"),
+ # OptionGroupName
+ "port": self._get_param('Port'),
+ "publicly_accessible": self._get_param("PubliclyAccessible"),
+ "source_db_identifier": self._get_param('SourceDBInstanceIdentifier'),
+ "storage_type": self._get_param("StorageType"),
}
def create_dbinstance(self):
@@ -50,6 +61,13 @@ class RDSResponse(BaseResponse):
template = self.response_template(CREATE_DATABASE_TEMPLATE)
return template.render(database=database)
+ def create_dbinstance_read_replica(self):
+ db_kwargs = self._get_db_replica_kwargs()
+
+ database = self.backend.create_database_replica(db_kwargs)
+ template = self.response_template(CREATE_DATABASE_REPLICA_TEMPLATE)
+ return template.render(database=database)
+
def describe_dbinstances(self):
db_instance_identifier = self._get_param('DBInstanceIdentifier')
databases = self.backend.describe_databases(db_instance_identifier)
@@ -126,6 +144,15 @@ CREATE_DATABASE_TEMPLATE = """
+
+ {{ database.to_xml() }}
+
+
+ ba8dedf0-bb9a-11d3-855b-576787000e19
+
+"""
+
DESCRIBE_DATABASES_TEMPLATE = """
diff --git a/tests/test_rds/test_rds.py b/tests/test_rds/test_rds.py
index a4c758953..02c644103 100644
--- a/tests/test_rds/test_rds.py
+++ b/tests/test_rds/test_rds.py
@@ -190,4 +190,42 @@ def test_delete_database_subnet_group():
conn.delete_db_subnet_group.when.called_with("db_subnet1").should.throw(BotoServerError)
-# TODO incorporate subnet groups with actual DBs
+
+@mock_ec2
+@mock_rds
+def test_create_database_in_subnet_group():
+ vpc_conn = boto.vpc.connect_to_region("us-west-2")
+ vpc = vpc_conn.create_vpc("10.0.0.0/16")
+ subnet = vpc_conn.create_subnet(vpc.id, "10.1.0.0/24")
+
+ conn = boto.rds.connect_to_region("us-west-2")
+ conn.create_db_subnet_group("db_subnet1", "my db subnet", [subnet.id])
+
+ database = conn.create_dbinstance("db-master-1", 10, 'db.m1.small',
+ 'root', 'hunter2', db_subnet_group_name="db_subnet1")
+
+ database = conn.get_all_dbinstances("db-master-1")[0]
+ database.subnet_group.name.should.equal("db_subnet1")
+
+
+@mock_rds
+def test_create_database_replica():
+ conn = boto.rds.connect_to_region("us-west-2")
+
+ primary = conn.create_dbinstance("db-master-1", 10, 'db.m1.small', 'root', 'hunter2')
+
+ replica = conn.create_dbinstance_read_replica("replica", "db-master-1", "db.m1.small")
+ replica.id.should.equal("replica")
+ replica.instance_class.should.equal("db.m1.small")
+ status_info = replica.status_infos[0]
+ status_info.normal.should.equal(True)
+ status_info.status_type.should.equal('read replication')
+ status_info.status.should.equal('replicating')
+
+ primary = conn.get_all_dbinstances("db-master-1")[0]
+ primary.read_replica_dbinstance_identifiers[0].should.equal("replica")
+
+ conn.delete_dbinstance("replica")
+
+ primary = conn.get_all_dbinstances("db-master-1")[0]
+ list(primary.read_replica_dbinstance_identifiers).should.have.length_of(0)