Skip to content

Commit eea6b16

Browse files
authored
RDS: Proxy Target Groups (#8237)
1 parent fa4352a commit eea6b16

9 files changed

Lines changed: 685 additions & 32 deletions

File tree

.github/workflows/tests_terraform_examples.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ jobs:
4545
docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:3.10-slim /moto/scripts/ci_moto_server.sh &
4646
python scripts/ci_wait_for_server.py
4747
- name: Run tests
48+
if: ${{ matrix.service != 'rds' }}
4849
run: |
4950
mkdir ~/.aws && touch ~/.aws/credentials && echo -e "[default]\naws_access_key_id = test\naws_secret_access_key = test" > ~/.aws/credentials
5051
cd other_langs/terraform/${{ matrix.service }}
@@ -56,3 +57,11 @@ jobs:
5657
sleep 30
5758
terraform plan -detailed-exitcode
5859
terraform apply -destroy --auto-approve
60+
- name: Run tests
61+
if: ${{ matrix.service == 'rds' }}
62+
run: |
63+
mkdir ~/.aws && touch ~/.aws/credentials && echo -e "[default]\naws_access_key_id = test\naws_secret_access_key = test" > ~/.aws/credentials
64+
cd other_langs/terraform/${{ matrix.service }}
65+
terraform init
66+
terraform apply --auto-approve
67+
terraform apply -destroy --auto-approve

IMPLEMENTATION_COVERAGE.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6460,7 +6460,7 @@
64606460

64616461
## rds
64626462
<details>
6463-
<summary>38% implemented</summary>
6463+
<summary>42% implemented</summary>
64646464

64656465
- [ ] add_role_to_db_cluster
64666466
- [ ] add_role_to_db_instance
@@ -6505,7 +6505,7 @@
65056505
- [X] delete_db_instance
65066506
- [ ] delete_db_instance_automated_backup
65076507
- [X] delete_db_parameter_group
6508-
- [ ] delete_db_proxy
6508+
- [X] delete_db_proxy
65096509
- [ ] delete_db_proxy_endpoint
65106510
- [ ] delete_db_security_group
65116511
- [ ] delete_db_shard_group
@@ -6516,7 +6516,7 @@
65166516
- [ ] delete_integration
65176517
- [X] delete_option_group
65186518
- [ ] delete_tenant_database
6519-
- [ ] deregister_db_proxy_targets
6519+
- [X] deregister_db_proxy_targets
65206520
- [ ] describe_account_attributes
65216521
- [ ] describe_blue_green_deployments
65226522
- [ ] describe_certificates
@@ -6536,8 +6536,8 @@
65366536
- [ ] describe_db_parameters
65376537
- [X] describe_db_proxies
65386538
- [ ] describe_db_proxy_endpoints
6539-
- [ ] describe_db_proxy_target_groups
6540-
- [ ] describe_db_proxy_targets
6539+
- [X] describe_db_proxy_target_groups
6540+
- [X] describe_db_proxy_targets
65416541
- [ ] describe_db_recommendations
65426542
- [ ] describe_db_security_groups
65436543
- [ ] describe_db_shard_groups
@@ -6580,7 +6580,7 @@
65806580
- [X] modify_db_parameter_group
65816581
- [ ] modify_db_proxy
65826582
- [ ] modify_db_proxy_endpoint
6583-
- [ ] modify_db_proxy_target_group
6583+
- [X] modify_db_proxy_target_group
65846584
- [ ] modify_db_recommendation
65856585
- [ ] modify_db_shard_group
65866586
- [ ] modify_db_snapshot
@@ -6597,7 +6597,7 @@
65976597
- [ ] reboot_db_cluster
65986598
- [X] reboot_db_instance
65996599
- [ ] reboot_db_shard_group
6600-
- [ ] register_db_proxy_targets
6600+
- [X] register_db_proxy_targets
66016601
- [X] remove_from_global_cluster
66026602
- [ ] remove_role_from_db_cluster
66036603
- [ ] remove_role_from_db_instance

docs/docs/services/rds.rst

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ rds
5757
- [X] delete_db_instance
5858
- [ ] delete_db_instance_automated_backup
5959
- [X] delete_db_parameter_group
60-
- [ ] delete_db_proxy
60+
- [X] delete_db_proxy
6161
- [ ] delete_db_proxy_endpoint
6262
- [ ] delete_db_security_group
6363
- [ ] delete_db_shard_group
@@ -68,7 +68,7 @@ rds
6868
- [ ] delete_integration
6969
- [X] delete_option_group
7070
- [ ] delete_tenant_database
71-
- [ ] deregister_db_proxy_targets
71+
- [X] deregister_db_proxy_targets
7272
- [ ] describe_account_attributes
7373
- [ ] describe_blue_green_deployments
7474
- [ ] describe_certificates
@@ -92,8 +92,8 @@ rds
9292

9393

9494
- [ ] describe_db_proxy_endpoints
95-
- [ ] describe_db_proxy_target_groups
96-
- [ ] describe_db_proxy_targets
95+
- [X] describe_db_proxy_target_groups
96+
- [X] describe_db_proxy_targets
9797
- [ ] describe_db_recommendations
9898
- [ ] describe_db_security_groups
9999
- [ ] describe_db_shard_groups
@@ -140,7 +140,7 @@ rds
140140
- [X] modify_db_parameter_group
141141
- [ ] modify_db_proxy
142142
- [ ] modify_db_proxy_endpoint
143-
- [ ] modify_db_proxy_target_group
143+
- [X] modify_db_proxy_target_group
144144
- [ ] modify_db_recommendation
145145
- [ ] modify_db_shard_group
146146
- [ ] modify_db_snapshot
@@ -157,7 +157,7 @@ rds
157157
- [ ] reboot_db_cluster
158158
- [X] reboot_db_instance
159159
- [ ] reboot_db_shard_group
160-
- [ ] register_db_proxy_targets
160+
- [X] register_db_proxy_targets
161161
- [X] remove_from_global_cluster
162162
- [ ] remove_role_from_db_cluster
163163
- [ ] remove_role_from_db_instance

moto/rds/models.py

Lines changed: 178 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import copy
2+
import math
23
import os
34
import re
45
import 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+
124190
class 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

28733046
class OptionGroup(RDSBaseModel):
28743047
resource_type = "og"

0 commit comments

Comments
 (0)