feature route53resolver: ip address associate/disassociate (#5254)

This commit is contained in:
Macwan Nevil 2022-06-25 17:02:04 +05:30 committed by GitHub
parent 3abb737579
commit ac07f03d9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 104 additions and 6 deletions

View File

@ -292,6 +292,46 @@ class ResolverEndpoint(BaseModel): # pylint: disable=too-many-instance-attribut
self.name = name
self.modification_time = datetime.now(timezone.utc).isoformat()
def associate_ip_address(self, ip_address):
self.ip_addresses.append(ip_address)
self.ip_address_count = len(self.ip_addresses)
eni_id = f"rni-{get_random_hex(17)}"
self.subnets[ip_address["SubnetId"]][ip_address["Ip"]] = eni_id
eni_info = ec2_backends[self.region].create_network_interface(
description=f"Route 53 Resolver: {self.id}:{eni_id}",
group_ids=self.security_group_ids,
interface_type="interface",
private_ip_address=ip_address.get("Ip"),
private_ip_addresses=[
{"Primary": True, "PrivateIpAddress": ip_address.get("Ip")}
],
subnet=ip_address.get("SubnetId"),
)
self.eni_ids.append(eni_info.id)
def disassociate_ip_address(self, ip_address):
if not ip_address.get("Ip") and ip_address.get("IpId"):
for ip_addr, eni_id in self.subnets[ip_address.get("SubnetId")].items():
if ip_address.get("IpId") == eni_id:
ip_address["Ip"] = ip_addr
if ip_address.get("Ip"):
self.ip_addresses = list(
filter(lambda i: i["Ip"] != ip_address.get("Ip"), self.ip_addresses)
)
if len(self.subnets[ip_address["SubnetId"]]) == 1:
self.subnets.pop(ip_address["SubnetId"])
else:
self.subnets[ip_address["SubnetId"]].pop(ip_address["Ip"])
for eni_id in self.eni_ids:
eni_info = ec2_backends[self.region].get_network_interface(eni_id)
if eni_info.private_ip_address == ip_address.get("Ip"):
ec2_backends[self.region].delete_network_interface(eni_id)
self.eni_ids.remove(eni_id)
self.ip_address_count = len(self.ip_addresses)
class Route53ResolverBackend(BaseBackend):
"""Implementation of Route53Resolver APIs."""
@ -353,15 +393,17 @@ class Route53ResolverBackend(BaseBackend):
return rule_association
@staticmethod
def _verify_subnet_ips(region, ip_addresses):
def _verify_subnet_ips(region, ip_addresses, initial=True):
"""Perform additional checks on the IPAddresses.
NOTE: This does not include IPv6 addresses.
"""
if len(ip_addresses) < 2:
raise InvalidRequestException(
"Resolver endpoint needs to have at least 2 IP addresses"
)
# only initial endpoint creation requires atleast two ip addresses
if initial:
if len(ip_addresses) < 2:
raise InvalidRequestException(
"Resolver endpoint needs to have at least 2 IP addresses"
)
subnets = defaultdict(set)
for subnet_id, ip_addr in [(x["SubnetId"], x["Ip"]) for x in ip_addresses]:
@ -836,5 +878,37 @@ class Route53ResolverBackend(BaseBackend):
resolver_endpoint.update_name(name)
return resolver_endpoint
def associate_resolver_endpoint_ip_address(
self, region, resolver_endpoint_id, ip_address
):
"""associate resolver endpoint ip address."""
self._validate_resolver_endpoint_id(resolver_endpoint_id)
resolver_endpoint = self.resolver_endpoints[resolver_endpoint_id]
if not ip_address.get("Ip"):
subnet_info = ec2_backends[region].get_all_subnets(
subnet_ids=[ip_address.get("SubnetId")]
)[0]
ip_address["Ip"] = subnet_info.get_available_subnet_ip(self)
self._verify_subnet_ips(region, [ip_address], False)
resolver_endpoint.associate_ip_address(ip_address)
return resolver_endpoint
def disassociate_resolver_endpoint_ip_address(
self, resolver_endpoint_id, ip_address
):
"""disassociate resolver endpoint ip address."""
self._validate_resolver_endpoint_id(resolver_endpoint_id)
resolver_endpoint = self.resolver_endpoints[resolver_endpoint_id]
if not (ip_address.get("Ip") or ip_address.get("IpId")):
raise InvalidRequestException(
"[RSLVR-00503] Need to specify either the IP ID or both subnet and IP address in order to remove IP address."
)
resolver_endpoint.disassociate_ip_address(ip_address)
return resolver_endpoint
route53resolver_backends = BackendDict(Route53ResolverBackend, "route53resolver")

View File

@ -257,3 +257,27 @@ class Route53ResolverResponse(BaseResponse):
resolver_endpoint_id=resolver_endpoint_id, name=name
)
return json.dumps({"ResolverEndpoint": resolver_endpoint.description()})
def associate_resolver_endpoint_ip_address(self):
ip_address = self._get_param("IpAddress")
resolver_endpoint_id = self._get_param("ResolverEndpointId")
resolver_endpoint = (
self.route53resolver_backend.associate_resolver_endpoint_ip_address(
region=self.region,
resolver_endpoint_id=resolver_endpoint_id,
ip_address=ip_address,
)
)
return json.dumps({"ResolverEndpoint": resolver_endpoint.description()})
def disassociate_resolver_endpoint_ip_address(self):
ip_address = self._get_param("IpAddress")
resolver_endpoint_id = self._get_param("ResolverEndpointId")
resolver_endpoint = (
self.route53resolver_backend.disassociate_resolver_endpoint_ip_address(
resolver_endpoint_id=resolver_endpoint_id,
ip_address=ip_address,
)
)
return json.dumps({"ResolverEndpoint": resolver_endpoint.description()})

View File

@ -189,7 +189,7 @@ route53:
- TestAccRoute53HealthCheck_withSNI
- TestAccRoute53HealthCheck_disabled
- TestAccRoute53HealthCheck_disappears
- TestAccRoute53ResolverEndpoint_basicInbound
- TestAccRoute53ResolverEndpoint
s3:
- TestAccS3BucketPolicy
- TestAccS3BucketPublicAccessBlock