diff --git a/moto/rds/exceptions.py b/moto/rds/exceptions.py index cc78be6d0..678d1a78c 100644 --- a/moto/rds/exceptions.py +++ b/moto/rds/exceptions.py @@ -218,3 +218,35 @@ class InvalidDBInstanceEngine(InvalidParameterCombination): f"The engine name requested for your DB instance ({instance_engine}) doesn't match " f"the engine name of your DB cluster ({cluster_engine})." ) + + +class InvalidSubnet(RDSClientError): + def __init__(self, subnet_identifier: str): + super().__init__( + "InvalidSubnet", + f"The requested subnet {subnet_identifier} is invalid, or multiple subnets were requested that are not all in a common VPC.", + ) + + +class DBProxyAlreadyExistsFault(RDSClientError): + def __init__(self, db_proxy_identifier: str): + super().__init__( + "DBProxyAlreadyExistsFault", + f"Cannot create the DBProxy because a DBProxy with the identifier {db_proxy_identifier} already exists.", + ) + + +class DBProxyQuotaExceededFault(RDSClientError): + def __init__(self) -> None: + super().__init__( + "DBProxyQuotaExceeded", + "The request cannot be processed because it would exceed the maximum number of DBProxies.", + ) + + +class DBProxyNotFoundFault(RDSClientError): + def __init__(self, db_proxy_identifier: str): + super().__init__( + "DBProxyNotFoundFault", + f"The specified proxy name {db_proxy_identifier} doesn't correspond to a proxy owned by your Amazon Web Services account in the specified Amazon Web Services Region.", + ) diff --git a/moto/rds/models.py b/moto/rds/models.py index 460360be5..18ffab32f 100644 --- a/moto/rds/models.py +++ b/moto/rds/models.py @@ -24,6 +24,9 @@ from .exceptions import ( DBClusterToBeDeletedHasActiveMembers, DBInstanceNotFoundError, DBParameterGroupNotFoundError, + DBProxyAlreadyExistsFault, + DBProxyNotFoundFault, + DBProxyQuotaExceededFault, DBSecurityGroupNotFoundError, DBSnapshotAlreadyExistsError, DBSnapshotNotFoundError, @@ -39,6 +42,7 @@ from .exceptions import ( InvalidGlobalClusterStateFault, InvalidParameterCombination, InvalidParameterValue, + InvalidSubnet, OptionGroupNotFoundFaultError, RDSClientError, SnapshotQuotaExceededError, @@ -1573,11 +1577,128 @@ class SubnetGroup(CloudFormationModel): backend.delete_subnet_group(self.subnet_name) +class DBProxy(BaseModel): + def __init__( + self, + db_proxy_name: str, + engine_family: str, + auth: List[Dict[str, str]], + role_arn: str, + vpc_subnet_ids: List[str], + region_name: str, + account_id: str, + vpc_security_group_ids: Optional[List[str]], + require_tls: Optional[bool] = False, + idle_client_timeout: Optional[int] = 1800, + debug_logging: Optional[bool] = False, + tags: Optional[List[Dict[str, str]]] = None, + ): + self.db_proxy_name = db_proxy_name + self.engine_family = engine_family + if self.engine_family not in ["MYSQL", "POSTGRESQ", "SQLSERVER"]: + raise InvalidParameterValue("Provided EngineFamily is not valid.") + self.auth = auth + self.role_arn = role_arn + self.vpc_subnet_ids = vpc_subnet_ids + self.vpc_security_group_ids = vpc_security_group_ids + self.require_tls = require_tls + if idle_client_timeout is None: + self.idle_client_timeout = 1800 + else: + if int(idle_client_timeout) < 1: + self.idle_client_timeout = 1 + elif int(idle_client_timeout) > 28800: + self.idle_client_timeout = 28800 + else: + self.idle_client_timeout = idle_client_timeout + self.debug_logging = debug_logging + self.created_date = iso_8601_datetime_with_milliseconds() + self.updated_date = iso_8601_datetime_with_milliseconds() + if tags is None: + self.tags = [] + else: + self.tags = tags + self.region_name = region_name + self.account_id = account_id + self.db_proxy_arn = f"arn:aws:rds:{self.region_name}:{self.account_id}:db-proxy:{self.db_proxy_name}" + self.arn = self.db_proxy_arn + ec2_backend = ec2_backends[self.account_id][self.region_name] + subnets = ec2_backend.describe_subnets(subnet_ids=self.vpc_subnet_ids) + vpcs = [] + for subnet in subnets: + vpcs.append(subnet.vpc_id) + if subnet.vpc_id != vpcs[0]: + raise InvalidSubnet(subnet_identifier=subnet.id) + + self.vpc_id = ec2_backend.describe_subnets(subnet_ids=[self.vpc_subnet_ids[0]])[ + 0 + ].vpc_id + self.status = "availible" + self.url_identifier = "".join( + random.choice(string.ascii_lowercase + string.digits) for _ in range(12) + ) + self.endpoint = f"{self.db_proxy_name}.db-proxy-{self.url_identifier}.{self.region_name}.rds.amazonaws.com" + + def get_tags(self) -> List[Dict[str, str]]: + return self.tags + + def add_tags(self, tags: List[Dict[str, str]]) -> List[Dict[str, str]]: + 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: List[str]) -> None: + self.tags = [tag_set for tag_set in self.tags if tag_set["Key"] not in tag_keys] + + def to_xml(self) -> str: + template = Template( + """ + {{ dbproxy.require_tls }} + + {% if dbproxy.VpcSecurityGroupIds %} + {% for vpcsecuritygroupid in dbproxy.VpcSecurityGroupIds %} + {{ vpcsecuritygroupid }} + {% endfor %} + {% endif %} + + + {% for auth in dbproxy.auth %} + + {{ auth["UserName"] }} + {{ auth["AuthScheme"] }} + {{ auth["SecretArn"] }} + {{ auth["IAMAuth"] }} + {{ auth["ClientPasswordAuthType"] }} + + {% endfor %} + + {{ dbproxy.engine_family }} + {{ dbproxy.updated_date }} + {{ dbproxy.db_proxy_name }} + {{ dbproxy.idle_client_timeout }} + {{ dbproxy.endpoint }} + {{ dbproxy.created_date }} + {{ dbproxy.role_arn }} + {{ dbproxy.debug_logging }} + {{ dbproxy.vpc_id }} + {{ dbproxy.db_proxy_arn }} + + {% for vpcsubnetid in dbproxy.vpc_subnet_ids %} + {{ vpcsubnetid }} + {% endfor %} + + {{ dbproxy.status }} + """ + ) + return template.render(dbproxy=self) + + class RDSBackend(BaseBackend): def __init__(self, region_name: str, account_id: str): super().__init__(region_name, account_id) self.arn_regex = re_compile( - r"^arn:aws:rds:.*:[0-9]*:(db|cluster|es|og|pg|ri|secgrp|snapshot|cluster-snapshot|subgrp):.*$" + r"^arn:aws:rds:.*:[0-9]*:(db|cluster|es|og|pg|ri|secgrp|snapshot|cluster-snapshot|subgrp|db-proxy):.*$" ) self.clusters: Dict[str, Cluster] = OrderedDict() self.global_clusters: Dict[str, GlobalCluster] = OrderedDict() @@ -1592,6 +1713,7 @@ class RDSBackend(BaseBackend): self.security_groups: Dict[str, SecurityGroup] = {} self.subnet_groups: Dict[str, SubnetGroup] = {} self._db_cluster_options: Optional[List[Dict[str, Any]]] = None + self.db_proxies: Dict[str, DBProxy] = OrderedDict() def reset(self) -> None: self.neptune.reset() @@ -2586,6 +2708,9 @@ class RDSBackend(BaseBackend): elif resource_type == "subgrp": # DB subnet group if resource_name in self.subnet_groups: return self.subnet_groups[resource_name].get_tags() + elif resource_type == "db-proxy": # DB Proxy + if resource_name in self.db_proxies: + return self.db_proxies[resource_name].get_tags() else: raise RDSClientError( "InvalidParameterValue", f"Invalid resource name: {arn}" @@ -2628,6 +2753,9 @@ class RDSBackend(BaseBackend): elif resource_type == "subgrp": # DB subnet group if resource_name in self.subnet_groups: self.subnet_groups[resource_name].remove_tags(tag_keys) + elif resource_type == "db-proxy": # DB Proxy + if resource_name in self.db_proxies: + self.db_proxies[resource_name].remove_tags(tag_keys) else: raise RDSClientError( "InvalidParameterValue", f"Invalid resource name: {arn}" @@ -2669,6 +2797,9 @@ class RDSBackend(BaseBackend): elif resource_type == "subgrp": # DB subnet group if resource_name in self.subnet_groups: return self.subnet_groups[resource_name].add_tags(tags) + elif resource_type == "db-proxy": # DB Proxy + if resource_name in self.db_proxies: + return self.db_proxies[resource_name].add_tags(tags) else: raise RDSClientError( "InvalidParameterValue", f"Invalid resource name: {arn}" @@ -2910,6 +3041,56 @@ class RDSBackend(BaseBackend): ) return snapshot.attributes + def create_db_proxy( + self, + db_proxy_name: str, + engine_family: str, + auth: List[Dict[str, str]], + role_arn: str, + vpc_subnet_ids: List[str], + vpc_security_group_ids: Optional[List[str]], + require_tls: Optional[bool], + idle_client_timeout: Optional[int], + debug_logging: Optional[bool], + tags: Optional[List[Dict[str, str]]], + ) -> DBProxy: + self._validate_db_identifier(db_proxy_name) + if db_proxy_name in self.db_proxies: + raise DBProxyAlreadyExistsFault(db_proxy_name) + if len(self.db_proxies) >= int(os.environ.get("MOTO_RDS_PROXY_LIMIT", "100")): + raise DBProxyQuotaExceededFault() + db_proxy = DBProxy( + db_proxy_name, + engine_family, + auth, + role_arn, + vpc_subnet_ids, + self.region_name, + self.account_id, + vpc_security_group_ids, + require_tls, + idle_client_timeout, + debug_logging, + tags, + ) + self.db_proxies[db_proxy_name] = db_proxy + return db_proxy + + def describe_db_proxies( + self, + db_proxy_name: Optional[str], + filters: Optional[List[Dict[str, Any]]] = None, + ) -> List[DBProxy]: + """ + The filters-argument is not yet supported + """ + db_proxies = list(self.db_proxies.values()) + if db_proxy_name and db_proxy_name in self.db_proxies.keys(): + db_proxies = [self.db_proxies[db_proxy_name]] + if db_proxy_name and db_proxy_name not in self.db_proxies.keys(): + raise DBProxyNotFoundFault(db_proxy_name) + return db_proxies + class OptionGroup: def __init__( diff --git a/moto/rds/responses.py b/moto/rds/responses.py index e3dbbe1cc..29cc66509 100644 --- a/moto/rds/responses.py +++ b/moto/rds/responses.py @@ -882,6 +882,46 @@ class RDSResponse(BaseResponse): db_cluster_snapshot_identifier=db_cluster_snapshot_identifier, ) + def describe_db_proxies(self) -> str: + params = self._get_params() + db_proxy_name = params.get("DBProxyName") + # filters = params.get("Filters") + marker = params.get("Marker") + db_proxies = self.backend.describe_db_proxies( + db_proxy_name=db_proxy_name, + # filters=filters, + ) + template = self.response_template(DESCRIBE_DB_PROXIES_TEMPLATE) + rendered = template.render(dbproxies=db_proxies, marker=marker) + return rendered + + def create_db_proxy(self) -> str: + params = self._get_params() + db_proxy_name = params["DBProxyName"] + engine_family = params["EngineFamily"] + auth = params["Auth"] + role_arn = params["RoleArn"] + vpc_subnet_ids = params["VpcSubnetIds"] + vpc_security_group_ids = params.get("VpcSecurityGroupIds") + require_tls = params.get("RequireTLS") + idle_client_timeout = params.get("IdleClientTimeout") + debug_logging = params.get("DebugLogging") + tags = self.unpack_list_params("Tags", "Tag") + db_proxy = self.backend.create_db_proxy( + db_proxy_name=db_proxy_name, + engine_family=engine_family, + auth=auth, + role_arn=role_arn, + vpc_subnet_ids=vpc_subnet_ids, + vpc_security_group_ids=vpc_security_group_ids, + require_tls=require_tls, + idle_client_timeout=idle_client_timeout, + debug_logging=debug_logging, + tags=tags, + ) + template = self.response_template(CREATE_DB_PROXY_TEMPLATE) + return template.render(dbproxy=db_proxy) + CREATE_DATABASE_TEMPLATE = """ @@ -1630,3 +1670,30 @@ DESCRIBE_DB_CLUSTER_SNAPSHOT_ATTRIBUTES_TEMPLATE = """1549581b-12b7-11e3-895e-1334a """ + +CREATE_DB_PROXY_TEMPLATE = """ + + + {{ dbproxy.to_xml() }} + + + + 1549581b-12b7-11e3-895e-1334aEXAMPLE + +""" + +DESCRIBE_DB_PROXIES_TEMPLATE = """ + + + {% for dbproxy in dbproxies %} + + {{ dbproxy.to_xml() }} + + {% endfor %} + + + + 1549581b-12b7-11e3-895e-1334a + + +""" diff --git a/moto/resourcegroupstaggingapi/models.py b/moto/resourcegroupstaggingapi/models.py index db2002a96..f54213ceb 100644 --- a/moto/resourcegroupstaggingapi/models.py +++ b/moto/resourcegroupstaggingapi/models.py @@ -456,6 +456,7 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend): "rds:db": self.rds_backend.databases, "rds:snapshot": self.rds_backend.database_snapshots, "rds:cluster-snapshot": self.rds_backend.cluster_snapshots, + "rds:db-proxy": self.rds_backend.db_proxies, } for resource_type, resource_source in resource_map.items(): if ( diff --git a/tests/test_rds/test_rds_proxy.py b/tests/test_rds/test_rds_proxy.py new file mode 100644 index 000000000..314337836 --- /dev/null +++ b/tests/test_rds/test_rds_proxy.py @@ -0,0 +1,266 @@ +import boto3 +import pytest +from botocore.exceptions import ClientError + +from moto import mock_aws +from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID + +DEFAULT_REGION = "us-west-2" + + +@mock_aws +def test_create_db_proxy(): + rds_client = boto3.client("rds", region_name=DEFAULT_REGION) + ec2_client = boto3.client("ec2", region_name=DEFAULT_REGION) + vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"] + subnet_id = ec2_client.create_subnet(CidrBlock="10.0.1.0/24", VpcId=vpc_id)[ + "Subnet" + ]["SubnetId"] + subnet_id_2 = ec2_client.create_subnet(CidrBlock="10.0.2.0/24", VpcId=vpc_id)[ + "Subnet" + ]["SubnetId"] + resp = rds_client.create_db_proxy( + DBProxyName="testrdsproxy", + EngineFamily="MYSQL", + Auth=[ + { + "Description": "Test Description", + "UserName": "Test Username", + "AuthScheme": "SECRETS", + "SecretArn": "TestSecretARN", + "IAMAuth": "ENABLED", + "ClientPasswordAuthType": "MYSQL_NATIVE_PASSWORD", + }, + ], + RoleArn="TestArn", + VpcSubnetIds=[subnet_id, subnet_id_2], + RequireTLS=True, + Tags=[{"Key": "TestKey", "Value": "TestValue"}], + ) + db_proxy = resp["DBProxy"] + assert db_proxy["DBProxyName"] == "testrdsproxy" + assert ( + db_proxy["DBProxyArn"] + == f"arn:aws:rds:us-west-2:{ACCOUNT_ID}:db-proxy:testrdsproxy" + ) + assert db_proxy["Status"] == "availible" + assert db_proxy["EngineFamily"] == "MYSQL" + assert db_proxy["VpcId"] == vpc_id + assert db_proxy["VpcSecurityGroupIds"] == [] + assert db_proxy["VpcSubnetIds"] == [subnet_id, subnet_id_2] + assert db_proxy["Auth"] == [ + { + "UserName": "Test Username", + "AuthScheme": "SECRETS", + "SecretArn": "TestSecretARN", + "IAMAuth": "ENABLED", + "ClientPasswordAuthType": "MYSQL_NATIVE_PASSWORD", + } + ] + assert db_proxy["RoleArn"] == "TestArn" + assert db_proxy["RequireTLS"] is True + assert db_proxy["IdleClientTimeout"] == 1800 + assert db_proxy["DebugLogging"] is False + + +@mock_aws +def test_describe_db_proxies(): + rds_client = boto3.client("rds", region_name=DEFAULT_REGION) + ec2_client = boto3.client("ec2", region_name=DEFAULT_REGION) + vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"] + subnet_id = ec2_client.create_subnet(CidrBlock="10.0.1.0/24", VpcId=vpc_id)[ + "Subnet" + ]["SubnetId"] + subnet_id_2 = ec2_client.create_subnet(CidrBlock="10.0.2.0/24", VpcId=vpc_id)[ + "Subnet" + ]["SubnetId"] + rds_client.create_db_proxy( + DBProxyName="testrdsproxydescribe", + EngineFamily="MYSQL", + Auth=[ + { + "Description": "Test Description", + "UserName": "Test Username", + "AuthScheme": "SECRETS", + "SecretArn": "TestSecretARN", + "IAMAuth": "ENABLED", + "ClientPasswordAuthType": "MYSQL_NATIVE_PASSWORD", + }, + ], + RoleArn="TestArn", + VpcSubnetIds=[subnet_id, subnet_id_2], + RequireTLS=True, + Tags=[ + {"Key": "TestKey", "Value": "TestValue"}, + {"Key": "aaa", "Value": "bbb"}, + ], + ) + response = rds_client.describe_db_proxies(DBProxyName="testrdsproxydescribe") + db_proxy = response["DBProxies"][0] + assert db_proxy["DBProxyName"] == "testrdsproxydescribe" + assert ( + db_proxy["DBProxyArn"] + == f"arn:aws:rds:us-west-2:{ACCOUNT_ID}:db-proxy:testrdsproxydescribe" + ) + assert db_proxy["Status"] == "availible" + assert db_proxy["EngineFamily"] == "MYSQL" + assert db_proxy["VpcId"] == vpc_id + assert db_proxy["VpcSecurityGroupIds"] == [] + assert db_proxy["VpcSubnetIds"] == [subnet_id, subnet_id_2] + assert db_proxy["Auth"] == [ + { + "UserName": "Test Username", + "AuthScheme": "SECRETS", + "SecretArn": "TestSecretARN", + "IAMAuth": "ENABLED", + "ClientPasswordAuthType": "MYSQL_NATIVE_PASSWORD", + } + ] + assert db_proxy["RoleArn"] == "TestArn" + assert db_proxy["RequireTLS"] is True + assert db_proxy["IdleClientTimeout"] == 1800 + assert db_proxy["DebugLogging"] is False + + +@mock_aws +def test_list_tags_db_proxy(): + rds_client = boto3.client("rds", region_name=DEFAULT_REGION) + ec2_client = boto3.client("ec2", region_name=DEFAULT_REGION) + vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"] + subnet_id = ec2_client.create_subnet(CidrBlock="10.0.1.0/24", VpcId=vpc_id)[ + "Subnet" + ]["SubnetId"] + subnet_id_2 = ec2_client.create_subnet(CidrBlock="10.0.2.0/24", VpcId=vpc_id)[ + "Subnet" + ]["SubnetId"] + resp = rds_client.create_db_proxy( + DBProxyName="testrdsproxydescribe", + EngineFamily="MYSQL", + Auth=[ + { + "Description": "Test Description", + "UserName": "Test Username", + "AuthScheme": "SECRETS", + "SecretArn": "TestSecretARN", + "IAMAuth": "ENABLED", + "ClientPasswordAuthType": "MYSQL_NATIVE_PASSWORD", + }, + ], + RoleArn="TestArn", + VpcSubnetIds=[subnet_id, subnet_id_2], + RequireTLS=True, + Tags=[ + {"Key": "TestKey", "Value": "TestValue"}, + {"Key": "aaa", "Value": "bbb"}, + ], + ) + arn = resp["DBProxy"]["DBProxyArn"] + resp = rds_client.list_tags_for_resource(ResourceName=arn) + assert resp["TagList"] == [ + {"Value": "TestValue", "Key": "TestKey"}, + {"Value": "bbb", "Key": "aaa"}, + ] + + +@mock_aws +def test_create_db_proxy_invalid_subnet(): + rds_client = boto3.client("rds", region_name=DEFAULT_REGION) + ec2_client = boto3.client("ec2", region_name=DEFAULT_REGION) + vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"] + vpc_id_2 = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"] + subnet_id = ec2_client.create_subnet(CidrBlock="10.0.1.0/24", VpcId=vpc_id)[ + "Subnet" + ]["SubnetId"] + subnet_id_2 = ec2_client.create_subnet(CidrBlock="10.0.2.0/24", VpcId=vpc_id_2)[ + "Subnet" + ]["SubnetId"] + with pytest.raises(ClientError) as ex: + rds_client.create_db_proxy( + DBProxyName="testrdsproxy", + EngineFamily="MYSQL", + Auth=[ + { + "Description": "Test Description", + "UserName": "Test Username", + "AuthScheme": "SECRETS", + "SecretArn": "TestSecretARN", + "IAMAuth": "ENABLED", + "ClientPasswordAuthType": "MYSQL_NATIVE_PASSWORD", + }, + ], + RoleArn="TestArn", + VpcSubnetIds=[subnet_id, subnet_id_2], + RequireTLS=True, + Tags=[{"Key": "TestKey", "Value": "TestValue"}], + ) + err = ex.value.response["Error"] + assert err["Code"] == "InvalidSubnet" + + +@mock_aws +def test_create_db_proxy_duplicate_name(): + rds_client = boto3.client("rds", region_name=DEFAULT_REGION) + ec2_client = boto3.client("ec2", region_name=DEFAULT_REGION) + vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"] + subnet_id = ec2_client.create_subnet(CidrBlock="10.0.1.0/24", VpcId=vpc_id)[ + "Subnet" + ]["SubnetId"] + subnet_id_2 = ec2_client.create_subnet(CidrBlock="10.0.2.0/24", VpcId=vpc_id)[ + "Subnet" + ]["SubnetId"] + rds_client.create_db_proxy( + DBProxyName="testrdsproxy", + EngineFamily="MYSQL", + Auth=[ + { + "Description": "Test Description", + "UserName": "Test Username", + "AuthScheme": "SECRETS", + "SecretArn": "TestSecretARN", + "IAMAuth": "ENABLED", + "ClientPasswordAuthType": "MYSQL_NATIVE_PASSWORD", + }, + ], + RoleArn="TestArn", + VpcSubnetIds=[subnet_id, subnet_id_2], + RequireTLS=True, + Tags=[{"Key": "TestKey", "Value": "TestValue"}], + ) + with pytest.raises(ClientError) as ex: + rds_client.create_db_proxy( + DBProxyName="testrdsproxy", + EngineFamily="MYSQL", + Auth=[ + { + "Description": "Test Description", + "UserName": "Test Username", + "AuthScheme": "SECRETS", + "SecretArn": "TestSecretARN", + "IAMAuth": "ENABLED", + "ClientPasswordAuthType": "MYSQL_NATIVE_PASSWORD", + }, + ], + RoleArn="TestArn", + VpcSubnetIds=[subnet_id, subnet_id_2], + RequireTLS=True, + Tags=[{"Key": "TestKey", "Value": "TestValue"}], + ) + err = ex.value.response["Error"] + assert err["Code"] == "DBProxyAlreadyExistsFault" + assert ( + err["Message"] + == "Cannot create the DBProxy because a DBProxy with the identifier testrdsproxy already exists." + ) + + +@mock_aws +def test_describe_db_proxies_not_found(): + rds_client = boto3.client("rds", region_name=DEFAULT_REGION) + with pytest.raises(ClientError) as ex: + rds_client.describe_db_proxies(DBProxyName="testrdsproxydescribe") + err = ex.value.response["Error"] + assert err["Code"] == "DBProxyNotFoundFault" + assert ( + err["Message"] + == "The specified proxy name testrdsproxydescribe doesn't correspond to a proxy owned by your Amazon Web Services account in the specified Amazon Web Services Region." + )