11import copy
2+ import math
23import os
34import re
45import string
@@ -121,6 +122,71 @@ def arn(self) -> str:
121122 return f"arn:{ self .partition } :rds:{ self .region } :{ self .account_id } :{ self .resource_type } :{ self .name } "
122123
123124
125+ class ProxyTarget (RDSBaseModel ):
126+ resource_type = "proxy-target"
127+
128+ def __init__ (
129+ self ,
130+ backend : "RDSBackend" ,
131+ resource_id : str ,
132+ endpoint : Optional [str ],
133+ type : str ,
134+ ):
135+ super ().__init__ (backend )
136+ self .endpoint = endpoint
137+ self .rds_resource_id = resource_id
138+ self .type = type
139+ self .role = ""
140+
141+
142+ class ProxyTargetGroup (RDSBaseModel ):
143+ resource_type = "target-group"
144+
145+ def __init__ (
146+ self ,
147+ backend : "RDSBackend" ,
148+ name : str ,
149+ proxy_name : str ,
150+ ):
151+ super ().__init__ (backend )
152+ self ._name = f"prx-tg-{ random .get_random_string (length = 17 , lower_case = True )} "
153+ self .group_name = name
154+ self .proxy_name = proxy_name
155+ self .targets : List [ProxyTarget ] = []
156+
157+ self .max_connections = 100
158+ self .max_idle_connections = 50
159+ self .borrow_timeout = 120
160+ self .session_pinning_filters : List [str ] = []
161+
162+ self .created_date = iso_8601_datetime_with_milliseconds ()
163+ self .updated_date = iso_8601_datetime_with_milliseconds ()
164+
165+ @property
166+ def name (self ) -> str :
167+ return self ._name
168+
169+ def to_xml (self ) -> str :
170+ template = Template ("""<DBProxyName>{{ group.proxy_name }}</DBProxyName>
171+ <TargetGroupName>{{ group.group_name }}</TargetGroupName>
172+ <TargetGroupArn>{{ group.arn }}</TargetGroupArn>
173+ <IsDefault>true</IsDefault>
174+ <Status>available</Status>
175+ <ConnectionPoolConfig>
176+ <MaxConnectionsPercent>{{ group.max_connections }}</MaxConnectionsPercent>
177+ <MaxIdleConnectionsPercent>{{ group.max_idle_connections }}</MaxIdleConnectionsPercent>
178+ <ConnectionBorrowTimeout>{{ group.borrow_timeout }}</ConnectionBorrowTimeout>
179+ <SessionPinningFilters>
180+ {% for filter in group.session_pinning_filters %}
181+ <member>{{ filter }}</member>
182+ {% endfor %}
183+ </SessionPinningFilters>
184+ </ConnectionPoolConfig>
185+ <CreatedDate>{{ group.created_date }}</CreatedDate>
186+ <UpdatedDate>{{ group.updated_date }}</UpdatedDate>""" )
187+ return template .render (group = self )
188+
189+
124190class GlobalCluster (RDSBaseModel ):
125191 resource_type = "global-cluster"
126192
@@ -729,7 +795,7 @@ def __init__(
729795 self .instance_create_time = iso_8601_datetime_with_milliseconds ()
730796 self .publicly_accessible = kwargs .get ("publicly_accessible" )
731797 if self .publicly_accessible is None :
732- self .publicly_accessible = True
798+ self .publicly_accessible = False
733799 self .copy_tags_to_snapshot = kwargs .get ("copy_tags_to_snapshot" )
734800 if self .copy_tags_to_snapshot is None :
735801 self .copy_tags_to_snapshot = False
@@ -750,6 +816,11 @@ def __init__(
750816 ].describe_db_subnet_groups (self .db_subnet_group_name )[0 ]
751817 self .security_groups = kwargs .get ("security_groups" , [])
752818 self .vpc_security_group_ids = kwargs .get ("vpc_security_group_ids" , [])
819+ if not self .vpc_security_group_ids :
820+ ec2_backend = ec2_backends [self .account_id ][self .region ]
821+ default_vpc = ec2_backend .default_vpc
822+ default_sg = ec2_backend .get_default_security_group (default_vpc .id )
823+ self .vpc_security_group_ids .append (default_sg .id ) # type: ignore
753824 self .preferred_maintenance_window = kwargs .get ("preferred_maintenance_window" )
754825 self .preferred_backup_window = kwargs .get ("preferred_backup_window" )
755826 msg = valid_preferred_maintenance_window (
@@ -1550,7 +1621,7 @@ def __init__(
15501621 self .auth = auth
15511622 self .role_arn = role_arn
15521623 self .vpc_subnet_ids = vpc_subnet_ids
1553- self .vpc_security_group_ids = vpc_security_group_ids
1624+ self .vpc_security_group_ids = vpc_security_group_ids or []
15541625 self .require_tls = require_tls
15551626 if idle_client_timeout is None :
15561627 self .idle_client_timeout = 1800
@@ -1577,6 +1648,9 @@ def __init__(
15771648 vpcs .append (subnet .vpc_id )
15781649 if subnet .vpc_id != vpcs [0 ]:
15791650 raise InvalidSubnet (subnet_identifier = subnet .id )
1651+ if not self .vpc_security_group_ids :
1652+ default_sg = ec2_backend .get_default_security_group (vpcs [0 ])
1653+ self .vpc_security_group_ids .append (default_sg .id ) # type: ignore
15801654
15811655 self .vpc_id = ec2_backend .describe_subnets (subnet_ids = [self .vpc_subnet_ids [0 ]])[
15821656 0
@@ -1587,18 +1661,30 @@ def __init__(
15871661 )
15881662 self .endpoint = f"{ self .db_proxy_name } .db-proxy-{ self .url_identifier } .{ self .region } .rds.amazonaws.com"
15891663
1664+ self .proxy_target_groups = {
1665+ "default" : ProxyTargetGroup (
1666+ backend = self .backend , name = "default" , proxy_name = db_proxy_name
1667+ )
1668+ }
1669+
1670+ self .unique_id = f"prx-{ random .get_random_string (17 , lower_case = True )} "
1671+
15901672 @property
15911673 def name (self ) -> str :
15921674 return self .db_proxy_name
15931675
1676+ @property
1677+ def arn (self ) -> str :
1678+ return f"arn:{ self .partition } :rds:{ self .region } :{ self .account_id } :{ self .resource_type } :{ self .unique_id } "
1679+
15941680 def to_xml (self ) -> str :
15951681 template = Template (
15961682 """
15971683 <RequireTLS>{{ dbproxy.require_tls }}</RequireTLS>
15981684 <VpcSecurityGroupIds>
1599- {% if dbproxy.VpcSecurityGroupIds %}
1600- {% for vpcsecuritygroupid in dbproxy.VpcSecurityGroupIds %}
1601- <member>{{ vpcsecuritygroupid }}</member>
1685+ {% if dbproxy.vpc_security_group_ids %}
1686+ {% for sg in dbproxy.vpc_security_group_ids %}
1687+ <member>{{ sg }}</member>
16021688 {% endfor %}
16031689 {% endif %}
16041690 </VpcSecurityGroupIds>
@@ -2558,6 +2644,13 @@ def _find_resource(self, resource_type: str, resource_name: str) -> Any:
25582644 if resource_type == getattr (resource_class , "resource_type" , "" ):
25592645 if resource_name in resources : # type: ignore
25602646 return resources [resource_name ] # type: ignore
2647+ # The resource_name is the last part of the ARN
2648+ # Usually that's the name - but for DBProxies, the last part of the ARN is a random identifier
2649+ # So we can't just use the dict-keys - we have to manually check the ARN
2650+ if resource_type == "db-proxy" :
2651+ for resource in self .db_proxies .values ():
2652+ if resource .arn .endswith (resource_name ):
2653+ return resource
25612654
25622655 def list_tags_for_resource (self , arn : str ) -> List [Dict [str , str ]]:
25632656 if self .arn_regex .match (arn ):
@@ -2869,6 +2962,86 @@ def describe_db_proxies(
28692962 raise DBProxyNotFoundFault (db_proxy_name )
28702963 return db_proxies
28712964
2965+ def deregister_db_proxy_targets (
2966+ self ,
2967+ db_proxy_name : str ,
2968+ target_group_name : str ,
2969+ db_cluster_identifiers : List [str ],
2970+ db_instance_identifiers : List [str ],
2971+ ) -> None :
2972+ db_proxy = self .db_proxies [db_proxy_name ]
2973+ target_group = db_proxy .proxy_target_groups [target_group_name or "default" ]
2974+ target_group .targets = [
2975+ t
2976+ for t in target_group .targets
2977+ if t .rds_resource_id not in db_cluster_identifiers
2978+ and t .rds_resource_id not in db_instance_identifiers
2979+ ]
2980+
2981+ def register_db_proxy_targets (
2982+ self ,
2983+ db_proxy_name : str ,
2984+ target_group_name : str ,
2985+ db_cluster_identifiers : List [str ],
2986+ db_instance_identifiers : List [str ],
2987+ ) -> List [ProxyTarget ]:
2988+ db_proxy = self .db_proxies [db_proxy_name ]
2989+ target_group = db_proxy .proxy_target_groups [target_group_name or "default" ]
2990+ new_targets = []
2991+ for cluster_id in db_cluster_identifiers :
2992+ cluster = self .clusters [cluster_id ]
2993+ target = ProxyTarget (
2994+ backend = self ,
2995+ resource_id = cluster_id ,
2996+ endpoint = cluster .endpoint ,
2997+ type = "TRACKED_CLUSTER" ,
2998+ )
2999+ new_targets .append (target )
3000+ for instance_id in db_instance_identifiers :
3001+ target = ProxyTarget (
3002+ backend = self ,
3003+ resource_id = instance_id ,
3004+ endpoint = None ,
3005+ type = "RDS_INSTANCE" ,
3006+ )
3007+ new_targets .append (target )
3008+ target_group .targets .extend (new_targets )
3009+ return new_targets
3010+
3011+ def delete_db_proxy (self , proxy_name : str ) -> DBProxy :
3012+ return self .db_proxies .pop (proxy_name )
3013+
3014+ def describe_db_proxy_targets (self , proxy_name : str ) -> List [ProxyTarget ]:
3015+ proxy = self .db_proxies [proxy_name ]
3016+ target_group = proxy .proxy_target_groups ["default" ]
3017+ return target_group .targets
3018+
3019+ def describe_db_proxy_target_groups (
3020+ self , proxy_name : str
3021+ ) -> List [ProxyTargetGroup ]:
3022+ proxy = self .db_proxies [proxy_name ]
3023+ return list (proxy .proxy_target_groups .values ())
3024+
3025+ def modify_db_proxy_target_group (
3026+ self , proxy_name : str , config : Dict [str , Any ]
3027+ ) -> ProxyTargetGroup :
3028+ proxy = self .db_proxies [proxy_name ]
3029+ target_group = proxy .proxy_target_groups ["default" ]
3030+ if max_connections := config .get ("MaxConnectionsPercent" ):
3031+ target_group .max_connections = max_connections
3032+ if max_idle := config .get ("MaxIdleConnectionsPercent" ):
3033+ target_group .max_idle_connections = max_idle
3034+ else :
3035+ target_group .max_idle_connections = math .floor (
3036+ int (target_group .max_connections ) / 2
3037+ )
3038+ target_group .borrow_timeout = config .get (
3039+ "ConnectionBorrowTimeout" , target_group .borrow_timeout
3040+ )
3041+ if "SessionPinningFilters" in config :
3042+ target_group .session_pinning_filters = config ["SessionPinningFilters" ]
3043+ return target_group
3044+
28723045
28733046class OptionGroup (RDSBaseModel ):
28743047 resource_type = "og"
0 commit comments