def lambda_rename_dbinstance(event, context): """Handles rename of a DB instance""" region = os.environ['Region'] rds = boto3.client('rds', region) result = {} original_instance_identifier = '' modified_instance_identifier = '' try: if event.get('Error') == 'InstanceRestoreException' and \ 'Identifier' in event.get('Cause'): #rename revert scenario in case of db restore failure response = util.get_identifier_from_error(event) modified_instance_identifier = response["modified_identifier"] original_instance_identifier = response["original_identifier"] else: original_instance_identifier = event['identifier'] modified_instance_identifier = event['identifier'] + '-temp' rds.modify_db_instance( DBInstanceIdentifier = original_instance_identifier, ApplyImmediately = True, NewDBInstanceIdentifier = modified_instance_identifier) result['taskname'] = constants.RENAME result['identifier'] = modified_instance_identifier return result except Exception as error: error_message = util.get_error_message(original_instance_identifier, error) if constants.RATE_EXCEEDED in str(error): raise custom_exceptions.RateExceededException(error_message) else: raise custom_exceptions.RenameException(error_message)
def lambda_delete_dbcluster(event, context): """Handles deletion of a DB cluster and its corresponding readers and writers""" region = os.environ['Region'] rds = boto3.client('rds', region) result = {} cluster_id = event['identifier'] + constants.TEMP_POSTFIX try: describe_db_response = rds.describe_db_clusters( DBClusterIdentifier=cluster_id) for db_cluster_member in describe_db_response['DBClusters'][0][ 'DBClusterMembers']: rds.delete_db_instance( DBInstanceIdentifier=db_cluster_member['DBInstanceIdentifier'], SkipFinalSnapshot=True) rds.delete_db_cluster(DBClusterIdentifier=cluster_id, SkipFinalSnapshot=True) result['taskname'] = constants.DELETE result['identifier'] = cluster_id return result except Exception as error: error_message = util.get_error_message(cluster_id, error) if constants.RATE_EXCEEDED in str(error): raise custom_exceptions.RateExceededException(error_message) else: raise custom_exceptions.DeletionException(error_message)
def lambda_rename_dbcluster(event, context): """Handles rename of a DB cluster and its corresponding readers and writers""" region = os.environ['Region'] rds = boto3.client('rds', region) result = {} rename_response = {} try: #rename revert scenario in case of db cluster restore failure if event.get('Error') == 'ClusterRestoreException' and \ 'Identifier' in event.get('Cause'): rename_response = cluster_instance_rename_reversal(event, rds) else: rename_response = cluster_instance_rename(event, rds) #rename of the db cluster rds.modify_db_cluster( DBClusterIdentifier = rename_response["original_cluster_identifier"], ApplyImmediately = True, NewDBClusterIdentifier = rename_response["modified_cluster_identifier"] ) result['taskname'] = constants.RENAME result['identifier'] = rename_response["modified_cluster_identifier"] return result except Exception as error: if constants.RATE_EXCEEDED in str(error): raise custom_exceptions.RateExceededException(str(error)) else: raise custom_exceptions.RenameException(str(error))
def eval_exception(error, identifier, taskname): result = {} error_message = constants.IDENTIFIER + identifier + ' \n' + str(error) if taskname == constants.DELETE and \ (str(error) == constants.CLUSTER_UNAVAILABLE or constants.CLUSTER_NOT_FOUND in str(error) or constants.NOT_FOUND in str(error)): result['task'] = constants.TASK_COMPLETE result['identifier'] = identifier result['taskname'] = taskname return result elif str(error) == constants.INSTANCE_UNAVAILABLE or constants.INSTANCE_NOT_FOUND in str(error) \ or str(error) == constants.CLUSTER_UNAVAILABLE or constants.CLUSTER_NOT_FOUND in str(error) \ or constants.NOT_FOUND in str(error): raise custom_exceptions.InstanceUnavailableException(error_message) elif constants.RATE_EXCEEDED in str(error) or constants.WAITER_MAX in str(error): raise custom_exceptions.RateExceededException(error_message) elif constants.WAITER_FAILURE in str(error): result['task'] = constants.TASK_FAILED result['status'] = error_message result['identifier'] = identifier result['taskname'] = taskname return result elif taskname in constants.TASK_ERROR_MAP: raise constants.TASK_ERROR_MAP[taskname](error_message) else: raise Exception(error_message)
def lambda_restore_dbinstance(event, context): """Handles restore of a db instance from its snapshot""" region = os.environ['Region'] rds = boto3.client('rds', region) result = {} response = util.get_modified_identifier(event['identifier']) try: describe_db_response = rds.describe_db_instances( DBInstanceIdentifier=event['identifier']) vpc_security_groups = describe_db_response['DBInstances'][0][ 'VpcSecurityGroups'] vpc_security_group_ids = [] for vpc_security_group in vpc_security_groups: vpc_security_group_ids.append( vpc_security_group['VpcSecurityGroupId']) rds.restore_db_instance_from_db_snapshot( DBInstanceIdentifier=response["instance_id"], DBSnapshotIdentifier=response["snapshot_id"], DBSubnetGroupName=describe_db_response['DBInstances'][0] ['DBSubnetGroup']['DBSubnetGroupName'], VpcSecurityGroupIds=vpc_security_group_ids) result['taskname'] = constants.DB_RESTORE result['identifier'] = response["instance_id"] return result except Exception as error: error_message = util.get_error_message(response["instance_id"], error) if constants.RATE_EXCEEDED in str(error): raise custom_exceptions.RateExceededException(error_message) else: raise custom_exceptions.InstanceRestoreException(error_message)
def setUp(self): self.event = {"identifier": "database-1"} self.instance_id = self.event['identifier'] + constants.TEMP_POSTFIX self.mocked_rate_exceeded_exception = custom_exceptions.RateExceededException( "Identifier:database-1-temp \nthrottling error: Rate exceeded") self.mocked_instance_not_found_exception = custom_exceptions.SnapshotCreationException( "Identifier:database-1-temp \nInstanceNotFoundFault")
def setUp(self): self.mock_waiter = Mock() self.task_complete = "TASK_COMPLETE" self.task_failed = "TASK_FAILED" self.mock_rateexceeded_exception = custom_exceptions.RateExceededException( "Identifier:database-1 \nWaiter database-1 Max attempts exceeded") self.mock_instance_not_found_failure = custom_exceptions.InstanceUnavailableException( "Identifier:database-1 \nWaiter database-1 not found") self.mock_snapshot_creation_failure_status = "Identifier:database-1 \nWaiter encountered a terminal failure state"
def lambda_restore_dbcluster(event, context): """Handles restore of a db cluster from its snapshot""" region = os.environ['Region'] result = {} rds = boto3.client('rds', region) old_cluster_id = event['identifier'] response = util.get_modified_identifier(event['identifier']) cluster_id = response["instance_id"] cluster_snapshot_id = response["snapshot_id"] try: describe_db_response = rds.describe_db_clusters( DBClusterIdentifier=old_cluster_id) vpc_security_groups = describe_db_response['DBClusters'][0][ 'VpcSecurityGroups'] engine = describe_db_response['DBClusters'][0]['Engine'] engine_version = describe_db_response['DBClusters'][0]['EngineVersion'] vpc_security_groups_ids = [] for vpc_security_group in vpc_security_groups: vpc_security_groups_ids.append( vpc_security_group['VpcSecurityGroupId']) rds.restore_db_cluster_from_snapshot( DBClusterIdentifier=cluster_id, SnapshotIdentifier=cluster_snapshot_id, Engine=engine, EngineVersion=engine_version, DBSubnetGroupName=describe_db_response['DBClusters'][0] ['DBSubnetGroup'], Port=describe_db_response['DBClusters'][0]['Port'], DatabaseName=describe_db_response['DBClusters'][0]['DatabaseName'], VpcSecurityGroupIds=vpc_security_groups_ids) for db_cluster_member in describe_db_response['DBClusters'][0][ 'DBClusterMembers']: desc_db_response = rds.describe_db_instances( DBInstanceIdentifier=db_cluster_member['DBInstanceIdentifier']) dbinstance_identifier = util.get_modified_identifier( db_cluster_member['DBInstanceIdentifier'])["instance_id"] rds.create_db_instance( DBInstanceIdentifier=dbinstance_identifier, DBInstanceClass=desc_db_response['DBInstances'][0] ['DBInstanceClass'], Engine=engine, DBClusterIdentifier=cluster_id) result['taskname'] = constants.CLUSTER_RESTORE result['identifier'] = cluster_id return result except Exception as error: error_message = util.get_error_message(cluster_id, error) if constants.RATE_EXCEEDED in str(error): raise custom_exceptions.RateExceededException(error_message) else: raise custom_exceptions.ClusterRestoreException(error_message)
def setUp(self): self.event = {"identifier": "database-1-temp"} self.cluster_id = "database-1" self.mocked_rate_exceeded_exception = custom_exceptions.RateExceededException( "Identifier:database-1 \nthrottling error: Rate exceeded") self.mocked_cluster_not_found_exception = custom_exceptions.ClusterRestoreException( "Identifier:database-1 \nDBClusterNotFound") self.cluster_restore_exception = custom_exceptions.ClusterRestoreException( "Identifier:database-1 \nCluster restoration failed") self.dbinstance_creation_exception = custom_exceptions.ClusterRestoreException( "Identifier:database-1-instance-1 \nDBInstance creation failed") self.mocked_describe_db_clusters = { "ResponseMetadata": { 'HTTPStatusCode': 200 }, "DBClusters": [{ "DBClusterIdentifier": "database-1-temp", "DatabaseName": "POSTGRES", "Port": 3306, "Engine": "xyzz", "EngineVersion": 10.7, "VpcSecurityGroups": [{ "VpcSecurityGroupId": "abc" }], "DBSubnetGroup": { "DBSubnetGroupName": "xyz" }, "DBClusterMembers": [{ "DBInstanceIdentifier": "database-1-instance-1-temp", "IsClusterWriter": True, "PromotionTier": 123 }] }] } self.mocked_describe_db_instances = { 'ResponseMetadata': { 'HTTPStatusCode': 200 }, "DBInstances": [{ "DBInstanceIdentifier": "database-1-instance-1-temp", "DBInstanceClass": "postgres-ee" }] }
def lambda_delete_dbinstance(event, context): """Handles deletion of a RDS db instance""" region = os.environ['Region'] rds = boto3.client('rds', region) result = {} instance_id = event['identifier'] + constants.TEMP_POSTFIX try: rds.delete_db_instance(DBInstanceIdentifier=instance_id, SkipFinalSnapshot=True) result['taskname'] = constants.DELETE result['identifier'] = instance_id return result except Exception as error: error_message = util.get_error_message(instance_id, error) if constants.RATE_EXCEEDED in str(error): raise custom_exceptions.RateExceededException(error_message) else: raise custom_exceptions.DeletionException(error_message)
def eval_snapshot_exception(error, identifier, rds_client): error_message = constants.IDENTIFIER + identifier + ' \n' + str(error) snapshot_id = identifier + constants.SNAPSHOT_POSTFIX if constants.RATE_EXCEEDED in str(error): raise custom_exceptions.RateExceededException(error_message) elif constants.CLUSTER_SNAPSHOT_EXISTS in str(error): waiter = rds_client.get_waiter('db_cluster_snapshot_deleted') rds_client.delete_db_cluster_snapshot( DBClusterSnapshotIdentifier=snapshot_id) waiter.wait(DBClusterSnapshotIdentifier=snapshot_id) raise custom_exceptions.RetryClusterSnapshotException(error_message) elif constants.DB_SNAPSHOT_EXISTS in str(error): waiter = rds_client.get_waiter('db_snapshot_deleted') rds_client.delete_db_snapshot(DBSnapshotIdentifier=snapshot_id) waiter.wait(DBSnapshotIdentifier=snapshot_id) raise custom_exceptions.RetryDBSnapshotException(error_message) else: raise custom_exceptions.SnapshotCreationException(error_message)
def setUp(self): self.event = {'identifier': 'database-1'} self.cluster_id = self.event['identifier'] + constants.TEMP_POSTFIX self.mocked_rate_exceeded_exception = custom_exceptions.RateExceededException( "Identifier:database-1-temp \nthrottling error: Rate exceeded") self.mocked_cluster_not_found_exception = custom_exceptions.DeletionException( "Identifier:database-1-temp \nDBClusterNotFound") self.mocked_instance_not_found_exception = custom_exceptions.DeletionException( "Identifier:database-1-temp \nInstanceDeletionFailure") self.mocked_describe_db_clusters = { 'ResponseMetadata': { 'HTTPStatusCode': 200 }, 'DBClusters': [{ "DBClusterIdentifier": "database-1-temp", "DBClusterMembers": [{ "DBInstanceIdentifier": "database-1-instance-1-temp", "IsClusterWriter": True, "PromotionTier": 123 }] }] }
def setUp(self): self.mock_waiter = Mock() self.task_complete = "TASK_COMPLETE" self.task_failed = "TASK_FAILED" self.mock_rateexceeded_exception = custom_exceptions.RateExceededException( "Identifier:database-1 \nWaiter database-1 Max attempts exceeded") self.mock_instance_not_found_failure = custom_exceptions.InstanceUnavailableException( "Identifier:database-1 \nWaiter database-1 not found") self.mock_snapshot_creation_failure_status = "Identifier:database-1 \nWaiter encountered a terminal failure state" self.mocked_describe_db_clusters = { "ResponseMetadata": { 'HTTPStatusCode': 200 }, "DBClusters": [{ "DBClusterIdentifier": "database-1", "DatabaseName": "POSTGRES", "Port": 3306, "Engine": "xyzz", "EngineVersion": 10.7, "VpcSecurityGroups": [{ "VpcSecurityGroupId": "abc" }], "DBSubnetGroup": { "DBSubnetGroupName": "xyz" }, "DBClusterMembers": [{ "DBInstanceIdentifier": "database-1-instance-1", "IsClusterWriter": True, "PromotionTier": 123 }] }] }
def setUp(self): self.event = {"identifier": "database-1"} self.revert_event = {"Error": "ClusterRestoreException","Cause": "DBClusterIdentifier:database-1 \n ThrottlingError: Rate exceeded"} self.updated_cluster_id = self.event['identifier'] + constants.TEMP_POSTFIX self.original_cluster_id = self.event['identifier'] self.mocked_rate_exceeded_exception = custom_exceptions.RateExceededException("Identifier:database-1-temp \nthrottling error: Rate exceeded") self.mocked_cluster_not_found_exception = custom_exceptions.RenameException("Identifier:database-1-temp \nDBClusterNotFound") self.mocked_instance_not_found_exception = custom_exceptions.RenameException("Identifier:database-1-temp \nDBInstanceNotFound") self.mocked_describe_db_clusters = { 'ResponseMetadata': { 'HTTPStatusCode': 200 }, 'DBClusters': [{ "DBClusterIdentifier": "database-1", "DBClusterMembers": [{ "DBInstanceIdentifier": "database-1-instance-1", "IsClusterWriter": True, "PromotionTier": 123 }] }] } self.mocked_modify_db_cluster = { 'ResponseMetadata': { 'HTTPStatusCode': 200 }, 'DBClusters': { "DBClusterIdentifier": "database-1-temp", "Status": "renaming", "DBClusterMembers": [{ "DBInstanceIdentifier": "database-1-instance-1-temp", "IsClusterWriter": True, "PromotionTier": 123 }] } }
def setUp(self): self.event = {"identifier": "database-1-temp"} self.restored_instance_id = "database-1" self.mocked_rate_exceeded_exception = custom_exceptions.RateExceededException( "Identifier:database-1 \nthrottling error: Rate exceeded") self.mocked_instance_not_found_exception = custom_exceptions.InstanceRestoreException( "Identifier:database-1 \nDBInstanceNotFound") self.mocked_duplicate_instance_exception = custom_exceptions.InstanceRestoreException( "Identifier:database-1 \nDBInstanceAlreadyExistsFault") self.mocked_describe_db_instances = { 'ResponseMetadata': { 'HTTPStatusCode': 200 }, "DBInstances": [{ "DBInstanceIdentifier": "database-1-temp", "VpcSecurityGroups": [{ "VpcSecurityGroupId": "abc" }], "DBSubnetGroup": { "DBSubnetGroupName": "xyz" } }] }
def setUp(self): self.event = {"identifier": "database-1"} self.mocked_rate_exceeded_exception = custom_exceptions.RateExceededException("Identifier:database-1 \nthrottling error: Rate exceeded") self.mocked_cluster_not_found_exception = custom_exceptions.SnapshotCreationException("Identifier:database-1 \nDBClusterNotFound") self.mocked_duplicate_snapshot_exception = custom_exceptions.RetryClusterSnapshotException("Identifier:database-1 \nDBClusterSnapshotAlreadyExistsFault")