From c84e8c86f0c471eab7ce5107508ec835a86ab1e2 Mon Sep 17 00:00:00 2001 From: rocky4570fft Date: Tue, 15 Aug 2017 00:55:09 +1000 Subject: [PATCH] modify to use create_snapshot, add extra tests for certain error conditions --- moto/rds2/exceptions.py | 18 +++++++++- moto/rds2/models.py | 24 ++++++------- tests/test_rds2/test_rds2.py | 70 +++++++++++++++++++++++++++++++++--- 3 files changed, 95 insertions(+), 17 deletions(-) diff --git a/moto/rds2/exceptions.py b/moto/rds2/exceptions.py index 9c0c0144a..0e716310e 100644 --- a/moto/rds2/exceptions.py +++ b/moto/rds2/exceptions.py @@ -74,4 +74,20 @@ class InvalidDBInstanceStateError(RDSClientError): estate = "in available state" if istate == 'stop' else "stopped, it cannot be started" super(InvalidDBInstanceStateError, self).__init__( 'InvalidDBInstanceState', - 'when calling the {}DBInstance operation: Instance {} is not {}.'.format(istate.title(), database_identifier, estate)) + 'Instance {} is not {}.'.format(database_identifier, estate)) + + +class SnapshotQuotaExceededError(RDSClientError): + + def __init__(self): + super(SnapshotQuotaExceededError, self).__init__( + 'SnapshotQuotaExceeded', + 'The request cannot be processed because it would exceed the maximum number of snapshots.') + + +class DBSnapshotAlreadyExistsError(RDSClientError): + + def __init__(self, database_snapshot_identifier): + super(DBSnapshotAlreadyExistsError, self).__init__( + 'DBSnapshotAlreadyExists', + 'Cannot create the snapshot because a snapshot with the identifier {} already exists.'.format(database_snapshot_identifier)) diff --git a/moto/rds2/models.py b/moto/rds2/models.py index 9fbb79ed9..d52cb916d 100644 --- a/moto/rds2/models.py +++ b/moto/rds2/models.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import copy import datetime +import os from collections import defaultdict import boto.rds2 @@ -20,7 +21,9 @@ from .exceptions import (RDSClientError, DBSubnetGroupNotFoundError, DBParameterGroupNotFoundError, InvalidDBClusterStateFaultError, - InvalidDBInstanceStateError) + InvalidDBInstanceStateError, + SnapshotQuotaExceededError, + DBSnapshotAlreadyExistsError) class Database(BaseModel): @@ -410,6 +413,7 @@ class Snapshot(BaseModel): self.tags = tags or [] 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) @@ -676,10 +680,14 @@ class RDS2Backend(BaseBackend): self.databases[database_id] = database return database - def create_snapshot(self, db_instance_identifier, db_snapshot_identifier, tags): + def create_snapshot(self, db_instance_identifier, db_snapshot_identifier, tags=None): database = self.databases.get(db_instance_identifier) if not database: raise DBInstanceNotFoundError(db_instance_identifier) + if db_snapshot_identifier in self.snapshots: + raise DBSnapshotAlreadyExistsError(db_snapshot_identifier) + if len(self.snapshots) >= int(os.environ.get('MOTO_RDS_SNAPSHOT_LIMIT', '100')): + raise SnapshotQuotaExceededError() snapshot = Snapshot(database, db_snapshot_identifier, tags) self.snapshots[db_snapshot_identifier] = snapshot return snapshot @@ -743,9 +751,8 @@ class RDS2Backend(BaseBackend): raise InvalidDBClusterStateFaultError(db_instance_identifier) if database.status != 'available': raise InvalidDBInstanceStateError(db_instance_identifier, 'stop') - # todo: create rds snapshots - # if db_snapshot_identifier: - # self.create_rds_snapshot(db_instance_identifier, db_snapshot_identifier) + if db_snapshot_identifier: + self.create_snapshot(db_instance_identifier, db_snapshot_identifier) database.status = 'shutdown' return database @@ -757,13 +764,6 @@ class RDS2Backend(BaseBackend): database.status = 'available' return database - # def create_rds_snapshot(self, db_instance_identifier, db_snapshot_identifier): - # database = self.describe_databases(db_instance_identifier)[0] - # # todo - # # DBSnapshotAlreadyExists - # # SnapshotQuotaExceeded - # return None - def find_db_from_id(self, db_id): if self.arn_regex.match(db_id): arn_breakdown = db_id.split(':') diff --git a/tests/test_rds2/test_rds2.py b/tests/test_rds2/test_rds2.py index 40e35c9c1..97914e7e0 100644 --- a/tests/test_rds2/test_rds2.py +++ b/tests/test_rds2/test_rds2.py @@ -31,6 +31,7 @@ def test_create_database(): database['DBInstance']['DBName'].should.equal('staging-postgres') database['DBInstance']['DBInstanceIdentifier'].should.equal("db-master-1") + @mock_rds2 def test_stop_database(): conn = boto3.client('rds', region_name='us-west-2') @@ -46,12 +47,17 @@ def test_stop_database(): DBSecurityGroups=["my_sg"]) mydb = conn.describe_db_instances(DBInstanceIdentifier=database['DBInstance']['DBInstanceIdentifier'])['DBInstances'][0] mydb['DBInstanceStatus'].should.equal('available') - # test stopping database + # test stopping database should shutdown response = conn.stop_db_instance(DBInstanceIdentifier=mydb['DBInstanceIdentifier']) response['ResponseMetadata']['HTTPStatusCode'].should.equal(200) response['DBInstance']['DBInstanceStatus'].should.equal('shutdown') # test rdsclient error when trying to stop an already stopped database conn.stop_db_instance.when.called_with(DBInstanceIdentifier=mydb['DBInstanceIdentifier']).should.throw(ClientError) + # test stopping a stopped database with snapshot should error and no snapshot should exist for that call + conn.stop_db_instance.when.called_with(DBInstanceIdentifier=mydb['DBInstanceIdentifier'], DBSnapshotIdentifier='rocky4570-rds-snap').should.throw(ClientError) + response = conn.describe_db_snapshots() + response['DBSnapshots'].should.equal([]) + @mock_rds2 def test_start_database(): @@ -68,14 +74,27 @@ def test_start_database(): DBSecurityGroups=["my_sg"]) mydb = conn.describe_db_instances(DBInstanceIdentifier=database['DBInstance']['DBInstanceIdentifier'])['DBInstances'][0] mydb['DBInstanceStatus'].should.equal('available') - # test trying to start an already started database + # test starting an already started database should error conn.start_db_instance.when.called_with(DBInstanceIdentifier=mydb['DBInstanceIdentifier']).should.throw(ClientError) - # stop and test start - should go from shutdown to available - response = conn.stop_db_instance(DBInstanceIdentifier=mydb['DBInstanceIdentifier']) + # stop and test start - should go from shutdown to available, create snapshot and check snapshot + response = conn.stop_db_instance(DBInstanceIdentifier=mydb['DBInstanceIdentifier'], DBSnapshotIdentifier='rocky4570-rds-snap') + response['ResponseMetadata']['HTTPStatusCode'].should.equal(200) response['DBInstance']['DBInstanceStatus'].should.equal('shutdown') + response = conn.describe_db_snapshots() + response['DBSnapshots'][0]['DBSnapshotIdentifier'].should.equal('rocky4570-rds-snap') response = conn.start_db_instance(DBInstanceIdentifier=mydb['DBInstanceIdentifier']) response['ResponseMetadata']['HTTPStatusCode'].should.equal(200) response['DBInstance']['DBInstanceStatus'].should.equal('available') + # starting database should not remove snapshot + response = conn.describe_db_snapshots() + response['DBSnapshots'][0]['DBSnapshotIdentifier'].should.equal('rocky4570-rds-snap') + # test stopping database, create snapshot with existing snapshot already created should throw error + conn.stop_db_instance.when.called_with(DBInstanceIdentifier=mydb['DBInstanceIdentifier'], DBSnapshotIdentifier='rocky4570-rds-snap').should.throw(ClientError) + # test stopping database not invoking snapshot should succeed. + response = conn.stop_db_instance(DBInstanceIdentifier=mydb['DBInstanceIdentifier']) + response['ResponseMetadata']['HTTPStatusCode'].should.equal(200) + response['DBInstance']['DBInstanceStatus'].should.equal('shutdown') + @mock_rds2 def test_fail_to_stop_multi_az(): @@ -99,6 +118,7 @@ def test_fail_to_stop_multi_az(): # multi-az databases arent allowed to be started up at this time. conn.start_db_instance.when.called_with(DBInstanceIdentifier=mydb['DBInstanceIdentifier']).should.throw(ClientError) + @mock_rds2 def test_fail_to_stop_readreplica(): conn = boto3.client('rds', region_name='us-west-2') @@ -124,6 +144,48 @@ def test_fail_to_stop_readreplica(): # read-replicas are not allowed to be started at this time. conn.start_db_instance.when.called_with(DBInstanceIdentifier=mydb['DBInstanceIdentifier']).should.throw(ClientError) + +@mock_rds2 +def test_snapshotquota_exceeded(): + import os + conn = boto3.client('rds', region_name='us-west-2') + database1 = conn.create_db_instance(DBInstanceIdentifier='db-master-1', + AllocatedStorage=10, + Engine='postgres', + DBName='staging-postgres', + DBInstanceClass='db.m1.small', + LicenseModel='license-included', + MasterUsername='root', + MasterUserPassword='hunter2', + Port=1234, + DBSecurityGroups=["my_sg"]) + database2 = conn.create_db_instance(DBInstanceIdentifier='db-master-2', + AllocatedStorage=10, + Engine='postgres', + DBName='staging-postgres', + DBInstanceClass='db.m1.small', + LicenseModel='license-included', + MasterUsername='root', + MasterUserPassword='hunter2', + Port=1234, + DBSecurityGroups=["my_sg"]) + database3 = conn.create_db_instance(DBInstanceIdentifier='db-master-3', + AllocatedStorage=10, + Engine='postgres', + DBName='staging-postgres', + DBInstanceClass='db.m1.small', + LicenseModel='license-included', + MasterUsername='root', + MasterUserPassword='hunter2', + Port=1234, + DBSecurityGroups=["my_sg"]) + conn.stop_db_instance(DBInstanceIdentifier=database1['DBInstance']['DBInstanceIdentifier'], DBSnapshotIdentifier='rocky4570-rds-snap1') + conn.stop_db_instance(DBInstanceIdentifier=database2['DBInstance']['DBInstanceIdentifier'], DBSnapshotIdentifier='rocky4570-rds-snap2') + os.environ['MOTO_RDS_SNAPSHOT_LIMIT'] = '2' + conn.stop_db_instance.when.called_with(DBInstanceIdentifier=database3['DBInstance']['DBInstanceIdentifier'], DBSnapshotIdentifier='rocky4570-rds-snap3').should.throw(ClientError) + os.unsetenv('MOTO_RDS_SNAPSHOT_LIMIT') + + @mock_rds2 def test_get_databases(): conn = boto3.client('rds', region_name='us-west-2')