Implementing RDS Snapshots
This commit is contained in:
parent
8956151e07
commit
63f01039c3
@ -28,6 +28,14 @@ class DBInstanceNotFoundError(RDSClientError):
|
||||
"Database {0} not found.".format(database_identifier))
|
||||
|
||||
|
||||
class DBSnapshotNotFoundError(RDSClientError):
|
||||
|
||||
def __init__(self):
|
||||
super(DBSnapshotNotFoundError, self).__init__(
|
||||
'DBSnapshotNotFound',
|
||||
"DBSnapshotIdentifier does not refer to an existing DB snapshot.")
|
||||
|
||||
|
||||
class DBSecurityGroupNotFoundError(RDSClientError):
|
||||
|
||||
def __init__(self, security_group_name):
|
||||
|
@ -1,6 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
|
||||
from collections import defaultdict
|
||||
import boto.rds2
|
||||
@ -10,9 +11,11 @@ from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
|
||||
from moto.compat import OrderedDict
|
||||
from moto.core import BaseBackend, BaseModel
|
||||
from moto.core.utils import get_random_hex
|
||||
from moto.core.utils import iso_8601_datetime_with_milliseconds
|
||||
from moto.ec2.models import ec2_backends
|
||||
from .exceptions import (RDSClientError,
|
||||
DBInstanceNotFoundError,
|
||||
DBSnapshotNotFoundError,
|
||||
DBSecurityGroupNotFoundError,
|
||||
DBSubnetGroupNotFoundError,
|
||||
DBParameterGroupNotFoundError)
|
||||
@ -205,7 +208,7 @@ class Database(BaseModel):
|
||||
{% endif %}
|
||||
{% if database.iops %}
|
||||
<Iops>{{ database.iops }}</Iops>
|
||||
<StorageType>io1</StorageType>
|
||||
<StorageType>standard</StorageType>
|
||||
{% else %}
|
||||
<StorageType>{{ database.storage_type }}</StorageType>
|
||||
{% endif %}
|
||||
@ -399,6 +402,53 @@ class Database(BaseModel):
|
||||
backend.delete_database(self.db_instance_identifier)
|
||||
|
||||
|
||||
class Snapshot(BaseModel):
|
||||
def __init__(self, database, snapshot_id, tags):
|
||||
self.database = database
|
||||
self.snapshot_id = snapshot_id
|
||||
self.tags = tags
|
||||
self.created_at = iso_8601_datetime_with_milliseconds(datetime.datetime.now())
|
||||
|
||||
@property
|
||||
def snapshot_arn(self):
|
||||
return "arn:aws:rds:{0}:1234567890:snapshot:{1}".format(self.database.region, self.snapshot_id)
|
||||
|
||||
def to_xml(self):
|
||||
template = Template("""<DBSnapshot>
|
||||
<DBSnapshotIdentifier>{{ snapshot.snapshot_id }}</DBSnapshotIdentifier>
|
||||
<DBInstanceIdentifier>{{ database.db_instance_identifier }}</DBInstanceIdentifier>
|
||||
<SnapshotCreateTime>{{ snapshot.created_at }}</SnapshotCreateTime>
|
||||
<Engine>{{ database.engine }}</Engine>
|
||||
<AllocatedStorage>{{ database.allocated_storage }}</AllocatedStorage>
|
||||
<Status>available</Status>
|
||||
<Port>{{ database.port }}</Port>
|
||||
<AvailabilityZone>{{ database.availability_zone }}</AvailabilityZone>
|
||||
<VpcId>{{ database.db_subnet_group.vpc_id }}</VpcId>
|
||||
<InstanceCreateTime>{{ snapshot.created_at }}</InstanceCreateTime>
|
||||
<MasterUsername>{{ database.master_username }}</MasterUsername>
|
||||
<EngineVersion>{{ database.engine_version }}</EngineVersion>
|
||||
<LicenseModel>general-public-license</LicenseModel>
|
||||
<SnapshotType>manual</SnapshotType>
|
||||
{% if database.iops %}
|
||||
<Iops>{{ database.iops }}</Iops>
|
||||
<StorageType>io1</StorageType>
|
||||
{% else %}
|
||||
<StorageType>{{ database.storage_type }}</StorageType>
|
||||
{% endif %}
|
||||
<OptionGroupName>{{ database.option_group_name }}</OptionGroupName>
|
||||
<PercentProgress>{{ 100 }}</PercentProgress>
|
||||
<SourceRegion>{{ database.region }}</SourceRegion>
|
||||
<SourceDBSnapshotIdentifier></SourceDBSnapshotIdentifier>
|
||||
<TdeCredentialArn></TdeCredentialArn>
|
||||
<Encrypted>{{ database.storage_encrypted }}</Encrypted>
|
||||
<KmsKeyId>{{ database.kms_key_id }}</KmsKeyId>
|
||||
<DBSnapshotArn>{{ snapshot.snapshot_arn }}</DBSnapshotArn>
|
||||
<Timezone></Timezone>
|
||||
<IAMDatabaseAuthenticationEnabled>false</IAMDatabaseAuthenticationEnabled>
|
||||
</DBSnapshot>""")
|
||||
return template.render(snapshot=self, database=self.database)
|
||||
|
||||
|
||||
class SecurityGroup(BaseModel):
|
||||
|
||||
def __init__(self, group_name, description, tags):
|
||||
@ -607,6 +657,7 @@ class RDS2Backend(BaseBackend):
|
||||
self.arn_regex = re_compile(
|
||||
r'^arn:aws:rds:.*:[0-9]*:(db|es|og|pg|ri|secgrp|snapshot|subgrp):.*$')
|
||||
self.databases = OrderedDict()
|
||||
self.snapshots = OrderedDict()
|
||||
self.db_parameter_groups = {}
|
||||
self.option_groups = {}
|
||||
self.security_groups = {}
|
||||
@ -624,6 +675,20 @@ class RDS2Backend(BaseBackend):
|
||||
self.databases[database_id] = database
|
||||
return database
|
||||
|
||||
def create_snapshot(self, db_instance_identifier, db_snapshot_identifier, tags):
|
||||
database = self.databases.get(db_instance_identifier)
|
||||
if not database:
|
||||
raise DBInstanceNotFoundError(db_instance_identifier)
|
||||
snapshot = Snapshot(database, db_snapshot_identifier, tags)
|
||||
self.snapshots[db_snapshot_identifier] = snapshot
|
||||
return snapshot
|
||||
|
||||
def delete_snapshot(self, db_snapshot_identifier):
|
||||
if db_snapshot_identifier not in self.snapshots:
|
||||
raise DBSnapshotNotFoundError()
|
||||
|
||||
return self.snapshots.pop(db_snapshot_identifier)
|
||||
|
||||
def create_database_replica(self, db_kwargs):
|
||||
database_id = db_kwargs['db_instance_identifier']
|
||||
source_database_id = db_kwargs['source_db_identifier']
|
||||
@ -646,6 +711,20 @@ class RDS2Backend(BaseBackend):
|
||||
raise DBInstanceNotFoundError(db_instance_identifier)
|
||||
return self.databases.values()
|
||||
|
||||
def describe_snapshots(self, db_instance_identifier, db_snapshot_identifier):
|
||||
if db_instance_identifier:
|
||||
for snapshot in self.snapshots.values():
|
||||
if snapshot.database.db_instance_identifier == db_instance_identifier:
|
||||
return [snapshot]
|
||||
raise DBSnapshotNotFoundError()
|
||||
|
||||
if db_snapshot_identifier:
|
||||
if db_snapshot_identifier in self.snapshots:
|
||||
return [self.snapshots[db_snapshot_identifier]]
|
||||
raise DBSnapshotNotFoundError()
|
||||
|
||||
return self.snapshots.values()
|
||||
|
||||
def modify_database(self, db_instance_identifier, db_kwargs):
|
||||
database = self.describe_databases(db_instance_identifier)[0]
|
||||
database.update(db_kwargs)
|
||||
|
@ -39,7 +39,7 @@ class RDS2Response(BaseResponse):
|
||||
"region": self.region,
|
||||
"security_groups": self._get_multi_param('DBSecurityGroups.DBSecurityGroupName'),
|
||||
"storage_encrypted": self._get_param("StorageEncrypted"),
|
||||
"storage_type": self._get_param("StorageType"),
|
||||
"storage_type": self._get_param("StorageType", 'standard'),
|
||||
# VpcSecurityGroupIds.member.N
|
||||
"tags": list(),
|
||||
}
|
||||
@ -150,6 +150,27 @@ class RDS2Response(BaseResponse):
|
||||
template = self.response_template(REBOOT_DATABASE_TEMPLATE)
|
||||
return template.render(database=database)
|
||||
|
||||
def create_db_snapshot(self):
|
||||
db_instance_identifier = self._get_param('DBInstanceIdentifier')
|
||||
db_snapshot_identifier = self._get_param('DBSnapshotIdentifier')
|
||||
tags = self._get_param('Tags', [])
|
||||
snapshot = self.backend.create_snapshot(db_instance_identifier, db_snapshot_identifier, tags)
|
||||
template = self.response_template(CREATE_SNAPSHOT_TEMPLATE)
|
||||
return template.render(snapshot=snapshot)
|
||||
|
||||
def describe_db_snapshots(self):
|
||||
db_instance_identifier = self._get_param('DBInstanceIdentifier')
|
||||
db_snapshot_identifier = self._get_param('DBSnapshotIdentifier')
|
||||
snapshots = self.backend.describe_snapshots(db_instance_identifier, db_snapshot_identifier)
|
||||
template = self.response_template(DESCRIBE_SNAPSHOTS_TEMPLATE)
|
||||
return template.render(snapshots=snapshots)
|
||||
|
||||
def delete_db_snapshot(self):
|
||||
db_snapshot_identifier = self._get_param('DBSnapshotIdentifier')
|
||||
snapshot = self.backend.delete_snapshot(db_snapshot_identifier)
|
||||
template = self.response_template(DELETE_SNAPSHOT_TEMPLATE)
|
||||
return template.render(snapshot=snapshot)
|
||||
|
||||
def list_tags_for_resource(self):
|
||||
arn = self._get_param('ResourceName')
|
||||
template = self.response_template(LIST_TAGS_FOR_RESOURCE_TEMPLATE)
|
||||
@ -397,6 +418,42 @@ DELETE_DATABASE_TEMPLATE = """<DeleteDBInstanceResponse xmlns="http://rds.amazon
|
||||
</ResponseMetadata>
|
||||
</DeleteDBInstanceResponse>"""
|
||||
|
||||
CREATE_SNAPSHOT_TEMPLATE = """<CreateDBSnapshotResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
|
||||
<CreateDBSnapshotResult>
|
||||
{{ snapshot.to_xml() }}
|
||||
</CreateDBSnapshotResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>523e3218-afc7-11c3-90f5-f90431260ab4</RequestId>
|
||||
</ResponseMetadata>
|
||||
</CreateDBSnapshotResponse>
|
||||
"""
|
||||
|
||||
DESCRIBE_SNAPSHOTS_TEMPLATE = """<DescribeDBSnapshotsResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
|
||||
<DescribeDBSnapshotsResult>
|
||||
<DBSnapshots>
|
||||
{%- for snapshot in snapshots -%}
|
||||
{{ snapshot.to_xml() }}
|
||||
{%- endfor -%}
|
||||
</DBSnapshots>
|
||||
{% if marker %}
|
||||
<Marker>{{ marker }}</Marker>
|
||||
{% endif %}
|
||||
</DescribeDBSnapshotsResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>523e3218-afc7-11c3-90f5-f90431260ab4</RequestId>
|
||||
</ResponseMetadata>
|
||||
</DescribeDBSnapshotsResponse>"""
|
||||
|
||||
DELETE_SNAPSHOT_TEMPLATE = """<DeleteDBSnapshotResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
|
||||
<DeleteDBSnapshotResult>
|
||||
{{ snapshot.to_xml() }}
|
||||
</DeleteDBSnapshotResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>523e3218-afc7-11c3-90f5-f90431260ab4</RequestId>
|
||||
</ResponseMetadata>
|
||||
</DeleteDBSnapshotResponse>
|
||||
"""
|
||||
|
||||
CREATE_SECURITY_GROUP_TEMPLATE = """<CreateDBSecurityGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
|
||||
<CreateDBSecurityGroupResult>
|
||||
{{ security_group.to_xml() }}
|
||||
|
@ -168,6 +168,81 @@ def test_delete_non_existant_database():
|
||||
DBInstanceIdentifier="not-a-db").should.throw(ClientError)
|
||||
|
||||
|
||||
@mock_rds2
|
||||
def test_create_db_snapshots():
|
||||
conn = boto3.client('rds', region_name='us-west-2')
|
||||
conn.create_db_snapshot.when.called_with(
|
||||
DBInstanceIdentifier='db-primary-1',
|
||||
DBSnapshotIdentifier='snapshot-1').should.throw(ClientError)
|
||||
|
||||
conn.create_db_instance(DBInstanceIdentifier='db-primary-1',
|
||||
AllocatedStorage=10,
|
||||
Engine='postgres',
|
||||
DBName='staging-postgres',
|
||||
DBInstanceClass='db.m1.small',
|
||||
MasterUsername='root',
|
||||
MasterUserPassword='hunter2',
|
||||
Port=1234,
|
||||
DBSecurityGroups=["my_sg"])
|
||||
|
||||
snapshot = conn.create_db_snapshot(DBInstanceIdentifier='db-primary-1',
|
||||
DBSnapshotIdentifier='g-1').get('DBSnapshot')
|
||||
|
||||
snapshot.get('Engine').should.equal('postgres')
|
||||
snapshot.get('DBInstanceIdentifier').should.equal('db-primary-1')
|
||||
snapshot.get('DBSnapshotIdentifier').should.equal('g-1')
|
||||
|
||||
|
||||
@mock_rds2
|
||||
def test_describe_db_snapshots():
|
||||
conn = boto3.client('rds', region_name='us-west-2')
|
||||
conn.create_db_instance(DBInstanceIdentifier='db-primary-1',
|
||||
AllocatedStorage=10,
|
||||
Engine='postgres',
|
||||
DBName='staging-postgres',
|
||||
DBInstanceClass='db.m1.small',
|
||||
MasterUsername='root',
|
||||
MasterUserPassword='hunter2',
|
||||
Port=1234,
|
||||
DBSecurityGroups=["my_sg"])
|
||||
conn.describe_db_snapshots.when.called_with(
|
||||
DBInstanceIdentifier="db-primary-1").should.throw(ClientError)
|
||||
|
||||
created = conn.create_db_snapshot(DBInstanceIdentifier='db-primary-1',
|
||||
DBSnapshotIdentifier='snapshot-1').get('DBSnapshot')
|
||||
|
||||
created.get('Engine').should.equal('postgres')
|
||||
|
||||
by_database_id = conn.describe_db_snapshots(DBInstanceIdentifier='db-primary-1').get('DBSnapshots')
|
||||
by_snapshot_id = conn.describe_db_snapshots(DBSnapshotIdentifier='snapshot-1').get('DBSnapshots')
|
||||
by_snapshot_id.should.equal(by_database_id)
|
||||
|
||||
snapshot = by_snapshot_id[0]
|
||||
snapshot.should.equal(created)
|
||||
snapshot.get('Engine').should.equal('postgres')
|
||||
|
||||
|
||||
@mock_rds2
|
||||
def test_delete_db_snapshot():
|
||||
conn = boto3.client('rds', region_name='us-west-2')
|
||||
conn.create_db_instance(DBInstanceIdentifier='db-primary-1',
|
||||
AllocatedStorage=10,
|
||||
Engine='postgres',
|
||||
DBName='staging-postgres',
|
||||
DBInstanceClass='db.m1.small',
|
||||
MasterUsername='root',
|
||||
MasterUserPassword='hunter2',
|
||||
Port=1234,
|
||||
DBSecurityGroups=["my_sg"])
|
||||
conn.create_db_snapshot(DBInstanceIdentifier='db-primary-1',
|
||||
DBSnapshotIdentifier='snapshot-1')
|
||||
|
||||
conn.describe_db_snapshots(DBSnapshotIdentifier='snapshot-1').get('DBSnapshots')[0]
|
||||
conn.delete_db_snapshot(DBSnapshotIdentifier='snapshot-1')
|
||||
conn.describe_db_snapshots.when.called_with(
|
||||
DBSnapshotIdentifier='snapshot-1').should.throw(ClientError)
|
||||
|
||||
|
||||
@mock_rds2
|
||||
def test_create_option_group():
|
||||
conn = boto3.client('rds', region_name='us-west-2')
|
||||
|
Loading…
Reference in New Issue
Block a user