diff --git a/moto/rds/exceptions.py b/moto/rds/exceptions.py
index 518ff401d..936b979d2 100644
--- a/moto/rds/exceptions.py
+++ b/moto/rds/exceptions.py
@@ -29,3 +29,10 @@ class DBSecurityGroupNotFoundError(RDSClientError):
super(DBSecurityGroupNotFoundError, self).__init__(
'DBSecurityGroupNotFound',
"Security Group {0} not found.".format(security_group_name))
+
+
+class DBSubnetGroupNotFoundError(RDSClientError):
+ def __init__(self, subnet_group_name):
+ super(DBSubnetGroupNotFoundError, self).__init__(
+ 'DBSubnetGroupNotFound',
+ "Subnet Group {0} not found.".format(subnet_group_name))
diff --git a/moto/rds/models.py b/moto/rds/models.py
index 06ada38c4..b7cb8ef5a 100644
--- a/moto/rds/models.py
+++ b/moto/rds/models.py
@@ -4,7 +4,7 @@ import boto.rds
from jinja2 import Template
from moto.core import BaseBackend
-from .exceptions import DBInstanceNotFoundError, DBSecurityGroupNotFoundError
+from .exceptions import DBInstanceNotFoundError, DBSecurityGroupNotFoundError, DBSubnetGroupNotFoundError
class Database(object):
@@ -118,11 +118,42 @@ class SecurityGroup(object):
self.ip_ranges.append(cidr_ip)
+class SubnetGroup(object):
+ def __init__(self, subnet_name, description, subnets):
+ self.subnet_name = subnet_name
+ self.description = description
+ self.subnets = subnets
+
+ self.vpc_id = self.subnets[0].vpc_id
+
+ def to_xml(self):
+ template = Template("""
+ {{ subnet_group.vpc_id }}
+ Complete
+ {{ subnet_group.description }}
+ {{ subnet_group.subnet_name }}
+
+ {% for subnet in subnet_group.subnets %}
+
+ Active
+ {{ subnet.id }}
+
+ {{ subnet.availability_zone }}
+ false
+
+
+ {% endfor %}
+
+ """)
+ return template.render(subnet_group=self)
+
+
class RDSBackend(BaseBackend):
def __init__(self):
self.databases = {}
self.security_groups = {}
+ self.subnet_groups = {}
def create_database(self, db_kwargs):
database_id = db_kwargs['db_instance_identifier']
@@ -173,6 +204,26 @@ class RDSBackend(BaseBackend):
security_group.authorize(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()
diff --git a/moto/rds/responses.py b/moto/rds/responses.py
index 4631ff6b0..b115b60a5 100644
--- a/moto/rds/responses.py
+++ b/moto/rds/responses.py
@@ -1,6 +1,7 @@
from __future__ import unicode_literals
from moto.core.responses import BaseResponse
+from moto.ec2.models import ec2_backends
from .models import rds_backends
@@ -94,6 +95,27 @@ class RDSResponse(BaseResponse):
template = self.response_template(AUTHORIZE_SECURITY_GROUP_TEMPLATE)
return template.render(security_group=security_group)
+ def create_dbsubnet_group(self):
+ subnet_name = self._get_param('DBSubnetGroupName')
+ description = self._get_param('DBSubnetGroupDescription')
+ subnet_ids = self._get_multi_param('SubnetIds.member')
+ 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)
+ template = self.response_template(CREATE_SUBNET_GROUP_TEMPLATE)
+ return template.render(subnet_group=subnet_group)
+
+ def describe_dbsubnet_groups(self):
+ subnet_name = self._get_param('DBSubnetGroupName')
+ subnet_groups = self.backend.describe_subnet_groups(subnet_name)
+ template = self.response_template(DESCRIBE_SUBNET_GROUPS_TEMPLATE)
+ return template.render(subnet_groups=subnet_groups)
+
+ def delete_dbsubnet_group(self):
+ subnet_name = self._get_param('DBSubnetGroupName')
+ subnet_group = self.backend.delete_subnet_group(subnet_name)
+ template = self.response_template(DELETE_SUBNET_GROUP_TEMPLATE)
+ return template.render(subnet_group=subnet_group)
+
CREATE_DATABASE_TEMPLATE = """
@@ -171,3 +193,31 @@ AUTHORIZE_SECURITY_GROUP_TEMPLATE = """6176b5f8-bfed-11d3-f92b-31fa5e8dbc99
"""
+
+CREATE_SUBNET_GROUP_TEMPLATE = """
+
+ {{ subnet_group.to_xml() }}
+
+
+ 3a401b3f-bb9e-11d3-f4c6-37db295f7674
+
+"""
+
+DESCRIBE_SUBNET_GROUPS_TEMPLATE = """
+
+
+ {% for subnet_group in subnet_groups %}
+ {{ subnet_group.to_xml() }}
+ {% endfor %}
+
+
+
+ b783db3b-b98c-11d3-fbc7-5c0aad74da7c
+
+"""
+
+DELETE_SUBNET_GROUP_TEMPLATE = """
+
+ 6295e5ab-bbf3-11d3-f4c6-37db295f7674
+
+"""
diff --git a/tests/test_rds/test_rds.py b/tests/test_rds/test_rds.py
index 1df5bf9ad..a4c758953 100644
--- a/tests/test_rds/test_rds.py
+++ b/tests/test_rds/test_rds.py
@@ -1,10 +1,11 @@
from __future__ import unicode_literals
import boto.rds
+import boto.vpc
from boto.exception import BotoServerError
import sure # noqa
-from moto import mock_rds
+from moto import mock_ec2, mock_rds
@mock_rds
@@ -138,3 +139,55 @@ def test_add_security_group_to_database():
list(database.security_groups).should.have.length_of(1)
database.security_groups[0].name.should.equal("db_sg")
+
+
+@mock_ec2
+@mock_rds
+def test_add_database_subnet_group():
+ vpc_conn = boto.vpc.connect_to_region("us-west-2")
+ vpc = vpc_conn.create_vpc("10.0.0.0/16")
+ subnet1 = vpc_conn.create_subnet(vpc.id, "10.1.0.0/24")
+ subnet2 = vpc_conn.create_subnet(vpc.id, "10.2.0.0/24")
+
+ subnet_ids = [subnet1.id, subnet2.id]
+ conn = boto.rds.connect_to_region("us-west-2")
+ subnet_group = conn.create_db_subnet_group("db_subnet", "my db subnet", subnet_ids)
+ subnet_group.name.should.equal('db_subnet')
+ subnet_group.description.should.equal("my db subnet")
+ list(subnet_group.subnet_ids).should.equal(subnet_ids)
+
+
+@mock_ec2
+@mock_rds
+def test_describe_database_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])
+ conn.create_db_subnet_group("db_subnet2", "my db subnet", [subnet.id])
+
+ list(conn.get_all_db_subnet_groups()).should.have.length_of(2)
+ list(conn.get_all_db_subnet_groups("db_subnet1")).should.have.length_of(1)
+
+ conn.get_all_db_subnet_groups.when.called_with("not-a-subnet").should.throw(BotoServerError)
+
+
+@mock_ec2
+@mock_rds
+def test_delete_database_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])
+ list(conn.get_all_db_subnet_groups()).should.have.length_of(1)
+
+ conn.delete_db_subnet_group("db_subnet1")
+ list(conn.get_all_db_subnet_groups()).should.have.length_of(0)
+
+ conn.delete_db_subnet_group.when.called_with("db_subnet1").should.throw(BotoServerError)
+
+# TODO incorporate subnet groups with actual DBs