Add database CRUD.

This commit is contained in:
Steve Pulec 2015-01-08 22:18:06 -05:00
parent e05a061993
commit dbe3eb5459
9 changed files with 336 additions and 0 deletions

View File

@ -12,6 +12,7 @@ from .elb import mock_elb # flake8: noqa
from .emr import mock_emr # flake8: noqa
from .iam import mock_iam # flake8: noqa
from .kinesis import mock_kinesis # flake8: noqa
from .rds import mock_rds # flake8: noqa
from .redshift import mock_redshift # flake8: noqa
from .s3 import mock_s3 # flake8: noqa
from .s3bucket_path import mock_s3bucket_path # flake8: noqa

View File

@ -7,6 +7,7 @@ from moto.ec2 import ec2_backend
from moto.elb import elb_backend
from moto.emr import emr_backend
from moto.kinesis import kinesis_backend
from moto.rds import rds_backend
from moto.redshift import redshift_backend
from moto.s3 import s3_backend
from moto.s3bucket_path import s3bucket_path_backend
@ -25,6 +26,7 @@ BACKENDS = {
'emr': emr_backend,
'kinesis': kinesis_backend,
'redshift': redshift_backend,
'rds': rds_backend,
's3': s3_backend,
's3bucket_path': s3bucket_path_backend,
'ses': ses_backend,

12
moto/rds/__init__.py Normal file
View File

@ -0,0 +1,12 @@
from __future__ import unicode_literals
from .models import rds_backends
from ..core.models import MockAWS
rds_backend = rds_backends['us-east-1']
def mock_rds(func=None):
if func:
return MockAWS(rds_backends)(func)
else:
return MockAWS(rds_backends)

24
moto/rds/exceptions.py Normal file
View File

@ -0,0 +1,24 @@
from __future__ import unicode_literals
import json
from werkzeug.exceptions import BadRequest
class RDSClientError(BadRequest):
def __init__(self, code, message):
super(RDSClientError, self).__init__()
self.description = json.dumps({
"Error": {
"Code": code,
"Message": message,
'Type': 'Sender',
},
'RequestId': '6876f774-7273-11e4-85dc-39e55ca848d1',
})
class DBInstanceNotFoundError(RDSClientError):
def __init__(self, database_identifier):
super(DBInstanceNotFoundError, self).__init__(
'DBInstanceNotFound',
"Database {0} not found.".format(database_identifier))

114
moto/rds/models.py Normal file
View File

@ -0,0 +1,114 @@
from __future__ import unicode_literals
import boto.rds
from jinja2 import Template
from moto.core import BaseBackend
from .exceptions import DBInstanceNotFoundError
class Database(object):
def __init__(self, **kwargs):
self.status = "available"
self.region = kwargs.get('region')
self.engine = kwargs.get("engine")
self.engine_version = kwargs.get("engine_version")
self.iops = kwargs.get("iops")
self.storage_type = kwargs.get("storage_type")
self.master_username = kwargs.get('master_username')
self.master_password = kwargs.get('master_password')
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.db_instance_class = kwargs.get('db_instance_class')
self.port = kwargs.get('port')
self.db_instance_identifier = kwargs.get('db_instance_identifier')
self.db_name = kwargs.get("db_name")
self.publicly_accessible = kwargs.get("publicly_accessible")
self.backup_retention_period = kwargs.get("backup_retention_period")
if self.backup_retention_period is None:
self.backup_retention_period = 1
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")
# PreferredBackupWindow
# PreferredMaintenanceWindow
# backup_retention_period = self._get_param("BackupRetentionPeriod")
# OptionGroupName
# DBParameterGroupName
# DBSecurityGroups.member.N
# VpcSecurityGroupIds.member.N
@property
def address(self):
return "{}.aaaaaaaaaa.{}.rds.amazonaws.com".format(self.db_instance_identifier, self.region)
def to_xml(self):
template = Template("""<DBInstance>
<BackupRetentionPeriod>{{ database.backup_retention_period }}</BackupRetentionPeriod>
<DBInstanceStatus>{{ database.status }}</DBInstanceStatus>
<MultiAZ>{{ database.multi_az }}</MultiAZ>
<VpcSecurityGroups/>
<DBInstanceIdentifier>{{ database.db_instance_identifier }}</DBInstanceIdentifier>
<PreferredBackupWindow>03:50-04:20</PreferredBackupWindow>
<PreferredMaintenanceWindow>wed:06:38-wed:07:08</PreferredMaintenanceWindow>
<ReadReplicaDBInstanceIdentifiers/>
<Engine>{{ database.engine }}</Engine>
<LicenseModel>general-public-license</LicenseModel>
<EngineVersion>{{ database.engine_version }}</EngineVersion>
<DBParameterGroups>
</DBParameterGroups>
<OptionGroupMemberships>
</OptionGroupMemberships>
<DBSecurityGroups>
<DBSecurityGroup>
<Status>active</Status>
<DBSecurityGroupName>default</DBSecurityGroupName>
</DBSecurityGroup>
</DBSecurityGroups>
<PubliclyAccessible>{{ database.publicly_accessible }}</PubliclyAccessible>
<AutoMinorVersionUpgrade>{{ database.auto_minor_version_upgrade }}</AutoMinorVersionUpgrade>
<AllocatedStorage>{{ database.allocated_storage }}</AllocatedStorage>
<DBInstanceClass>{{ database.db_instance_class }}</DBInstanceClass>
<MasterUsername>{{ database.master_username }}</MasterUsername>
<Endpoint>
<Address>{{ database.address }}</Address>
<Port>{{ database.port }}</Port>
</Endpoint>
</DBInstance>""")
return template.render(database=self)
class RDSBackend(BaseBackend):
def __init__(self):
self.databases = {}
def create_database(self, db_kwargs):
database_id = db_kwargs['db_instance_identifier']
database = Database(**db_kwargs)
self.databases[database_id] = database
return database
def describe_databases(self, db_instance_identifier=None):
if db_instance_identifier:
if db_instance_identifier in self.databases:
return [self.databases[db_instance_identifier]]
else:
raise DBInstanceNotFoundError(db_instance_identifier)
return self.databases.values()
def delete_database(self, db_instance_identifier):
if db_instance_identifier in self.databases:
return self.databases.pop(db_instance_identifier)
else:
raise DBInstanceNotFoundError(db_instance_identifier)
rds_backends = {}
for region in boto.rds.regions():
rds_backends[region.name] = RDSBackend()

91
moto/rds/responses.py Normal file
View File

@ -0,0 +1,91 @@
from __future__ import unicode_literals
from moto.core.responses import BaseResponse
from .models import rds_backends
class RDSResponse(BaseResponse):
@property
def backend(self):
return rds_backends[self.region]
def create_dbinstance(self):
db_kwargs = {
"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'),
"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"),
# PreferredBackupWindow
# PreferredMaintenanceWindow
"backup_retention_period": self._get_param("BackupRetentionPeriod"),
# OptionGroupName
# DBParameterGroupName
# DBSecurityGroups.member.N
# VpcSecurityGroupIds.member.N
"availability_zone": self._get_param("AvailabilityZone"),
"multi_az": self._get_bool_param("MultiAZ"),
"db_subnet_group_name": self._get_param("DBSubnetGroupName"),
}
database = self.backend.create_database(db_kwargs)
template = self.response_template(CREATE_DATABASE_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)
template = self.response_template(DESCRIBE_DATABASES_TEMPLATE)
return template.render(databases=databases)
def delete_dbinstance(self):
db_instance_identifier = self._get_param('DBInstanceIdentifier')
database = self.backend.delete_database(db_instance_identifier)
template = self.response_template(DELETE_DATABASE_TEMPLATE)
return template.render(database=database)
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>"""
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>01b2685a-b978-11d3-f272-7cd6cce12cc5</RequestId>
</ResponseMetadata>
</DescribeDBInstancesResponse>"""
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>"""

10
moto/rds/urls.py Normal file
View File

@ -0,0 +1,10 @@
from __future__ import unicode_literals
from .responses import RDSResponse
url_bases = [
"https?://rds.(.+).amazonaws.com",
]
url_paths = {
'{0}/$': RDSResponse().dispatch,
}

View File

@ -0,0 +1,62 @@
from __future__ import unicode_literals
import boto.rds
from boto.exception import BotoServerError
import sure # noqa
from moto import mock_rds
@mock_rds
def test_create_database():
conn = boto.rds.connect_to_region("us-west-2")
database = conn.create_dbinstance("db-master-1", 10, 'db.m1.small', 'root', 'hunter2')
database.status.should.equal('available')
database.id.should.equal("db-master-1")
database.allocated_storage.should.equal(10)
database.instance_class.should.equal("db.m1.small")
database.master_username.should.equal("root")
database.endpoint.should.equal(('db-master-1.aaaaaaaaaa.us-west-2.rds.amazonaws.com', 3306))
@mock_rds
def test_get_databases():
conn = boto.rds.connect_to_region("us-west-2")
list(conn.get_all_dbinstances()).should.have.length_of(0)
conn.create_dbinstance("db-master-1", 10, 'db.m1.small', 'root', 'hunter2')
conn.create_dbinstance("db-master-2", 10, 'db.m1.small', 'root', 'hunter2')
list(conn.get_all_dbinstances()).should.have.length_of(2)
databases = conn.get_all_dbinstances("db-master-1")
list(databases).should.have.length_of(1)
databases[0].id.should.equal("db-master-1")
@mock_rds
def test_describe_non_existant_database():
conn = boto.rds.connect_to_region("us-west-2")
conn.get_all_dbinstances.when.called_with("not-a-db").should.throw(BotoServerError)
@mock_rds
def test_delete_database():
conn = boto.rds.connect_to_region("us-west-2")
list(conn.get_all_dbinstances()).should.have.length_of(0)
conn.create_dbinstance("db-master-1", 10, 'db.m1.small', 'root', 'hunter2')
list(conn.get_all_dbinstances()).should.have.length_of(1)
conn.delete_dbinstance("db-master-1")
list(conn.get_all_dbinstances()).should.have.length_of(0)
@mock_rds
def test_delete_non_existant_database():
conn = boto.rds.connect_to_region("us-west-2")
conn.delete_dbinstance.when.called_with("not-a-db").should.throw(BotoServerError)

View File

@ -0,0 +1,20 @@
from __future__ import unicode_literals
import sure # noqa
import moto.server as server
from moto import mock_rds
'''
Test the different server responses
'''
@mock_rds
def test_list_databases():
backend = server.create_backend_app("rds")
test_client = backend.test_client()
res = test_client.get('/?Action=DescribeDBInstances')
res.data.decode("utf-8").should.contain("<DescribeDBInstancesResult>")