Add database CRUD.
This commit is contained in:
parent
e05a061993
commit
dbe3eb5459
@ -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
|
||||
|
@ -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
12
moto/rds/__init__.py
Normal 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
24
moto/rds/exceptions.py
Normal 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
114
moto/rds/models.py
Normal 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
91
moto/rds/responses.py
Normal 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
10
moto/rds/urls.py
Normal 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,
|
||||
}
|
62
tests/test_rds/test_rds.py
Normal file
62
tests/test_rds/test_rds.py
Normal 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)
|
20
tests/test_rds/test_server.py
Normal file
20
tests/test_rds/test_server.py
Normal 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>")
|
Loading…
Reference in New Issue
Block a user