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