from __future__ import unicode_literals import copy import datetime import os from collections import defaultdict from boto3 import Session from jinja2 import Template from re import compile as re_compile 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, OptionGroupNotFoundFaultError, InvalidDBClusterStateFaultError, InvalidDBInstanceStateError, SnapshotQuotaExceededError, DBSnapshotAlreadyExistsError, ) class Database(BaseModel): def __init__(self, **kwargs): self.status = "available" self.is_replica = False self.replicas = [] self.region = kwargs.get("region") self.engine = kwargs.get("engine") self.engine_version = kwargs.get("engine_version", None) self.default_engine_versions = { "MySQL": "5.6.21", "mysql": "5.6.21", "oracle-se1": "11.2.0.4.v3", "oracle-se": "11.2.0.4.v3", "oracle-ee": "11.2.0.4.v3", "sqlserver-ee": "11.00.2100.60.v1", "sqlserver-se": "11.00.2100.60.v1", "sqlserver-ex": "11.00.2100.60.v1", "sqlserver-web": "11.00.2100.60.v1", "postgres": "9.3.3", } if not self.engine_version and self.engine in self.default_engine_versions: self.engine_version = self.default_engine_versions[self.engine] self.iops = kwargs.get("iops") self.storage_encrypted = kwargs.get("storage_encrypted", False) if self.storage_encrypted: self.kms_key_id = kwargs.get("kms_key_id", "default_kms_key_id") else: self.kms_key_id = kwargs.get("kms_key_id") self.storage_type = kwargs.get("storage_type") if self.storage_type is None: self.storage_type = Database.default_storage_type(iops=self.iops) self.master_username = kwargs.get("master_username") self.master_user_password = kwargs.get("master_user_password") self.auto_minor_version_upgrade = kwargs.get("auto_minor_version_upgrade") if self.auto_minor_version_upgrade is None: self.auto_minor_version_upgrade = True self.allocated_storage = kwargs.get("allocated_storage") if self.allocated_storage is None: self.allocated_storage = Database.default_allocated_storage( engine=self.engine, storage_type=self.storage_type ) self.db_instance_identifier = kwargs.get("db_instance_identifier") self.source_db_identifier = kwargs.get("source_db_identifier") self.db_instance_class = kwargs.get("db_instance_class") self.port = kwargs.get("port") if self.port is None: self.port = Database.default_port(self.engine) self.db_instance_identifier = kwargs.get("db_instance_identifier") self.db_name = kwargs.get("db_name") self.instance_create_time = iso_8601_datetime_with_milliseconds( datetime.datetime.now() ) self.publicly_accessible = kwargs.get("publicly_accessible") if self.publicly_accessible is None: self.publicly_accessible = True self.copy_tags_to_snapshot = kwargs.get("copy_tags_to_snapshot") if self.copy_tags_to_snapshot is None: self.copy_tags_to_snapshot = False 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") if self.db_subnet_group_name: self.db_subnet_group = rds2_backends[self.region].describe_subnet_groups( self.db_subnet_group_name )[0] else: self.db_subnet_group = None self.security_groups = kwargs.get("security_groups", []) self.vpc_security_group_ids = kwargs.get("vpc_security_group_ids", []) self.preferred_maintenance_window = kwargs.get( "preferred_maintenance_window", "wed:06:38-wed:07:08" ) self.db_parameter_group_name = kwargs.get("db_parameter_group_name") if ( self.db_parameter_group_name and self.db_parameter_group_name not in rds2_backends[self.region].db_parameter_groups ): raise DBParameterGroupNotFoundError(self.db_parameter_group_name) self.preferred_backup_window = kwargs.get( "preferred_backup_window", "13:14-13:44" ) self.license_model = kwargs.get("license_model", "general-public-license") self.option_group_name = kwargs.get("option_group_name", None) if ( self.option_group_name and self.option_group_name not in rds2_backends[self.region].option_groups ): raise OptionGroupNotFoundFaultError(self.option_group_name) self.default_option_groups = { "MySQL": "default.mysql5.6", "mysql": "default.mysql5.6", "postgres": "default.postgres9.3", } if not self.option_group_name and self.engine in self.default_option_groups: self.option_group_name = self.default_option_groups[self.engine] self.character_set_name = kwargs.get("character_set_name", None) self.enable_iam_database_authentication = kwargs.get( "enable_iam_database_authentication", False ) self.dbi_resource_id = "db-M5ENSHXFPU6XHZ4G4ZEI5QIO2U" self.tags = kwargs.get("tags", []) @property def db_instance_arn(self): return "arn:aws:rds:{0}:1234567890:db:{1}".format( self.region, self.db_instance_identifier ) @property def physical_resource_id(self): return self.db_instance_identifier def db_parameter_groups(self): if not self.db_parameter_group_name: ( db_family, db_parameter_group_name, ) = self.default_db_parameter_group_details() description = "Default parameter group for {0}".format(db_family) return [ DBParameterGroup( name=db_parameter_group_name, family=db_family, description=description, tags={}, ) ] else: return [ rds2_backends[self.region].db_parameter_groups[ self.db_parameter_group_name ] ] def default_db_parameter_group_details(self): if not self.engine_version: return (None, None) minor_engine_version = ".".join(self.engine_version.rsplit(".")[:-1]) db_family = "{0}{1}".format(self.engine.lower(), minor_engine_version) return db_family, "default.{0}".format(db_family) def to_xml(self): template = Template( """ {{ database.backup_retention_period }} {{ database.status }} {% if database.db_name %}{{ database.db_name }}{% endif %} {{ database.multi_az }} {% for vpc_security_group_id in database.vpc_security_group_ids %} active {{ vpc_security_group_id }} {% endfor %} {{ database.db_instance_identifier }} {{ database.dbi_resource_id }} {{ database.instance_create_time }} 03:50-04:20 wed:06:38-wed:07:08 {% for replica_id in database.replicas %} {{ replica_id }} {% endfor %} {% if database.is_replica %} read replication replicating true {% endif %} {% if database.is_replica %} {{ database.source_db_identifier }} {% endif %} {{ database.engine }} {{database.enable_iam_database_authentication|lower }} {{ database.license_model }} {{ database.engine_version }} {{ database.option_group_name }} in-sync {% for db_parameter_group in database.db_parameter_groups() %} in-sync {{ db_parameter_group.name }} {% endfor %} {% for security_group in database.security_groups %} active {{ security_group }} {% endfor %} {% if database.db_subnet_group %} {{ database.db_subnet_group.subnet_name }} {{ database.db_subnet_group.description }} {{ database.db_subnet_group.status }} {% for subnet in database.db_subnet_group.subnets %} Active {{ subnet.id }} {{ subnet.availability_zone }} false {% endfor %} {{ database.db_subnet_group.vpc_id }} {% endif %} {{ database.publicly_accessible }} {{ database.copy_tags_to_snapshot }} {{ database.auto_minor_version_upgrade }} {{ database.allocated_storage }} {{ database.storage_encrypted }} {% if database.kms_key_id %} {{ database.kms_key_id }} {% endif %} {% if database.iops %} {{ database.iops }} io1 {% else %} {{ database.storage_type }} {% endif %} {{ database.db_instance_class }} {{ database.master_username }}
{{ database.address }}
{{ database.port }}
{{ database.db_instance_arn }}
""" ) return template.render(database=self) @property def address(self): return "{0}.aaaaaaaaaa.{1}.rds.amazonaws.com".format( self.db_instance_identifier, self.region ) def add_replica(self, replica): self.replicas.append(replica.db_instance_identifier) def remove_replica(self, replica): self.replicas.remove(replica.db_instance_identifier) def set_as_replica(self): self.is_replica = True self.replicas = [] def update(self, db_kwargs): for key, value in db_kwargs.items(): if value is not None: setattr(self, key, value) def get_cfn_attribute(self, attribute_name): # Local import to avoid circular dependency with cloudformation.parsing from moto.cloudformation.exceptions import UnformattedGetAttTemplateException if attribute_name == "Endpoint.Address": return self.address elif attribute_name == "Endpoint.Port": return self.port raise UnformattedGetAttTemplateException() @staticmethod def default_port(engine): return { "mysql": 3306, "mariadb": 3306, "postgres": 5432, "oracle-ee": 1521, "oracle-se2": 1521, "oracle-se1": 1521, "oracle-se": 1521, "sqlserver-ee": 1433, "sqlserver-ex": 1433, "sqlserver-se": 1433, "sqlserver-web": 1433, }[engine] @staticmethod def default_storage_type(iops): if iops is None: return "gp2" else: return "io1" @staticmethod def default_allocated_storage(engine, storage_type): return { "aurora": {"gp2": 0, "io1": 0, "standard": 0}, "mysql": {"gp2": 20, "io1": 100, "standard": 5}, "mariadb": {"gp2": 20, "io1": 100, "standard": 5}, "postgres": {"gp2": 20, "io1": 100, "standard": 5}, "oracle-ee": {"gp2": 20, "io1": 100, "standard": 10}, "oracle-se2": {"gp2": 20, "io1": 100, "standard": 10}, "oracle-se1": {"gp2": 20, "io1": 100, "standard": 10}, "oracle-se": {"gp2": 20, "io1": 100, "standard": 10}, "sqlserver-ee": {"gp2": 200, "io1": 200, "standard": 200}, "sqlserver-ex": {"gp2": 20, "io1": 100, "standard": 20}, "sqlserver-se": {"gp2": 200, "io1": 200, "standard": 200}, "sqlserver-web": {"gp2": 20, "io1": 100, "standard": 20}, }[engine][storage_type] @classmethod def create_from_cloudformation_json( cls, resource_name, cloudformation_json, region_name ): properties = cloudformation_json["Properties"] db_instance_identifier = properties.get("DBInstanceIdentifier") if not db_instance_identifier: db_instance_identifier = resource_name.lower() + get_random_hex(12) db_security_groups = properties.get("DBSecurityGroups") if not db_security_groups: db_security_groups = [] security_groups = [group.group_name for group in db_security_groups] db_subnet_group = properties.get("DBSubnetGroupName") db_subnet_group_name = db_subnet_group.subnet_name if db_subnet_group else None db_kwargs = { "auto_minor_version_upgrade": properties.get("AutoMinorVersionUpgrade"), "allocated_storage": properties.get("AllocatedStorage"), "availability_zone": properties.get("AvailabilityZone"), "backup_retention_period": properties.get("BackupRetentionPeriod"), "db_instance_class": properties.get("DBInstanceClass"), "db_instance_identifier": db_instance_identifier, "db_name": properties.get("DBName"), "db_subnet_group_name": db_subnet_group_name, "engine": properties.get("Engine"), "engine_version": properties.get("EngineVersion"), "iops": properties.get("Iops"), "kms_key_id": properties.get("KmsKeyId"), "master_user_password": properties.get("MasterUserPassword"), "master_username": properties.get("MasterUsername"), "multi_az": properties.get("MultiAZ"), "db_parameter_group_name": properties.get("DBParameterGroupName"), "port": properties.get("Port", 3306), "publicly_accessible": properties.get("PubliclyAccessible"), "copy_tags_to_snapshot": properties.get("CopyTagsToSnapshot"), "region": region_name, "security_groups": security_groups, "storage_encrypted": properties.get("StorageEncrypted"), "storage_type": properties.get("StorageType"), "tags": properties.get("Tags"), "vpc_security_group_ids": properties.get("VpcSecurityGroupIds", []), } rds2_backend = rds2_backends[region_name] source_db_identifier = properties.get("SourceDBInstanceIdentifier") if source_db_identifier: # Replica db_kwargs["source_db_identifier"] = source_db_identifier database = rds2_backend.create_database_replica(db_kwargs) else: database = rds2_backend.create_database(db_kwargs) return database def to_json(self): template = Template( """{ "AllocatedStorage": 10, "AutoMinorVersionUpgrade": "{{ database.auto_minor_version_upgrade }}", "AvailabilityZone": "{{ database.availability_zone }}", "BackupRetentionPeriod": "{{ database.backup_retention_period }}", "CharacterSetName": {%- if database.character_set_name -%}{{ database.character_set_name }}{%- else %} null{%- endif -%}, "DBInstanceClass": "{{ database.db_instance_class }}", "DBInstanceIdentifier": "{{ database.db_instance_identifier }}", "DBInstanceStatus": "{{ database.status }}", "DBName": {%- if database.db_name -%}"{{ database.db_name }}"{%- else %} null{%- endif -%}, {% if database.db_parameter_group_name -%}"DBParameterGroups": { "DBParameterGroup": { "ParameterApplyStatus": "in-sync", "DBParameterGroupName": "{{ database.db_parameter_group_name }}" } },{%- endif %} "DBSecurityGroups": [ {% for security_group in database.security_groups -%}{%- if loop.index != 1 -%},{%- endif -%} {"DBSecurityGroup": { "Status": "active", "DBSecurityGroupName": "{{ security_group }}" }}{% endfor %} ], {%- if database.db_subnet_group -%}{{ database.db_subnet_group.to_json() }},{%- endif %} "Engine": "{{ database.engine }}", "EngineVersion": "{{ database.engine_version }}", "LatestRestorableTime": null, "LicenseModel": "{{ database.license_model }}", "MasterUsername": "{{ database.master_username }}", "MultiAZ": "{{ database.multi_az }}",{% if database.option_group_name %} "OptionGroupMemberships": [{ "OptionGroupMembership": { "OptionGroupName": "{{ database.option_group_name }}", "Status": "in-sync" } }],{%- endif %} "PendingModifiedValues": { "MasterUserPassword": "****" }, "PreferredBackupWindow": "{{ database.preferred_backup_window }}", "PreferredMaintenanceWindow": "{{ database.preferred_maintenance_window }}", "PubliclyAccessible": "{{ database.publicly_accessible }}", "CopyTagsToSnapshot": "{{ database.copy_tags_to_snapshot }}", "AllocatedStorage": "{{ database.allocated_storage }}", "Endpoint": { "Address": "{{ database.address }}", "Port": "{{ database.port }}" }, "InstanceCreateTime": "{{ database.instance_create_time }}", "Iops": null, "ReadReplicaDBInstanceIdentifiers": [{%- for replica in database.replicas -%} {%- if not loop.first -%},{%- endif -%} "{{ replica }}" {%- endfor -%} ], {%- if database.source_db_identifier -%} "ReadReplicaSourceDBInstanceIdentifier": "{{ database.source_db_identifier }}", {%- else -%} "ReadReplicaSourceDBInstanceIdentifier": null, {%- endif -%} "SecondaryAvailabilityZone": null, "StatusInfos": null, "VpcSecurityGroups": [ {% for vpc_security_group_id in database.vpc_security_group_ids %} { "Status": "active", "VpcSecurityGroupId": "{{ vpc_security_group_id }}" } {% endfor %} ], "DBInstanceArn": "{{ database.db_instance_arn }}" }""" ) return template.render(database=self) def get_tags(self): return self.tags def add_tags(self, tags): new_keys = [tag_set["Key"] for tag_set in tags] self.tags = [tag_set for tag_set in self.tags if tag_set["Key"] not in new_keys] self.tags.extend(tags) return self.tags def remove_tags(self, tag_keys): self.tags = [tag_set for tag_set in self.tags if tag_set["Key"] not in tag_keys] def delete(self, region_name): backend = rds2_backends[region_name] 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( """ {{ snapshot.snapshot_id }} {{ database.db_instance_identifier }} {{ snapshot.created_at }} {{ database.engine }} {{ database.allocated_storage }} available {{ database.port }} {{ database.availability_zone }} {{ database.db_subnet_group.vpc_id }} {{ snapshot.created_at }} {{ database.master_username }} {{ database.engine_version }} {{ database.license_model }} manual {% if database.iops %} {{ database.iops }} io1 {% else %} {{ database.storage_type }} {% endif %} {{ database.option_group_name }} {{ 100 }} {{ database.region }} {{ database.storage_encrypted }} {{ database.kms_key_id }} {{ snapshot.snapshot_arn }} {{ database.enable_iam_database_authentication|lower }} """ ) return template.render(snapshot=self, database=self.database) def get_tags(self): return self.tags def add_tags(self, tags): new_keys = [tag_set["Key"] for tag_set in tags] self.tags = [tag_set for tag_set in self.tags if tag_set["Key"] not in new_keys] self.tags.extend(tags) return self.tags def remove_tags(self, tag_keys): self.tags = [tag_set for tag_set in self.tags if tag_set["Key"] not in tag_keys] class SecurityGroup(BaseModel): def __init__(self, group_name, description, tags): self.group_name = group_name self.description = description self.status = "authorized" self.ip_ranges = [] self.ec2_security_groups = [] self.tags = tags self.owner_id = "1234567890" self.vpc_id = None def to_xml(self): template = Template( """ {% for security_group in security_group.ec2_security_groups %} {{ security_group.id }} {{ security_group.name }} {{ security_group.owner_id }} authorized {% endfor %} {{ security_group.description }} {% for ip_range in security_group.ip_ranges %} {{ ip_range }} authorized {% endfor %} {{ security_group.ownder_id }} {{ security_group.group_name }} """ ) return template.render(security_group=self) def to_json(self): template = Template( """{ "DBSecurityGroupDescription": "{{ security_group.description }}", "DBSecurityGroupName": "{{ security_group.group_name }}", "EC2SecurityGroups": {{ security_group.ec2_security_groups }}, "IPRanges": [{%- for ip in security_group.ip_ranges -%} {%- if loop.index != 1 -%},{%- endif -%} "{{ ip }}" {%- endfor -%} ], "OwnerId": "{{ security_group.owner_id }}", "VpcId": "{{ security_group.vpc_id }}" }""" ) return template.render(security_group=self) def authorize_cidr(self, cidr_ip): self.ip_ranges.append(cidr_ip) def authorize_security_group(self, security_group): self.ec2_security_groups.append(security_group) @classmethod def create_from_cloudformation_json( cls, resource_name, cloudformation_json, region_name ): properties = cloudformation_json["Properties"] group_name = resource_name.lower() + get_random_hex(12) description = properties["GroupDescription"] security_group_ingress_rules = properties.get("DBSecurityGroupIngress", []) tags = properties.get("Tags") ec2_backend = ec2_backends[region_name] rds2_backend = rds2_backends[region_name] security_group = rds2_backend.create_security_group( group_name, description, tags ) for security_group_ingress in security_group_ingress_rules: for ingress_type, ingress_value in security_group_ingress.items(): if ingress_type == "CIDRIP": security_group.authorize_cidr(ingress_value) elif ingress_type == "EC2SecurityGroupName": subnet = ec2_backend.get_security_group_from_name(ingress_value) security_group.authorize_security_group(subnet) elif ingress_type == "EC2SecurityGroupId": subnet = ec2_backend.get_security_group_from_id(ingress_value) security_group.authorize_security_group(subnet) return security_group def get_tags(self): return self.tags def add_tags(self, tags): new_keys = [tag_set["Key"] for tag_set in tags] self.tags = [tag_set for tag_set in self.tags if tag_set["Key"] not in new_keys] self.tags.extend(tags) return self.tags def remove_tags(self, tag_keys): self.tags = [tag_set for tag_set in self.tags if tag_set["Key"] not in tag_keys] def delete(self, region_name): backend = rds2_backends[region_name] backend.delete_security_group(self.group_name) class SubnetGroup(BaseModel): def __init__(self, subnet_name, description, subnets, tags): self.subnet_name = subnet_name self.description = description self.subnets = subnets self.status = "Complete" self.tags = tags self.vpc_id = self.subnets[0].vpc_id def to_xml(self): template = Template( """ {{ subnet_group.vpc_id }} {{ subnet_group.status }} {{ subnet_group.description }} {{ subnet_group.subnet_name }} {% for subnet in subnet_group.subnets %} Active {{ subnet.id }} {{ subnet.availability_zone }} false {% endfor %} """ ) return template.render(subnet_group=self) def to_json(self): template = Template( """"DBSubnetGroup": { "VpcId": "{{ subnet_group.vpc_id }}", "SubnetGroupStatus": "{{ subnet_group.status }}", "DBSubnetGroupDescription": "{{ subnet_group.description }}", "DBSubnetGroupName": "{{ subnet_group.subnet_name }}", "Subnets": { "Subnet": [ {% for subnet in subnet_group.subnets %}{ "SubnetStatus": "Active", "SubnetIdentifier": "{{ subnet.id }}", "SubnetAvailabilityZone": { "Name": "{{ subnet.availability_zone }}", "ProvisionedIopsCapable": "false" } }{%- if not loop.last -%},{%- endif -%}{% endfor %} ] } }""" ) return template.render(subnet_group=self) @classmethod def create_from_cloudformation_json( cls, resource_name, cloudformation_json, region_name ): properties = cloudformation_json["Properties"] subnet_name = resource_name.lower() + get_random_hex(12) description = properties["DBSubnetGroupDescription"] subnet_ids = properties["SubnetIds"] tags = properties.get("Tags") ec2_backend = ec2_backends[region_name] subnets = [ec2_backend.get_subnet(subnet_id) for subnet_id in subnet_ids] rds2_backend = rds2_backends[region_name] subnet_group = rds2_backend.create_subnet_group( subnet_name, description, subnets, tags ) return subnet_group def get_tags(self): return self.tags def add_tags(self, tags): new_keys = [tag_set["Key"] for tag_set in tags] self.tags = [tag_set for tag_set in self.tags if tag_set["Key"] not in new_keys] self.tags.extend(tags) return self.tags def remove_tags(self, tag_keys): self.tags = [tag_set for tag_set in self.tags if tag_set["Key"] not in tag_keys] def delete(self, region_name): backend = rds2_backends[region_name] backend.delete_subnet_group(self.subnet_name) class RDS2Backend(BaseBackend): def __init__(self, region): self.region = region 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 = {} self.subnet_groups = {} def reset(self): # preserve region region = self.region self.__dict__ = {} self.__init__(region) 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 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() if tags is None: tags = list() if database.copy_tags_to_snapshot and not tags: tags = database.get_tags() 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"] primary = self.find_db_from_id(source_database_id) if self.arn_regex.match(source_database_id): db_kwargs["region"] = self.region # Shouldn't really copy here as the instance is duplicated. RDS replicas have different instances. replica = copy.copy(primary) replica.update(db_kwargs) replica.set_as_replica() self.databases[database_id] = replica primary.add_replica(replica) return replica 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 describe_snapshots(self, db_instance_identifier, db_snapshot_identifier): if db_instance_identifier: db_instance_snapshots = [] for snapshot in self.snapshots.values(): if snapshot.database.db_instance_identifier == db_instance_identifier: db_instance_snapshots.append(snapshot) return db_instance_snapshots 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] if "new_db_instance_identifier" in db_kwargs: del self.databases[db_instance_identifier] db_instance_identifier = db_kwargs[ "db_instance_identifier" ] = db_kwargs.pop("new_db_instance_identifier") self.databases[db_instance_identifier] = database database.update(db_kwargs) return database def reboot_db_instance(self, db_instance_identifier): database = self.describe_databases(db_instance_identifier)[0] return database def stop_database(self, db_instance_identifier, db_snapshot_identifier=None): database = self.describe_databases(db_instance_identifier)[0] # todo: certain rds types not allowed to be stopped at this time. # https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_StopInstance.html#USER_StopInstance.Limitations if database.is_replica or ( database.multi_az and database.engine.lower().startswith("sqlserver") ): # todo: more db types not supported by stop/start instance api raise InvalidDBClusterStateFaultError(db_instance_identifier) if database.status != "available": raise InvalidDBInstanceStateError(db_instance_identifier, "stop") if db_snapshot_identifier: self.create_snapshot(db_instance_identifier, db_snapshot_identifier) database.status = "stopped" return database def start_database(self, db_instance_identifier): database = self.describe_databases(db_instance_identifier)[0] # todo: bunch of different error messages to be generated from this api call if database.status != "stopped": raise InvalidDBInstanceStateError(db_instance_identifier, "start") database.status = "available" return database def find_db_from_id(self, db_id): if self.arn_regex.match(db_id): arn_breakdown = db_id.split(":") region = arn_breakdown[3] backend = rds2_backends[region] db_name = arn_breakdown[-1] else: backend = self db_name = db_id return backend.describe_databases(db_name)[0] def delete_database(self, db_instance_identifier, db_snapshot_name=None): if db_instance_identifier in self.databases: if db_snapshot_name: self.create_snapshot(db_instance_identifier, db_snapshot_name) database = self.databases.pop(db_instance_identifier) if database.is_replica: primary = self.find_db_from_id(database.source_db_identifier) primary.remove_replica(database) database.status = "deleting" return database else: raise DBInstanceNotFoundError(db_instance_identifier) def create_security_group(self, group_name, description, tags): security_group = SecurityGroup(group_name, description, tags) self.security_groups[group_name] = security_group return security_group def describe_security_groups(self, security_group_name): if security_group_name: if security_group_name in self.security_groups: return [self.security_groups[security_group_name]] else: raise DBSecurityGroupNotFoundError(security_group_name) return self.security_groups.values() def delete_security_group(self, security_group_name): if security_group_name in self.security_groups: return self.security_groups.pop(security_group_name) else: raise DBSecurityGroupNotFoundError(security_group_name) def delete_db_parameter_group(self, db_parameter_group_name): if db_parameter_group_name in self.db_parameter_groups: return self.db_parameter_groups.pop(db_parameter_group_name) else: raise DBParameterGroupNotFoundError(db_parameter_group_name) def authorize_security_group(self, security_group_name, cidr_ip): security_group = self.describe_security_groups(security_group_name)[0] security_group.authorize_cidr(cidr_ip) return security_group def create_subnet_group(self, subnet_name, description, subnets, tags): subnet_group = SubnetGroup(subnet_name, description, subnets, tags) self.subnet_groups[subnet_name] = subnet_group return subnet_group def describe_subnet_groups(self, subnet_group_name): if subnet_group_name: if subnet_group_name in self.subnet_groups: return [self.subnet_groups[subnet_group_name]] else: raise DBSubnetGroupNotFoundError(subnet_group_name) return self.subnet_groups.values() def delete_subnet_group(self, subnet_name): if subnet_name in self.subnet_groups: return self.subnet_groups.pop(subnet_name) else: raise DBSubnetGroupNotFoundError(subnet_name) def create_option_group(self, option_group_kwargs): option_group_id = option_group_kwargs["name"] valid_option_group_engines = { "mariadb": ["10.0", "10.1", "10.2", "10.3"], "mysql": ["5.5", "5.6", "5.7", "8.0"], "oracle-se2": ["11.2", "12.1", "12.2"], "oracle-se1": ["11.2", "12.1", "12.2"], "oracle-se": ["11.2", "12.1", "12.2"], "oracle-ee": ["11.2", "12.1", "12.2"], "sqlserver-se": ["10.50", "11.00"], "sqlserver-ee": ["10.50", "11.00"], "sqlserver-ex": ["10.50", "11.00"], "sqlserver-web": ["10.50", "11.00"], } if option_group_kwargs["name"] in self.option_groups: raise RDSClientError( "OptionGroupAlreadyExistsFault", "An option group named {0} already exists.".format( option_group_kwargs["name"] ), ) if ( "description" not in option_group_kwargs or not option_group_kwargs["description"] ): raise RDSClientError( "InvalidParameterValue", "The parameter OptionGroupDescription must be provided and must not be blank.", ) if option_group_kwargs["engine_name"] not in valid_option_group_engines.keys(): raise RDSClientError( "InvalidParameterValue", "Invalid DB engine: non-existent" ) if ( option_group_kwargs["major_engine_version"] not in valid_option_group_engines[option_group_kwargs["engine_name"]] ): raise RDSClientError( "InvalidParameterCombination", "Cannot find major version {0} for {1}".format( option_group_kwargs["major_engine_version"], option_group_kwargs["engine_name"], ), ) option_group = OptionGroup(**option_group_kwargs) self.option_groups[option_group_id] = option_group return option_group def delete_option_group(self, option_group_name): if option_group_name in self.option_groups: return self.option_groups.pop(option_group_name) else: raise OptionGroupNotFoundFaultError(option_group_name) def describe_option_groups(self, option_group_kwargs): option_group_list = [] if option_group_kwargs["marker"]: marker = option_group_kwargs["marker"] else: marker = 0 if option_group_kwargs["max_records"]: if ( option_group_kwargs["max_records"] < 20 or option_group_kwargs["max_records"] > 100 ): raise RDSClientError( "InvalidParameterValue", "Invalid value for max records. Must be between 20 and 100", ) max_records = option_group_kwargs["max_records"] else: max_records = 100 for option_group_name, option_group in self.option_groups.items(): if ( option_group_kwargs["name"] and option_group.name != option_group_kwargs["name"] ): continue elif ( option_group_kwargs["engine_name"] and option_group.engine_name != option_group_kwargs["engine_name"] ): continue elif ( option_group_kwargs["major_engine_version"] and option_group.major_engine_version != option_group_kwargs["major_engine_version"] ): continue else: option_group_list.append(option_group) if not len(option_group_list): raise OptionGroupNotFoundFaultError(option_group_kwargs["name"]) return option_group_list[marker : max_records + marker] @staticmethod def describe_option_group_options(engine_name, major_engine_version=None): default_option_group_options = { "mysql": { "5.6": '\n \n \n \n 5.611211TrueInnodb Memcached for MySQLMEMCACHED1-4294967295STATIC1TrueSpecifies how many memcached read operations (get) to perform before doing a COMMIT to start a new transactionDAEMON_MEMCACHED_R_BATCH_SIZE1-4294967295STATIC1TrueSpecifies how many memcached write operations, such as add, set, or incr, to perform before doing a COMMIT to start a new transactionDAEMON_MEMCACHED_W_BATCH_SIZE1-1073741824DYNAMIC5TrueSpecifies how often to auto-commit idle connections that use the InnoDB memcached interface.INNODB_API_BK_COMMIT_INTERVAL0,1STATIC0TrueDisables the use of row locks when using the InnoDB memcached interface.INNODB_API_DISABLE_ROWLOCK0,1STATIC0TrueLocks the table used by the InnoDB memcached plugin, so that it cannot be dropped or altered by DDL through the SQL interface.INNODB_API_ENABLE_MDL0-3STATIC0TrueLets you control the transaction isolation level on queries processed by the memcached interface.INNODB_API_TRX_LEVELauto,ascii,binarySTATICautoTrueThe binding protocol to use which can be either auto, ascii, or binary. The default is auto which means the server automatically negotiates the protocol with the client.BINDING_PROTOCOL1-2048STATIC1024TrueThe backlog queue configures how many network connections can be waiting to be processed by memcachedBACKLOG_QUEUE_LIMIT0,1STATIC0TrueDisable the use of compare and swap (CAS) which reduces the per-item size by 8 bytes.CAS_DISABLED1-48STATIC48TrueMinimum chunk size in bytes to allocate for the smallest item\'s key, value, and flags. The default is 48 and you can get a significant memory efficiency gain with a lower value.CHUNK_SIZE1-2STATIC1.25TrueChunk size growth factor that controls the size of each successive chunk with each chunk growing times this amount larger than the previous chunk.CHUNK_SIZE_GROWTH_FACTOR0,1STATIC0TrueIf enabled when there is no more memory to store items, memcached will return an error rather than evicting items.ERROR_ON_MEMORY_EXHAUSTED10-1024STATIC1024TrueMaximum number of concurrent connections. Setting this value to anything less than 10 prevents MySQL from starting.MAX_SIMULTANEOUS_CONNECTIONSv,vv,vvvSTATICvTrueVerbose level for memcached.VERBOSITYmysql\n \n \n \n \n 457f7bb8-9fbf-11e4-9084-5754f80d5144\n \n', "all": '\n \n \n \n 5.611211TrueInnodb Memcached for MySQLMEMCACHED1-4294967295STATIC1TrueSpecifies how many memcached read operations (get) to perform before doing a COMMIT to start a new transactionDAEMON_MEMCACHED_R_BATCH_SIZE1-4294967295STATIC1TrueSpecifies how many memcached write operations, such as add, set, or incr, to perform before doing a COMMIT to start a new transactionDAEMON_MEMCACHED_W_BATCH_SIZE1-1073741824DYNAMIC5TrueSpecifies how often to auto-commit idle connections that use the InnoDB memcached interface.INNODB_API_BK_COMMIT_INTERVAL0,1STATIC0TrueDisables the use of row locks when using the InnoDB memcached interface.INNODB_API_DISABLE_ROWLOCK0,1STATIC0TrueLocks the table used by the InnoDB memcached plugin, so that it cannot be dropped or altered by DDL through the SQL interface.INNODB_API_ENABLE_MDL0-3STATIC0TrueLets you control the transaction isolation level on queries processed by the memcached interface.INNODB_API_TRX_LEVELauto,ascii,binarySTATICautoTrueThe binding protocol to use which can be either auto, ascii, or binary. The default is auto which means the server automatically negotiates the protocol with the client.BINDING_PROTOCOL1-2048STATIC1024TrueThe backlog queue configures how many network connections can be waiting to be processed by memcachedBACKLOG_QUEUE_LIMIT0,1STATIC0TrueDisable the use of compare and swap (CAS) which reduces the per-item size by 8 bytes.CAS_DISABLED1-48STATIC48TrueMinimum chunk size in bytes to allocate for the smallest item\'s key, value, and flags. The default is 48 and you can get a significant memory efficiency gain with a lower value.CHUNK_SIZE1-2STATIC1.25TrueChunk size growth factor that controls the size of each successive chunk with each chunk growing times this amount larger than the previous chunk.CHUNK_SIZE_GROWTH_FACTOR0,1STATIC0TrueIf enabled when there is no more memory to store items, memcached will return an error rather than evicting items.ERROR_ON_MEMORY_EXHAUSTED10-1024STATIC1024TrueMaximum number of concurrent connections. Setting this value to anything less than 10 prevents MySQL from starting.MAX_SIMULTANEOUS_CONNECTIONSv,vv,vvvSTATICvTrueVerbose level for memcached.VERBOSITYmysql\n \n \n \n \n 457f7bb8-9fbf-11e4-9084-5754f80d5144\n \n', }, "oracle-ee": { "11.2": '\n \n \n \n 11.2XMLDBOracle Application Express Runtime EnvironmentAPEXoracle-ee\n \n 11.2APEXOracle Application Express Development EnvironmentAPEX-DEVoracle-ee\n \n 11.2Oracle Advanced Security - Native Network EncryptionNATIVE_NETWORK_ENCRYPTIONACCEPTED,REJECTED,REQUESTED,REQUIREDSTATICREQUESTEDTrueSpecifies the desired encryption behaviorSQLNET.ENCRYPTION_SERVERACCEPTED,REJECTED,REQUESTED,REQUIREDSTATICREQUESTEDTrueSpecifies the desired data integrity behaviorSQLNET.CRYPTO_CHECKSUM_SERVERRC4_256,AES256,AES192,3DES168,RC4_128,AES128,3DES112,RC4_56,DES,RC4_40,DES40STATICRC4_256,AES256,AES192,3DES168,RC4_128,AES128,3DES112,RC4_56,DES,RC4_40,DES40TrueSpecifies list of encryption algorithms in order of intended useSQLNET.ENCRYPTION_TYPES_SERVERSHA1,MD5STATICSHA1,MD5TrueSpecifies list of checksumming algorithms in order of intended useSQLNET.CRYPTO_CHECKSUM_TYPES_SERVERoracle-ee\n \n 11.21158TrueOracle Enterprise Manager (Database Control only)OEMoracle-ee\n \n 11.2Oracle StatspackSTATSPACKoracle-ee\n \n 11.2TrueTrueOracle Advanced Security - Transparent Data EncryptionTDEoracle-ee\n \n 11.2TrueTrueOracle Advanced Security - TDE with HSMTDE_HSMoracle-ee\n \n 11.2TrueTrueChange time zoneTimezoneAfrica/Cairo,Africa/Casablanca,Africa/Harare,Africa/Monrovia,Africa/Nairobi,Africa/Tripoli,Africa/Windhoek,America/Araguaina,America/Asuncion,America/Bogota,America/Caracas,America/Chihuahua,America/Cuiaba,America/Denver,America/Fortaleza,America/Guatemala,America/Halifax,America/Manaus,America/Matamoros,America/Monterrey,America/Montevideo,America/Phoenix,America/Santiago,America/Tijuana,Asia/Amman,Asia/Ashgabat,Asia/Baghdad,Asia/Baku,Asia/Bangkok,Asia/Beirut,Asia/Calcutta,Asia/Damascus,Asia/Dhaka,Asia/Irkutsk,Asia/Jerusalem,Asia/Kabul,Asia/Karachi,Asia/Kathmandu,Asia/Krasnoyarsk,Asia/Magadan,Asia/Muscat,Asia/Novosibirsk,Asia/Riyadh,Asia/Seoul,Asia/Shanghai,Asia/Singapore,Asia/Taipei,Asia/Tehran,Asia/Tokyo,Asia/Ulaanbaatar,Asia/Vladivostok,Asia/Yakutsk,Asia/Yerevan,Atlantic/Azores,Australia/Adelaide,Australia/Brisbane,Australia/Darwin,Australia/Hobart,Australia/Perth,Australia/Sydney,Brazil/East,Canada/Newfoundland,Canada/Saskatchewan,Europe/Amsterdam,Europe/Athens,Europe/Dublin,Europe/Helsinki,Europe/Istanbul,Europe/Kaliningrad,Europe/Moscow,Europe/Paris,Europe/Prague,Europe/Sarajevo,Pacific/Auckland,Pacific/Fiji,Pacific/Guam,Pacific/Honolulu,Pacific/Samoa,US/Alaska,US/Central,US/Eastern,US/East-Indiana,US/Pacific,UTCDYNAMICUTCTrueSpecifies the timezone the user wants to change the system time toTIME_ZONEoracle-ee\n \n 11.2Oracle XMLDB RepositoryXMLDBoracle-ee\n \n \n \n \n 457f7bb8-9fbf-11e4-9084-5754f80d5144\n \n', "all": '\n \n \n \n 11.2XMLDBOracle Application Express Runtime EnvironmentAPEXoracle-ee\n \n 11.2APEXOracle Application Express Development EnvironmentAPEX-DEVoracle-ee\n \n 11.2Oracle Advanced Security - Native Network EncryptionNATIVE_NETWORK_ENCRYPTIONACCEPTED,REJECTED,REQUESTED,REQUIREDSTATICREQUESTEDTrueSpecifies the desired encryption behaviorSQLNET.ENCRYPTION_SERVERACCEPTED,REJECTED,REQUESTED,REQUIREDSTATICREQUESTEDTrueSpecifies the desired data integrity behaviorSQLNET.CRYPTO_CHECKSUM_SERVERRC4_256,AES256,AES192,3DES168,RC4_128,AES128,3DES112,RC4_56,DES,RC4_40,DES40STATICRC4_256,AES256,AES192,3DES168,RC4_128,AES128,3DES112,RC4_56,DES,RC4_40,DES40TrueSpecifies list of encryption algorithms in order of intended useSQLNET.ENCRYPTION_TYPES_SERVERSHA1,MD5STATICSHA1,MD5TrueSpecifies list of checksumming algorithms in order of intended useSQLNET.CRYPTO_CHECKSUM_TYPES_SERVERoracle-ee\n \n 11.21158TrueOracle Enterprise Manager (Database Control only)OEMoracle-ee\n \n 11.2Oracle StatspackSTATSPACKoracle-ee\n \n 11.2TrueTrueOracle Advanced Security - Transparent Data EncryptionTDEoracle-ee\n \n 11.2TrueTrueOracle Advanced Security - TDE with HSMTDE_HSMoracle-ee\n \n 11.2TrueTrueChange time zoneTimezoneAfrica/Cairo,Africa/Casablanca,Africa/Harare,Africa/Monrovia,Africa/Nairobi,Africa/Tripoli,Africa/Windhoek,America/Araguaina,America/Asuncion,America/Bogota,America/Caracas,America/Chihuahua,America/Cuiaba,America/Denver,America/Fortaleza,America/Guatemala,America/Halifax,America/Manaus,America/Matamoros,America/Monterrey,America/Montevideo,America/Phoenix,America/Santiago,America/Tijuana,Asia/Amman,Asia/Ashgabat,Asia/Baghdad,Asia/Baku,Asia/Bangkok,Asia/Beirut,Asia/Calcutta,Asia/Damascus,Asia/Dhaka,Asia/Irkutsk,Asia/Jerusalem,Asia/Kabul,Asia/Karachi,Asia/Kathmandu,Asia/Krasnoyarsk,Asia/Magadan,Asia/Muscat,Asia/Novosibirsk,Asia/Riyadh,Asia/Seoul,Asia/Shanghai,Asia/Singapore,Asia/Taipei,Asia/Tehran,Asia/Tokyo,Asia/Ulaanbaatar,Asia/Vladivostok,Asia/Yakutsk,Asia/Yerevan,Atlantic/Azores,Australia/Adelaide,Australia/Brisbane,Australia/Darwin,Australia/Hobart,Australia/Perth,Australia/Sydney,Brazil/East,Canada/Newfoundland,Canada/Saskatchewan,Europe/Amsterdam,Europe/Athens,Europe/Dublin,Europe/Helsinki,Europe/Istanbul,Europe/Kaliningrad,Europe/Moscow,Europe/Paris,Europe/Prague,Europe/Sarajevo,Pacific/Auckland,Pacific/Fiji,Pacific/Guam,Pacific/Honolulu,Pacific/Samoa,US/Alaska,US/Central,US/Eastern,US/East-Indiana,US/Pacific,UTCDYNAMICUTCTrueSpecifies the timezone the user wants to change the system time toTIME_ZONEoracle-ee\n \n 11.2Oracle XMLDB RepositoryXMLDBoracle-ee\n \n \n \n \n 457f7bb8-9fbf-11e4-9084-5754f80d5144\n \n', }, "oracle-sa": { "11.2": '\n \n \n \n 11.2XMLDBOracle Application Express Runtime EnvironmentAPEXoracle-ee\n \n 11.2APEXOracle Application Express Development EnvironmentAPEX-DEVoracle-ee\n \n 11.2Oracle Advanced Security - Native Network EncryptionNATIVE_NETWORK_ENCRYPTIONACCEPTED,REJECTED,REQUESTED,REQUIREDSTATICREQUESTEDTrueSpecifies the desired encryption behaviorSQLNET.ENCRYPTION_SERVERACCEPTED,REJECTED,REQUESTED,REQUIREDSTATICREQUESTEDTrueSpecifies the desired data integrity behaviorSQLNET.CRYPTO_CHECKSUM_SERVERRC4_256,AES256,AES192,3DES168,RC4_128,AES128,3DES112,RC4_56,DES,RC4_40,DES40STATICRC4_256,AES256,AES192,3DES168,RC4_128,AES128,3DES112,RC4_56,DES,RC4_40,DES40TrueSpecifies list of encryption algorithms in order of intended useSQLNET.ENCRYPTION_TYPES_SERVERSHA1,MD5STATICSHA1,MD5TrueSpecifies list of checksumming algorithms in order of intended useSQLNET.CRYPTO_CHECKSUM_TYPES_SERVERoracle-ee\n \n 11.21158TrueOracle Enterprise Manager (Database Control only)OEMoracle-ee\n \n 11.2Oracle StatspackSTATSPACKoracle-ee\n \n 11.2TrueTrueOracle Advanced Security - Transparent Data EncryptionTDEoracle-ee\n \n 11.2TrueTrueOracle Advanced Security - TDE with HSMTDE_HSMoracle-ee\n \n 11.2TrueTrueChange time zoneTimezoneAfrica/Cairo,Africa/Casablanca,Africa/Harare,Africa/Monrovia,Africa/Nairobi,Africa/Tripoli,Africa/Windhoek,America/Araguaina,America/Asuncion,America/Bogota,America/Caracas,America/Chihuahua,America/Cuiaba,America/Denver,America/Fortaleza,America/Guatemala,America/Halifax,America/Manaus,America/Matamoros,America/Monterrey,America/Montevideo,America/Phoenix,America/Santiago,America/Tijuana,Asia/Amman,Asia/Ashgabat,Asia/Baghdad,Asia/Baku,Asia/Bangkok,Asia/Beirut,Asia/Calcutta,Asia/Damascus,Asia/Dhaka,Asia/Irkutsk,Asia/Jerusalem,Asia/Kabul,Asia/Karachi,Asia/Kathmandu,Asia/Krasnoyarsk,Asia/Magadan,Asia/Muscat,Asia/Novosibirsk,Asia/Riyadh,Asia/Seoul,Asia/Shanghai,Asia/Singapore,Asia/Taipei,Asia/Tehran,Asia/Tokyo,Asia/Ulaanbaatar,Asia/Vladivostok,Asia/Yakutsk,Asia/Yerevan,Atlantic/Azores,Australia/Adelaide,Australia/Brisbane,Australia/Darwin,Australia/Hobart,Australia/Perth,Australia/Sydney,Brazil/East,Canada/Newfoundland,Canada/Saskatchewan,Europe/Amsterdam,Europe/Athens,Europe/Dublin,Europe/Helsinki,Europe/Istanbul,Europe/Kaliningrad,Europe/Moscow,Europe/Paris,Europe/Prague,Europe/Sarajevo,Pacific/Auckland,Pacific/Fiji,Pacific/Guam,Pacific/Honolulu,Pacific/Samoa,US/Alaska,US/Central,US/Eastern,US/East-Indiana,US/Pacific,UTCDYNAMICUTCTrueSpecifies the timezone the user wants to change the system time toTIME_ZONEoracle-ee\n \n 11.2Oracle XMLDB RepositoryXMLDBoracle-ee\n \n \n \n \n 457f7bb8-9fbf-11e4-9084-5754f80d5144\n \n', "all": '\n \n \n \n 11.2XMLDBOracle Application Express Runtime EnvironmentAPEXoracle-ee\n \n 11.2APEXOracle Application Express Development EnvironmentAPEX-DEVoracle-ee\n \n 11.2Oracle Advanced Security - Native Network EncryptionNATIVE_NETWORK_ENCRYPTIONACCEPTED,REJECTED,REQUESTED,REQUIREDSTATICREQUESTEDTrueSpecifies the desired encryption behaviorSQLNET.ENCRYPTION_SERVERACCEPTED,REJECTED,REQUESTED,REQUIREDSTATICREQUESTEDTrueSpecifies the desired data integrity behaviorSQLNET.CRYPTO_CHECKSUM_SERVERRC4_256,AES256,AES192,3DES168,RC4_128,AES128,3DES112,RC4_56,DES,RC4_40,DES40STATICRC4_256,AES256,AES192,3DES168,RC4_128,AES128,3DES112,RC4_56,DES,RC4_40,DES40TrueSpecifies list of encryption algorithms in order of intended useSQLNET.ENCRYPTION_TYPES_SERVERSHA1,MD5STATICSHA1,MD5TrueSpecifies list of checksumming algorithms in order of intended useSQLNET.CRYPTO_CHECKSUM_TYPES_SERVERoracle-ee\n \n 11.21158TrueOracle Enterprise Manager (Database Control only)OEMoracle-ee\n \n 11.2Oracle StatspackSTATSPACKoracle-ee\n \n 11.2TrueTrueOracle Advanced Security - Transparent Data EncryptionTDEoracle-ee\n \n 11.2TrueTrueOracle Advanced Security - TDE with HSMTDE_HSMoracle-ee\n \n 11.2TrueTrueChange time zoneTimezoneAfrica/Cairo,Africa/Casablanca,Africa/Harare,Africa/Monrovia,Africa/Nairobi,Africa/Tripoli,Africa/Windhoek,America/Araguaina,America/Asuncion,America/Bogota,America/Caracas,America/Chihuahua,America/Cuiaba,America/Denver,America/Fortaleza,America/Guatemala,America/Halifax,America/Manaus,America/Matamoros,America/Monterrey,America/Montevideo,America/Phoenix,America/Santiago,America/Tijuana,Asia/Amman,Asia/Ashgabat,Asia/Baghdad,Asia/Baku,Asia/Bangkok,Asia/Beirut,Asia/Calcutta,Asia/Damascus,Asia/Dhaka,Asia/Irkutsk,Asia/Jerusalem,Asia/Kabul,Asia/Karachi,Asia/Kathmandu,Asia/Krasnoyarsk,Asia/Magadan,Asia/Muscat,Asia/Novosibirsk,Asia/Riyadh,Asia/Seoul,Asia/Shanghai,Asia/Singapore,Asia/Taipei,Asia/Tehran,Asia/Tokyo,Asia/Ulaanbaatar,Asia/Vladivostok,Asia/Yakutsk,Asia/Yerevan,Atlantic/Azores,Australia/Adelaide,Australia/Brisbane,Australia/Darwin,Australia/Hobart,Australia/Perth,Australia/Sydney,Brazil/East,Canada/Newfoundland,Canada/Saskatchewan,Europe/Amsterdam,Europe/Athens,Europe/Dublin,Europe/Helsinki,Europe/Istanbul,Europe/Kaliningrad,Europe/Moscow,Europe/Paris,Europe/Prague,Europe/Sarajevo,Pacific/Auckland,Pacific/Fiji,Pacific/Guam,Pacific/Honolulu,Pacific/Samoa,US/Alaska,US/Central,US/Eastern,US/East-Indiana,US/Pacific,UTCDYNAMICUTCTrueSpecifies the timezone the user wants to change the system time toTIME_ZONEoracle-ee\n \n 11.2Oracle XMLDB RepositoryXMLDBoracle-ee\n \n \n \n \n 457f7bb8-9fbf-11e4-9084-5754f80d5144\n \n', }, "oracle-sa1": { "11.2": '\n \n \n \n 11.2XMLDBOracle Application Express Runtime EnvironmentAPEXoracle-ee\n \n 11.2APEXOracle Application Express Development EnvironmentAPEX-DEVoracle-ee\n \n 11.2Oracle Advanced Security - Native Network EncryptionNATIVE_NETWORK_ENCRYPTIONACCEPTED,REJECTED,REQUESTED,REQUIREDSTATICREQUESTEDTrueSpecifies the desired encryption behaviorSQLNET.ENCRYPTION_SERVERACCEPTED,REJECTED,REQUESTED,REQUIREDSTATICREQUESTEDTrueSpecifies the desired data integrity behaviorSQLNET.CRYPTO_CHECKSUM_SERVERRC4_256,AES256,AES192,3DES168,RC4_128,AES128,3DES112,RC4_56,DES,RC4_40,DES40STATICRC4_256,AES256,AES192,3DES168,RC4_128,AES128,3DES112,RC4_56,DES,RC4_40,DES40TrueSpecifies list of encryption algorithms in order of intended useSQLNET.ENCRYPTION_TYPES_SERVERSHA1,MD5STATICSHA1,MD5TrueSpecifies list of checksumming algorithms in order of intended useSQLNET.CRYPTO_CHECKSUM_TYPES_SERVERoracle-ee\n \n 11.21158TrueOracle Enterprise Manager (Database Control only)OEMoracle-ee\n \n 11.2Oracle StatspackSTATSPACKoracle-ee\n \n 11.2TrueTrueOracle Advanced Security - Transparent Data EncryptionTDEoracle-ee\n \n 11.2TrueTrueOracle Advanced Security - TDE with HSMTDE_HSMoracle-ee\n \n 11.2TrueTrueChange time zoneTimezoneAfrica/Cairo,Africa/Casablanca,Africa/Harare,Africa/Monrovia,Africa/Nairobi,Africa/Tripoli,Africa/Windhoek,America/Araguaina,America/Asuncion,America/Bogota,America/Caracas,America/Chihuahua,America/Cuiaba,America/Denver,America/Fortaleza,America/Guatemala,America/Halifax,America/Manaus,America/Matamoros,America/Monterrey,America/Montevideo,America/Phoenix,America/Santiago,America/Tijuana,Asia/Amman,Asia/Ashgabat,Asia/Baghdad,Asia/Baku,Asia/Bangkok,Asia/Beirut,Asia/Calcutta,Asia/Damascus,Asia/Dhaka,Asia/Irkutsk,Asia/Jerusalem,Asia/Kabul,Asia/Karachi,Asia/Kathmandu,Asia/Krasnoyarsk,Asia/Magadan,Asia/Muscat,Asia/Novosibirsk,Asia/Riyadh,Asia/Seoul,Asia/Shanghai,Asia/Singapore,Asia/Taipei,Asia/Tehran,Asia/Tokyo,Asia/Ulaanbaatar,Asia/Vladivostok,Asia/Yakutsk,Asia/Yerevan,Atlantic/Azores,Australia/Adelaide,Australia/Brisbane,Australia/Darwin,Australia/Hobart,Australia/Perth,Australia/Sydney,Brazil/East,Canada/Newfoundland,Canada/Saskatchewan,Europe/Amsterdam,Europe/Athens,Europe/Dublin,Europe/Helsinki,Europe/Istanbul,Europe/Kaliningrad,Europe/Moscow,Europe/Paris,Europe/Prague,Europe/Sarajevo,Pacific/Auckland,Pacific/Fiji,Pacific/Guam,Pacific/Honolulu,Pacific/Samoa,US/Alaska,US/Central,US/Eastern,US/East-Indiana,US/Pacific,UTCDYNAMICUTCTrueSpecifies the timezone the user wants to change the system time toTIME_ZONEoracle-ee\n \n 11.2Oracle XMLDB RepositoryXMLDBoracle-ee\n \n \n \n \n 457f7bb8-9fbf-11e4-9084-5754f80d5144\n \n', "all": '\n \n \n \n 11.2XMLDBOracle Application Express Runtime EnvironmentAPEXoracle-ee\n \n 11.2APEXOracle Application Express Development EnvironmentAPEX-DEVoracle-ee\n \n 11.2Oracle Advanced Security - Native Network EncryptionNATIVE_NETWORK_ENCRYPTIONACCEPTED,REJECTED,REQUESTED,REQUIREDSTATICREQUESTEDTrueSpecifies the desired encryption behaviorSQLNET.ENCRYPTION_SERVERACCEPTED,REJECTED,REQUESTED,REQUIREDSTATICREQUESTEDTrueSpecifies the desired data integrity behaviorSQLNET.CRYPTO_CHECKSUM_SERVERRC4_256,AES256,AES192,3DES168,RC4_128,AES128,3DES112,RC4_56,DES,RC4_40,DES40STATICRC4_256,AES256,AES192,3DES168,RC4_128,AES128,3DES112,RC4_56,DES,RC4_40,DES40TrueSpecifies list of encryption algorithms in order of intended useSQLNET.ENCRYPTION_TYPES_SERVERSHA1,MD5STATICSHA1,MD5TrueSpecifies list of checksumming algorithms in order of intended useSQLNET.CRYPTO_CHECKSUM_TYPES_SERVERoracle-ee\n \n 11.21158TrueOracle Enterprise Manager (Database Control only)OEMoracle-ee\n \n 11.2Oracle StatspackSTATSPACKoracle-ee\n \n 11.2TrueTrueOracle Advanced Security - Transparent Data EncryptionTDEoracle-ee\n \n 11.2TrueTrueOracle Advanced Security - TDE with HSMTDE_HSMoracle-ee\n \n 11.2TrueTrueChange time zoneTimezoneAfrica/Cairo,Africa/Casablanca,Africa/Harare,Africa/Monrovia,Africa/Nairobi,Africa/Tripoli,Africa/Windhoek,America/Araguaina,America/Asuncion,America/Bogota,America/Caracas,America/Chihuahua,America/Cuiaba,America/Denver,America/Fortaleza,America/Guatemala,America/Halifax,America/Manaus,America/Matamoros,America/Monterrey,America/Montevideo,America/Phoenix,America/Santiago,America/Tijuana,Asia/Amman,Asia/Ashgabat,Asia/Baghdad,Asia/Baku,Asia/Bangkok,Asia/Beirut,Asia/Calcutta,Asia/Damascus,Asia/Dhaka,Asia/Irkutsk,Asia/Jerusalem,Asia/Kabul,Asia/Karachi,Asia/Kathmandu,Asia/Krasnoyarsk,Asia/Magadan,Asia/Muscat,Asia/Novosibirsk,Asia/Riyadh,Asia/Seoul,Asia/Shanghai,Asia/Singapore,Asia/Taipei,Asia/Tehran,Asia/Tokyo,Asia/Ulaanbaatar,Asia/Vladivostok,Asia/Yakutsk,Asia/Yerevan,Atlantic/Azores,Australia/Adelaide,Australia/Brisbane,Australia/Darwin,Australia/Hobart,Australia/Perth,Australia/Sydney,Brazil/East,Canada/Newfoundland,Canada/Saskatchewan,Europe/Amsterdam,Europe/Athens,Europe/Dublin,Europe/Helsinki,Europe/Istanbul,Europe/Kaliningrad,Europe/Moscow,Europe/Paris,Europe/Prague,Europe/Sarajevo,Pacific/Auckland,Pacific/Fiji,Pacific/Guam,Pacific/Honolulu,Pacific/Samoa,US/Alaska,US/Central,US/Eastern,US/East-Indiana,US/Pacific,UTCDYNAMICUTCTrueSpecifies the timezone the user wants to change the system time toTIME_ZONEoracle-ee\n \n 11.2Oracle XMLDB RepositoryXMLDBoracle-ee\n \n \n \n \n 457f7bb8-9fbf-11e4-9084-5754f80d5144\n \n', }, "sqlserver-ee": { "10.50": '\n \n \n \n 10.50SQLServer Database MirroringMirroringsqlserver-ee\n \n 10.50TrueSQL Server - Transparent Data EncryptionTDEsqlserver-ee\n \n \n \n \n 457f7bb8-9fbf-11e4-9084-5754f80d5144\n \n', "11.00": '\n \n \n \n 11.00SQLServer Database MirroringMirroringsqlserver-ee\n \n 11.00TrueSQL Server - Transparent Data EncryptionTDEsqlserver-ee\n \n \n \n \n 457f7bb8-9fbf-11e4-9084-5754f80d5144\n \n', "all": '\n \n \n \n 10.50SQLServer Database MirroringMirroringsqlserver-ee\n \n 10.50TrueSQL Server - Transparent Data EncryptionTDEsqlserver-ee\n \n 11.00SQLServer Database MirroringMirroringsqlserver-ee\n \n 11.00TrueSQL Server - Transparent Data EncryptionTDEsqlserver-ee\n \n \n \n \n 457f7bb8-9fbf-11e4-9084-5754f80d5144\n \n', }, } if engine_name not in default_option_group_options: raise RDSClientError( "InvalidParameterValue", "Invalid DB engine: {0}".format(engine_name) ) if ( major_engine_version and major_engine_version not in default_option_group_options[engine_name] ): raise RDSClientError( "InvalidParameterCombination", "Cannot find major version {0} for {1}".format( major_engine_version, engine_name ), ) if major_engine_version: return default_option_group_options[engine_name][major_engine_version] return default_option_group_options[engine_name]["all"] def modify_option_group( self, option_group_name, options_to_include=None, options_to_remove=None, apply_immediately=None, ): if option_group_name not in self.option_groups: raise OptionGroupNotFoundFaultError(option_group_name) if not options_to_include and not options_to_remove: raise RDSClientError( "InvalidParameterValue", "At least one option must be added, modified, or removed.", ) if options_to_remove: self.option_groups[option_group_name].remove_options(options_to_remove) if options_to_include: self.option_groups[option_group_name].add_options(options_to_include) return self.option_groups[option_group_name] def create_db_parameter_group(self, db_parameter_group_kwargs): db_parameter_group_id = db_parameter_group_kwargs["name"] if db_parameter_group_kwargs["name"] in self.db_parameter_groups: raise RDSClientError( "DBParameterGroupAlreadyExistsFault", "A DB parameter group named {0} already exists.".format( db_parameter_group_kwargs["name"] ), ) if not db_parameter_group_kwargs.get("description"): raise RDSClientError( "InvalidParameterValue", "The parameter Description must be provided and must not be blank.", ) if not db_parameter_group_kwargs.get("family"): raise RDSClientError( "InvalidParameterValue", "The parameter DBParameterGroupName must be provided and must not be blank.", ) db_parameter_group = DBParameterGroup(**db_parameter_group_kwargs) self.db_parameter_groups[db_parameter_group_id] = db_parameter_group return db_parameter_group def describe_db_parameter_groups(self, db_parameter_group_kwargs): db_parameter_group_list = [] if db_parameter_group_kwargs.get("marker"): marker = db_parameter_group_kwargs["marker"] else: marker = 0 if db_parameter_group_kwargs.get("max_records"): if ( db_parameter_group_kwargs["max_records"] < 20 or db_parameter_group_kwargs["max_records"] > 100 ): raise RDSClientError( "InvalidParameterValue", "Invalid value for max records. Must be between 20 and 100", ) max_records = db_parameter_group_kwargs["max_records"] else: max_records = 100 for ( db_parameter_group_name, db_parameter_group, ) in self.db_parameter_groups.items(): if not db_parameter_group_kwargs.get( "name" ) or db_parameter_group.name == db_parameter_group_kwargs.get("name"): db_parameter_group_list.append(db_parameter_group) else: continue return db_parameter_group_list[marker : max_records + marker] def modify_db_parameter_group( self, db_parameter_group_name, db_parameter_group_parameters ): if db_parameter_group_name not in self.db_parameter_groups: raise DBParameterGroupNotFoundError(db_parameter_group_name) db_parameter_group = self.db_parameter_groups[db_parameter_group_name] db_parameter_group.update_parameters(db_parameter_group_parameters) return db_parameter_group def list_tags_for_resource(self, arn): if self.arn_regex.match(arn): arn_breakdown = arn.split(":") resource_type = arn_breakdown[len(arn_breakdown) - 2] resource_name = arn_breakdown[len(arn_breakdown) - 1] if resource_type == "db": # Database if resource_name in self.databases: return self.databases[resource_name].get_tags() elif resource_type == "es": # Event Subscription # TODO: Complete call to tags on resource type Event # Subscription return [] elif resource_type == "og": # Option Group if resource_name in self.option_groups: return self.option_groups[resource_name].get_tags() elif resource_type == "pg": # Parameter Group if resource_name in self.db_parameter_groups: return self.db_parameter_groups[resource_name].get_tags() elif resource_type == "ri": # Reserved DB instance # TODO: Complete call to tags on resource type Reserved DB # instance return [] elif resource_type == "secgrp": # DB security group if resource_name in self.security_groups: return self.security_groups[resource_name].get_tags() elif resource_type == "snapshot": # DB Snapshot if resource_name in self.snapshots: return self.snapshots[resource_name].get_tags() elif resource_type == "subgrp": # DB subnet group if resource_name in self.subnet_groups: return self.subnet_groups[resource_name].get_tags() else: raise RDSClientError( "InvalidParameterValue", "Invalid resource name: {0}".format(arn) ) return [] def remove_tags_from_resource(self, arn, tag_keys): if self.arn_regex.match(arn): arn_breakdown = arn.split(":") resource_type = arn_breakdown[len(arn_breakdown) - 2] resource_name = arn_breakdown[len(arn_breakdown) - 1] if resource_type == "db": # Database if resource_name in self.databases: self.databases[resource_name].remove_tags(tag_keys) elif resource_type == "es": # Event Subscription return None elif resource_type == "og": # Option Group if resource_name in self.option_groups: return self.option_groups[resource_name].remove_tags(tag_keys) elif resource_type == "pg": # Parameter Group return None elif resource_type == "ri": # Reserved DB instance return None elif resource_type == "secgrp": # DB security group if resource_name in self.security_groups: return self.security_groups[resource_name].remove_tags(tag_keys) elif resource_type == "snapshot": # DB Snapshot if resource_name in self.snapshots: return self.snapshots[resource_name].remove_tags(tag_keys) elif resource_type == "subgrp": # DB subnet group if resource_name in self.subnet_groups: return self.subnet_groups[resource_name].remove_tags(tag_keys) else: raise RDSClientError( "InvalidParameterValue", "Invalid resource name: {0}".format(arn) ) def add_tags_to_resource(self, arn, tags): if self.arn_regex.match(arn): arn_breakdown = arn.split(":") resource_type = arn_breakdown[len(arn_breakdown) - 2] resource_name = arn_breakdown[len(arn_breakdown) - 1] if resource_type == "db": # Database if resource_name in self.databases: return self.databases[resource_name].add_tags(tags) elif resource_type == "es": # Event Subscription return [] elif resource_type == "og": # Option Group if resource_name in self.option_groups: return self.option_groups[resource_name].add_tags(tags) elif resource_type == "pg": # Parameter Group return [] elif resource_type == "ri": # Reserved DB instance return [] elif resource_type == "secgrp": # DB security group if resource_name in self.security_groups: return self.security_groups[resource_name].add_tags(tags) elif resource_type == "snapshot": # DB Snapshot if resource_name in self.snapshots: return self.snapshots[resource_name].add_tags(tags) elif resource_type == "subgrp": # DB subnet group if resource_name in self.subnet_groups: return self.subnet_groups[resource_name].add_tags(tags) else: raise RDSClientError( "InvalidParameterValue", "Invalid resource name: {0}".format(arn) ) class OptionGroup(object): def __init__(self, name, engine_name, major_engine_version, description=None): self.engine_name = engine_name self.major_engine_version = major_engine_version self.description = description self.name = name self.vpc_and_non_vpc_instance_memberships = False self.options = {} self.vpcId = "null" self.tags = [] def to_json(self): template = Template( """{ "VpcId": null, "MajorEngineVersion": "{{ option_group.major_engine_version }}", "OptionGroupDescription": "{{ option_group.description }}", "AllowsVpcAndNonVpcInstanceMemberships": "{{ option_group.vpc_and_non_vpc_instance_memberships }}", "EngineName": "{{ option_group.engine_name }}", "Options": [], "OptionGroupName": "{{ option_group.name }}" }""" ) return template.render(option_group=self) def to_xml(self): template = Template( """ {{ option_group.name }} {{ option_group.vpc_and_non_vpc_instance_memberships }} {{ option_group.major_engine_version }} {{ option_group.engine_name }} {{ option_group.description }} """ ) return template.render(option_group=self) def remove_options(self, options_to_remove): # TODO: Check for option in self.options and remove if exists. Raise # error otherwise return def add_options(self, options_to_add): # TODO: Validate option and add it to self.options. If invalid raise # error return def get_tags(self): return self.tags def add_tags(self, tags): new_keys = [tag_set["Key"] for tag_set in tags] self.tags = [tag_set for tag_set in self.tags if tag_set["Key"] not in new_keys] self.tags.extend(tags) return self.tags def remove_tags(self, tag_keys): self.tags = [tag_set for tag_set in self.tags if tag_set["Key"] not in tag_keys] class OptionGroupOption(object): def __init__(self, **kwargs): self.default_port = kwargs.get("default_port") self.description = kwargs.get("description") self.engine_name = kwargs.get("engine_name") self.major_engine_version = kwargs.get("major_engine_version") self.name = kwargs.get("name") self.option_group_option_settings = self._make_option_group_option_settings( kwargs.get("option_group_option_settings", []) ) self.options_depended_on = kwargs.get("options_depended_on", []) self.permanent = kwargs.get("permanent") self.persistent = kwargs.get("persistent") self.port_required = kwargs.get("port_required") def _make_option_group_option_settings(self, option_group_option_settings_kwargs): return [ OptionGroupOptionSetting(**setting_kwargs) for setting_kwargs in option_group_option_settings_kwargs ] def to_json(self): template = Template( """{ "MinimumRequiredMinorEngineVersion": "2789.0.v1", "OptionsDependedOn": [], "MajorEngineVersion": "10.50", "Persistent": false, "DefaultPort": null, "Permanent": false, "OptionGroupOptionSettings": [], "EngineName": "sqlserver-se", "Name": "Mirroring", "PortRequired": false, "Description": "SQLServer Database Mirroring" }""" ) return template.render(option_group=self) def to_xml(self): template = Template( """ {{ option_group.major_engine_version }} {{ option_group.default_port }} {{ option_group.port_required }} {{ option_group.persistent }} {%- for option_name in option_group.options_depended_on -%} {{ option_name }} {%- endfor -%} {{ option_group.permanent }} {{ option_group.description }} {{ option_group.name }} {%- for setting in option_group.option_group_option_settings -%} {{ setting.to_xml() }} {%- endfor -%} {{ option_group.engine_name }} {{ option_group.minimum_required_minor_engine_version }} """ ) return template.render(option_group=self) class OptionGroupOptionSetting(object): def __init__(self, *kwargs): self.allowed_values = kwargs.get("allowed_values") self.apply_type = kwargs.get("apply_type") self.default_value = kwargs.get("default_value") self.is_modifiable = kwargs.get("is_modifiable") self.setting_description = kwargs.get("setting_description") self.setting_name = kwargs.get("setting_name") def to_xml(self): template = Template( """ {{ option_group_option_setting.allowed_values }} {{ option_group_option_setting.apply_type }} {{ option_group_option_setting.default_value }} {{ option_group_option_setting.is_modifiable }} {{ option_group_option_setting.setting_description }} {{ option_group_option_setting.setting_name }} """ ) return template.render(option_group_option_setting=self) class DBParameterGroup(object): def __init__(self, name, description, family, tags): self.name = name self.description = description self.family = family self.tags = tags self.parameters = defaultdict(dict) def to_xml(self): template = Template( """ {{ param_group.name }} {{ param_group.family }} {{ param_group.description }} """ ) return template.render(param_group=self) def get_tags(self): return self.tags def add_tags(self, tags): new_keys = [tag_set["Key"] for tag_set in tags] self.tags = [tag_set for tag_set in self.tags if tag_set["Key"] not in new_keys] self.tags.extend(tags) return self.tags def remove_tags(self, tag_keys): self.tags = [tag_set for tag_set in self.tags if tag_set["Key"] not in tag_keys] def update_parameters(self, new_parameters): for new_parameter in new_parameters: parameter = self.parameters[new_parameter["ParameterName"]] parameter.update(new_parameter) def delete(self, region_name): backend = rds2_backends[region_name] backend.delete_db_parameter_group(self.name) @classmethod def create_from_cloudformation_json( cls, resource_name, cloudformation_json, region_name ): properties = cloudformation_json["Properties"] db_parameter_group_kwargs = { "description": properties["Description"], "family": properties["Family"], "name": resource_name.lower(), "tags": properties.get("Tags"), } db_parameter_group_parameters = [] for db_parameter, db_parameter_value in properties.get( "Parameters", {} ).items(): db_parameter_group_parameters.append( {"ParameterName": db_parameter, "ParameterValue": db_parameter_value} ) rds2_backend = rds2_backends[region_name] db_parameter_group = rds2_backend.create_db_parameter_group( db_parameter_group_kwargs ) db_parameter_group.update_parameters(db_parameter_group_parameters) return db_parameter_group rds2_backends = {} for region in Session().get_available_regions("rds"): rds2_backends[region] = RDS2Backend(region) for region in Session().get_available_regions("rds", partition_name="aws-us-gov"): rds2_backends[region] = RDS2Backend(region) for region in Session().get_available_regions("rds", partition_name="aws-cn"): rds2_backends[region] = RDS2Backend(region)