From db044df0a9a92ffa9bd8bc09e56e80e63b2fe042 Mon Sep 17 00:00:00 2001 From: dreadpirateshawn Date: Tue, 14 Oct 2014 11:23:42 -0700 Subject: [PATCH] Route Tables: Added support for associate/disassociate subnets. (added replace route table association) --- moto/ec2/models.py | 8 ++++ moto/ec2/responses/route_tables.py | 13 +++++- tests/test_ec2/test_route_tables.py | 66 +++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 5579c8744..c9e76a14c 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -1659,6 +1659,14 @@ class RouteTableBackend(object): return route_table.associations.pop(association_id, None) raise InvalidAssociationIdError(association_id) + def replace_route_table_association(self, association_id, route_table_id): + route_tables_by_association_id = ec2_backend.get_all_route_tables(filters={'association.route-table-association-id':[association_id]}) + if not route_tables_by_association_id: + raise InvalidAssociationIdError(association_id) + 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) + class Route(object): def __init__(self, route_table, destination_cidr_block, local=False, diff --git a/moto/ec2/responses/route_tables.py b/moto/ec2/responses/route_tables.py index 58f9db949..4d73bc917 100644 --- a/moto/ec2/responses/route_tables.py +++ b/moto/ec2/responses/route_tables.py @@ -83,7 +83,11 @@ class RouteTables(BaseResponse): return template.render() def replace_route_table_association(self): - raise NotImplementedError('RouteTables(AmazonVPC).replace_route_table_association is not yet implemented') + route_table_id = self.querystring.get('RouteTableId')[0] + association_id = self.querystring.get('AssociationId')[0] + new_association_id = ec2_backend.replace_route_table_association(association_id, route_table_id) + template = Template(REPLACE_ROUTE_TABLE_ASSOCIATION_RESPONSE) + return template.render(association_id=new_association_id) CREATE_ROUTE_RESPONSE = """ @@ -201,3 +205,10 @@ DISASSOCIATE_ROUTE_TABLE_RESPONSE = """ true """ + +REPLACE_ROUTE_TABLE_ASSOCIATION_RESPONSE = """ + + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + {{ association_id }} + +""" diff --git a/tests/test_ec2/test_route_tables.py b/tests/test_ec2/test_route_tables.py index f5ed8caf7..919410c14 100644 --- a/tests/test_ec2/test_route_tables.py +++ b/tests/test_ec2/test_route_tables.py @@ -213,6 +213,72 @@ def test_route_table_associations(): cm.exception.request_id.should_not.be.none +@mock_ec2 +def test_route_table_replace_route_table_association(): + """ + Note: Boto has deprecated replace_route_table_assocation (which returns status) + and now uses replace_route_table_assocation_with_assoc (which returns association ID). + """ + conn = boto.connect_vpc('the_key', 'the_secret') + vpc = conn.create_vpc("10.0.0.0/16") + subnet = conn.create_subnet(vpc.id, "10.0.0.0/18") + route_table1 = conn.create_route_table(vpc.id) + route_table2 = conn.create_route_table(vpc.id) + + all_route_tables = conn.get_all_route_tables() + all_route_tables.should.have.length_of(3) + + # Refresh + route_table1 = conn.get_all_route_tables(route_table1.id)[0] + route_table1.associations.should.have.length_of(0) + + # Associate + association_id1 = conn.associate_route_table(route_table1.id, subnet.id) + + # Refresh + route_table1 = conn.get_all_route_tables(route_table1.id)[0] + route_table2 = conn.get_all_route_tables(route_table2.id)[0] + + # Validate + route_table1.associations.should.have.length_of(1) + route_table2.associations.should.have.length_of(0) + + route_table1.associations[0].id.should.equal(association_id1) + route_table1.associations[0].main.should.equal(False) + route_table1.associations[0].route_table_id.should.equal(route_table1.id) + route_table1.associations[0].subnet_id.should.equal(subnet.id) + + # Replace Association + association_id2 = conn.replace_route_table_association_with_assoc(association_id1, route_table2.id) + + # Refresh + route_table1 = conn.get_all_route_tables(route_table1.id)[0] + route_table2 = conn.get_all_route_tables(route_table2.id)[0] + + # Validate + route_table1.associations.should.have.length_of(0) + route_table2.associations.should.have.length_of(1) + + route_table2.associations[0].id.should.equal(association_id2) + route_table2.associations[0].main.should.equal(False) + route_table2.associations[0].route_table_id.should.equal(route_table2.id) + route_table2.associations[0].subnet_id.should.equal(subnet.id) + + # Error: Replace association with invalid association ID + with assert_raises(EC2ResponseError) as cm: + conn.replace_route_table_association_with_assoc("rtbassoc-1234abcd", route_table1.id) + cm.exception.code.should.equal('InvalidAssociationID.NotFound') + cm.exception.status.should.equal(400) + cm.exception.request_id.should_not.be.none + + # Error: Replace association with invalid route table ID + with assert_raises(EC2ResponseError) as cm: + conn.replace_route_table_association_with_assoc(association_id2, "rtb-1234abcd") + cm.exception.code.should.equal('InvalidRouteTableID.NotFound') + cm.exception.status.should.equal(400) + cm.exception.request_id.should_not.be.none + + @mock_ec2 def test_routes_additional(): conn = boto.connect_vpc('the_key', 'the_secret')