EC2 - Fix 3 issues with route table associations (#5509)
This commit is contained in:
parent
3118090fdc
commit
3679521d76
@ -25,8 +25,10 @@ class RouteTable(TaggedEC2Resource, CloudFormationModel):
|
||||
self.ec2_backend = ec2_backend
|
||||
self.id = route_table_id
|
||||
self.vpc_id = vpc_id
|
||||
self.main = main
|
||||
self.main_association = random_subnet_association_id()
|
||||
if main:
|
||||
self.main_association_id = random_subnet_association_id()
|
||||
else:
|
||||
self.main_association_id = None
|
||||
self.associations = {}
|
||||
self.routes = {}
|
||||
|
||||
@ -64,7 +66,7 @@ class RouteTable(TaggedEC2Resource, CloudFormationModel):
|
||||
if filter_name == "association.main":
|
||||
# Note: Boto only supports 'true'.
|
||||
# https://github.com/boto/boto/issues/1742
|
||||
if self.main:
|
||||
if self.main_association_id is not None:
|
||||
return "true"
|
||||
else:
|
||||
return "false"
|
||||
@ -75,7 +77,7 @@ class RouteTable(TaggedEC2Resource, CloudFormationModel):
|
||||
elif filter_name == "association.route-table-id":
|
||||
return self.id
|
||||
elif filter_name == "association.route-table-association-id":
|
||||
return self.associations.keys()
|
||||
return self.all_associations_ids
|
||||
elif filter_name == "association.subnet-id":
|
||||
return self.associations.values()
|
||||
elif filter_name == "route.gateway-id":
|
||||
@ -87,6 +89,14 @@ class RouteTable(TaggedEC2Resource, CloudFormationModel):
|
||||
else:
|
||||
return super().get_filter_value(filter_name, "DescribeRouteTables")
|
||||
|
||||
@property
|
||||
def all_associations_ids(self):
|
||||
# NOTE(yoctozepto): Doing an explicit copy to not touch the original.
|
||||
all_associations = set(self.associations)
|
||||
if self.main_association_id is not None:
|
||||
all_associations.add(self.main_association_id)
|
||||
return all_associations
|
||||
|
||||
|
||||
class RouteTableBackend:
|
||||
def __init__(self):
|
||||
@ -186,7 +196,7 @@ class RouteTableBackend:
|
||||
def replace_route_table_association(self, association_id, route_table_id):
|
||||
# Idempotent if association already exists.
|
||||
new_route_table = self.get_route_table(route_table_id)
|
||||
if association_id in new_route_table.associations:
|
||||
if association_id in new_route_table.all_associations_ids:
|
||||
return association_id
|
||||
|
||||
# Find route table which currently has the association, error if none.
|
||||
@ -195,11 +205,19 @@ class RouteTableBackend:
|
||||
)
|
||||
if not route_tables_by_association_id:
|
||||
raise InvalidAssociationIdError(association_id)
|
||||
previous_route_table = route_tables_by_association_id[0]
|
||||
|
||||
# Remove existing association, create new one.
|
||||
previous_route_table = route_tables_by_association_id[0]
|
||||
subnet_id = previous_route_table.associations.pop(association_id, None)
|
||||
return self.associate_route_table(route_table_id, subnet_id)
|
||||
new_association_id = random_subnet_association_id()
|
||||
if previous_route_table.main_association_id == association_id:
|
||||
previous_route_table.main_association_id = None
|
||||
new_route_table.main_association_id = new_association_id
|
||||
else:
|
||||
association_target_id = previous_route_table.associations.pop(
|
||||
association_id
|
||||
)
|
||||
new_route_table.associations[new_association_id] = association_target_id
|
||||
return new_association_id
|
||||
|
||||
|
||||
# TODO: refractor to isloate class methods from backend logic
|
||||
|
@ -251,14 +251,16 @@ DESCRIBE_ROUTE_TABLES_RESPONSE = """
|
||||
{% endfor %}
|
||||
</routeSet>
|
||||
<associationSet>
|
||||
{% if route_table.main_association_id is not none %}
|
||||
<item>
|
||||
<routeTableAssociationId>{{ route_table.main_association }}</routeTableAssociationId>
|
||||
<routeTableAssociationId>{{ route_table.main_association_id }}</routeTableAssociationId>
|
||||
<routeTableId>{{ route_table.id }}</routeTableId>
|
||||
<main>true</main>
|
||||
<associationState>
|
||||
<state>associated</state>
|
||||
</associationState>
|
||||
</item>
|
||||
{% endif %}
|
||||
{% for association_id,subnet_id in route_table.associations.items() %}
|
||||
<item>
|
||||
<routeTableAssociationId>{{ association_id }}</routeTableAssociationId>
|
||||
@ -324,5 +326,8 @@ REPLACE_ROUTE_TABLE_ASSOCIATION_RESPONSE = """
|
||||
<ReplaceRouteTableAssociationResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
|
||||
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
|
||||
<newAssociationId>{{ association_id }}</newAssociationId>
|
||||
<associationState>
|
||||
<state>associated</state>
|
||||
</associationState>
|
||||
</ReplaceRouteTableAssociationResponse>
|
||||
"""
|
||||
|
@ -185,7 +185,7 @@ def test_route_tables_filters_associations():
|
||||
)["RouteTables"]
|
||||
association1_route_tables.should.have.length_of(1)
|
||||
association1_route_tables[0]["RouteTableId"].should.equal(route_table1.id)
|
||||
association1_route_tables[0]["Associations"].should.have.length_of(3)
|
||||
association1_route_tables[0]["Associations"].should.have.length_of(2)
|
||||
|
||||
# Filter by route table ID
|
||||
route_table2_route_tables = client.describe_route_tables(
|
||||
@ -193,7 +193,7 @@ def test_route_tables_filters_associations():
|
||||
)["RouteTables"]
|
||||
route_table2_route_tables.should.have.length_of(1)
|
||||
route_table2_route_tables[0]["RouteTableId"].should.equal(route_table2.id)
|
||||
route_table2_route_tables[0]["Associations"].should.have.length_of(2)
|
||||
route_table2_route_tables[0]["Associations"].should.have.length_of(1)
|
||||
|
||||
# Filter by subnet ID
|
||||
subnet_route_tables = client.describe_route_tables(
|
||||
@ -201,7 +201,7 @@ def test_route_tables_filters_associations():
|
||||
)["RouteTables"]
|
||||
subnet_route_tables.should.have.length_of(1)
|
||||
subnet_route_tables[0]["RouteTableId"].should.equal(route_table1.id)
|
||||
subnet_route_tables[0]["Associations"].should.have.length_of(3)
|
||||
subnet_route_tables[0]["Associations"].should.have.length_of(2)
|
||||
|
||||
|
||||
@mock_ec2
|
||||
@ -215,7 +215,7 @@ def test_route_table_associations():
|
||||
|
||||
# Refresh
|
||||
r = client.describe_route_tables(RouteTableIds=[route_table.id])["RouteTables"][0]
|
||||
r["Associations"].should.have.length_of(1)
|
||||
r["Associations"].should.have.length_of(0)
|
||||
|
||||
# Associate
|
||||
association_id = client.associate_route_table(
|
||||
@ -224,12 +224,12 @@ def test_route_table_associations():
|
||||
|
||||
# Refresh
|
||||
r = client.describe_route_tables(RouteTableIds=[route_table.id])["RouteTables"][0]
|
||||
r["Associations"].should.have.length_of(2)
|
||||
r["Associations"].should.have.length_of(1)
|
||||
|
||||
r["Associations"][1]["RouteTableAssociationId"].should.equal(association_id)
|
||||
r["Associations"][1]["Main"].should.equal(False)
|
||||
r["Associations"][1]["RouteTableId"].should.equal(route_table.id)
|
||||
r["Associations"][1]["SubnetId"].should.equal(subnet.id)
|
||||
r["Associations"][0]["RouteTableAssociationId"].should.equal(association_id)
|
||||
r["Associations"][0]["Main"].should.equal(False)
|
||||
r["Associations"][0]["RouteTableId"].should.equal(route_table.id)
|
||||
r["Associations"][0]["SubnetId"].should.equal(subnet.id)
|
||||
|
||||
# Associate is idempotent
|
||||
association_id_idempotent = client.associate_route_table(
|
||||
@ -247,16 +247,9 @@ def test_route_table_associations():
|
||||
# Disassociate
|
||||
client.disassociate_route_table(AssociationId=association_id)
|
||||
|
||||
# Refresh - The default (main) route should be there
|
||||
# Validate
|
||||
r = client.describe_route_tables(RouteTableIds=[route_table.id])["RouteTables"][0]
|
||||
r["Associations"].should.have.length_of(1)
|
||||
r["Associations"][0].should.have.key("Main").equal(True)
|
||||
r["Associations"][0].should.have.key("RouteTableAssociationId")
|
||||
r["Associations"][0].should.have.key("RouteTableId").equals(route_table.id)
|
||||
r["Associations"][0].should.have.key("AssociationState").equals(
|
||||
{"State": "associated"}
|
||||
)
|
||||
r["Associations"][0].shouldnt.have.key("SubnetId")
|
||||
r["Associations"].should.have.length_of(0)
|
||||
|
||||
# Error: Disassociate with invalid association ID
|
||||
with pytest.raises(ClientError) as ex:
|
||||
@ -301,7 +294,7 @@ def test_route_table_replace_route_table_association():
|
||||
route_table1 = client.describe_route_tables(RouteTableIds=[route_table1_id])[
|
||||
"RouteTables"
|
||||
][0]
|
||||
route_table1["Associations"].should.have.length_of(1)
|
||||
route_table1["Associations"].should.have.length_of(0)
|
||||
|
||||
# Associate
|
||||
association_id1 = client.associate_route_table(
|
||||
@ -317,15 +310,15 @@ def test_route_table_replace_route_table_association():
|
||||
][0]
|
||||
|
||||
# Validate
|
||||
route_table1["Associations"].should.have.length_of(2)
|
||||
route_table2["Associations"].should.have.length_of(1)
|
||||
route_table1["Associations"].should.have.length_of(1)
|
||||
route_table2["Associations"].should.have.length_of(0)
|
||||
|
||||
route_table1["Associations"][1]["RouteTableAssociationId"].should.equal(
|
||||
route_table1["Associations"][0]["RouteTableAssociationId"].should.equal(
|
||||
association_id1
|
||||
)
|
||||
route_table1["Associations"][1]["Main"].should.equal(False)
|
||||
route_table1["Associations"][1]["RouteTableId"].should.equal(route_table1_id)
|
||||
route_table1["Associations"][1]["SubnetId"].should.equal(subnet.id)
|
||||
route_table1["Associations"][0]["Main"].should.equal(False)
|
||||
route_table1["Associations"][0]["RouteTableId"].should.equal(route_table1_id)
|
||||
route_table1["Associations"][0]["SubnetId"].should.equal(subnet.id)
|
||||
|
||||
# Replace Association
|
||||
association_id2 = client.replace_route_table_association(
|
||||
@ -341,15 +334,15 @@ def test_route_table_replace_route_table_association():
|
||||
][0]
|
||||
|
||||
# Validate
|
||||
route_table1["Associations"].should.have.length_of(1)
|
||||
route_table2["Associations"].should.have.length_of(2)
|
||||
route_table1["Associations"].should.have.length_of(0)
|
||||
route_table2["Associations"].should.have.length_of(1)
|
||||
|
||||
route_table2["Associations"][1]["RouteTableAssociationId"].should.equal(
|
||||
route_table2["Associations"][0]["RouteTableAssociationId"].should.equal(
|
||||
association_id2
|
||||
)
|
||||
route_table2["Associations"][1]["Main"].should.equal(False)
|
||||
route_table2["Associations"][1]["RouteTableId"].should.equal(route_table2_id)
|
||||
route_table2["Associations"][1]["SubnetId"].should.equal(subnet.id)
|
||||
route_table2["Associations"][0]["Main"].should.equal(False)
|
||||
route_table2["Associations"][0]["RouteTableId"].should.equal(route_table2_id)
|
||||
route_table2["Associations"][0]["SubnetId"].should.equal(subnet.id)
|
||||
|
||||
# Replace Association is idempotent
|
||||
association_id_idempotent = client.replace_route_table_association(
|
||||
@ -376,6 +369,52 @@ def test_route_table_replace_route_table_association():
|
||||
ex.value.response["Error"]["Code"].should.equal("InvalidRouteTableID.NotFound")
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_route_table_replace_route_table_association_for_main():
|
||||
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")
|
||||
new_route_table_id = ec2.create_route_table(VpcId=vpc.id).id
|
||||
|
||||
# Get main route table details
|
||||
main_route_table = client.describe_route_tables(
|
||||
Filters=[
|
||||
{"Name": "vpc-id", "Values": [vpc.id]},
|
||||
{"Name": "association.main", "Values": ["true"]},
|
||||
]
|
||||
)["RouteTables"][0]
|
||||
main_route_table_id = main_route_table["RouteTableId"]
|
||||
main_route_table_association_id = main_route_table["Associations"][0][
|
||||
"RouteTableAssociationId"
|
||||
]
|
||||
|
||||
# Replace Association
|
||||
new_association = client.replace_route_table_association(
|
||||
AssociationId=main_route_table_association_id, RouteTableId=new_route_table_id
|
||||
)
|
||||
new_association_id = new_association["NewAssociationId"]
|
||||
|
||||
# Validate the format
|
||||
new_association["AssociationState"]["State"].should.equal("associated")
|
||||
|
||||
# Refresh
|
||||
main_route_table = client.describe_route_tables(
|
||||
RouteTableIds=[main_route_table_id]
|
||||
)["RouteTables"][0]
|
||||
new_route_table = client.describe_route_tables(RouteTableIds=[new_route_table_id])[
|
||||
"RouteTables"
|
||||
][0]
|
||||
|
||||
# Validate
|
||||
main_route_table["Associations"].should.have.length_of(0)
|
||||
new_route_table["Associations"].should.have.length_of(1)
|
||||
new_route_table["Associations"][0]["RouteTableAssociationId"].should.equal(
|
||||
new_association_id
|
||||
)
|
||||
new_route_table["Associations"][0]["Main"].should.equal(True)
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_route_table_get_by_tag():
|
||||
ec2 = boto3.resource("ec2", region_name="eu-central-1")
|
||||
@ -950,14 +989,12 @@ def test_associate_route_table_by_gateway():
|
||||
]
|
||||
)["RouteTables"]
|
||||
|
||||
# First assocation is the main
|
||||
verify[0]["Associations"][0]["Main"].should.equal(True)
|
||||
verify[0]["Associations"].should.have.length_of(1)
|
||||
|
||||
# Second is our Gateway
|
||||
verify[0]["Associations"][1]["Main"].should.equal(False)
|
||||
verify[0]["Associations"][1]["GatewayId"].should.equal(igw_id)
|
||||
verify[0]["Associations"][1]["RouteTableAssociationId"].should.equal(assoc_id)
|
||||
verify[0]["Associations"][1].doesnt.have.key("SubnetId")
|
||||
verify[0]["Associations"][0]["Main"].should.equal(False)
|
||||
verify[0]["Associations"][0]["GatewayId"].should.equal(igw_id)
|
||||
verify[0]["Associations"][0]["RouteTableAssociationId"].should.equal(assoc_id)
|
||||
verify[0]["Associations"][0].doesnt.have.key("SubnetId")
|
||||
|
||||
|
||||
@mock_ec2
|
||||
@ -977,11 +1014,9 @@ def test_associate_route_table_by_subnet():
|
||||
]
|
||||
)["RouteTables"]
|
||||
|
||||
# First assocation is the main
|
||||
verify[0]["Associations"][0].should.have.key("Main").equals(True)
|
||||
verify[0]["Associations"].should.have.length_of(1)
|
||||
|
||||
# Second is our Gateway
|
||||
verify[0]["Associations"][1]["Main"].should.equal(False)
|
||||
verify[0]["Associations"][1]["SubnetId"].should.equals(subnet_id)
|
||||
verify[0]["Associations"][1]["RouteTableAssociationId"].should.equal(assoc_id)
|
||||
verify[0]["Associations"][1].doesnt.have.key("GatewayId")
|
||||
verify[0]["Associations"][0]["Main"].should.equal(False)
|
||||
verify[0]["Associations"][0]["SubnetId"].should.equals(subnet_id)
|
||||
verify[0]["Associations"][0]["RouteTableAssociationId"].should.equal(assoc_id)
|
||||
verify[0]["Associations"][0].doesnt.have.key("GatewayId")
|
||||
|
Loading…
Reference in New Issue
Block a user