Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: handle overlapping routes (#5187) #5233

Merged
merged 1 commit into from Jun 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions moto/ec2/exceptions.py
Expand Up @@ -239,6 +239,14 @@ def __init__(self, route_table_id, cidr):
)


class RouteAlreadyExistsError(EC2ClientError):
def __init__(self, cidr):
super().__init__(
"RouteAlreadyExists",
"The route identified by {0} already exists".format(cidr),
)


class InvalidInstanceIdError(EC2ClientError):
def __init__(self, instance_id):
if isinstance(instance_id, str):
Expand Down
33 changes: 28 additions & 5 deletions moto/ec2/models/route_tables.py
@@ -1,4 +1,5 @@
import ipaddress

from moto.core import get_account_id, CloudFormationModel
from .core import TaggedEC2Resource
from ..exceptions import (
Expand All @@ -7,6 +8,7 @@
InvalidRouteTableIdError,
InvalidAssociationIdError,
InvalidDestinationCIDRBlockParameterError,
RouteAlreadyExistsError,
)
from ..utils import (
EC2_RESOURCE_TO_PREFIX,
Expand Down Expand Up @@ -323,11 +325,10 @@ def create_route(
elif EC2_RESOURCE_TO_PREFIX["vpc-endpoint"] in gateway_id:
gateway = self.get_vpc_end_point(gateway_id)

try:
if destination_cidr_block:
ipaddress.IPv4Network(str(destination_cidr_block), strict=False)
except ValueError:
raise InvalidDestinationCIDRBlockParameterError(destination_cidr_block)
if destination_cidr_block:
self.__validate_destination_cidr_block(
destination_cidr_block, route_table
)

if nat_gateway_id is not None:
nat_gateway = self.nat_gateways.get(nat_gateway_id)
Expand Down Expand Up @@ -440,3 +441,25 @@ def delete_route(
if not deleted:
raise InvalidRouteError(route_table_id, cidr)
return deleted

def __validate_destination_cidr_block(self, destination_cidr_block, route_table):
"""
Utility function to check the destination CIDR block
Will validate the format and check for overlap with existing routes
"""
try:
ip_v4_network = ipaddress.IPv4Network(
str(destination_cidr_block), strict=False
)
except ValueError:
raise InvalidDestinationCIDRBlockParameterError(destination_cidr_block)

if not route_table.routes:
return
for route in route_table.routes.values():
if not route.destination_cidr_block:
continue
if not route.local and ip_v4_network.overlaps(
ipaddress.IPv4Network(str(route.destination_cidr_block))
):
raise RouteAlreadyExistsError(destination_cidr_block)
50 changes: 50 additions & 0 deletions tests/test_ec2/test_route_tables.py
Expand Up @@ -520,6 +520,56 @@ def get_target_route():
ex.value.response["Error"]["Code"].should.equal("InvalidRouteTableID.NotFound")


@mock_ec2
def test_routes_already_exist():
client = boto3.client("ec2", region_name="us-east-1")
ec2 = boto3.resource("ec2", region_name="us-east-1")
vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")

main_route_table_id = client.describe_route_tables(
Filters=[
{"Name": "vpc-id", "Values": [vpc.id]},
{"Name": "association.main", "Values": ["true"]},
]
)["RouteTables"][0]["RouteTableId"]
main_route_table = ec2.RouteTable(main_route_table_id)
ROUTE_CIDR = "10.0.0.0/23"
ROUTE_SUB_CIDR = "10.0.0.0/24"
ROUTE_NO_CONFLICT_CIDR = "10.0.2.0/24"

# Various route targets
igw = ec2.create_internet_gateway()

# Create initial route
main_route_table.create_route(DestinationCidrBlock=ROUTE_CIDR, GatewayId=igw.id)
main_route_table.create_route(
DestinationCidrBlock=ROUTE_NO_CONFLICT_CIDR, GatewayId=igw.id
)

# Create
with pytest.raises(ClientError) as ex:
client.create_route(
RouteTableId=main_route_table.id,
DestinationCidrBlock=ROUTE_CIDR,
GatewayId=igw.id,
)

ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.value.response["ResponseMetadata"].should.have.key("RequestId")
ex.value.response["Error"]["Code"].should.equal("RouteAlreadyExists")

with pytest.raises(ClientError) as ex:
client.create_route(
RouteTableId=main_route_table.id,
DestinationCidrBlock=ROUTE_SUB_CIDR,
GatewayId=igw.id,
)

ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.value.response["ResponseMetadata"].should.have.key("RequestId")
ex.value.response["Error"]["Code"].should.equal("RouteAlreadyExists")


@mock_ec2
def test_routes_not_supported():
client = boto3.client("ec2", region_name="us-east-1")
Expand Down