From 22e6166e4ee66e8646808524e50220e5cfc92ea0 Mon Sep 17 00:00:00 2001 From: Shawn Falkner-Horine Date: Thu, 4 Sep 2014 16:23:59 -0700 Subject: [PATCH] Route Tables / Routes: Initial implementation. --- moto/ec2/exceptions.py | 16 ++ moto/ec2/models.py | 153 +++++++++++++++++-- moto/ec2/responses/route_tables.py | 167 ++++++++++++++++++++- moto/ec2/utils.py | 22 +++ tests/test_ec2/test_route_tables.py | 218 +++++++++++++++++++++++++++- 5 files changed, 552 insertions(+), 24 deletions(-) diff --git a/moto/ec2/exceptions.py b/moto/ec2/exceptions.py index 358c84b2b..381a86214 100644 --- a/moto/ec2/exceptions.py +++ b/moto/ec2/exceptions.py @@ -95,6 +95,22 @@ class InvalidPermissionNotFoundError(EC2ClientError): "Could not find a matching ingress rule") +class InvalidRouteTableIdError(EC2ClientError): + def __init__(self, route_table_id): + super(InvalidRouteTableIdError, self).__init__( + "InvalidRouteTableID.NotFound", + "The routeTable ID '{0}' does not exist" + .format(route_table_id)) + + +class InvalidRouteError(EC2ClientError): + def __init__(self, route_table_id, cidr): + super(InvalidRouteError, self).__init__( + "InvalidRoute.NotFound", + "no route with destination-cidr-block {0} in route table {1}" + .format(cidr, route_table_id)) + + class InvalidInstanceIdError(EC2ClientError): def __init__(self, instance_id): super(InvalidInstanceIdError, self).__init__( diff --git a/moto/ec2/models.py b/moto/ec2/models.py index bab604890..17222f472 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -29,6 +29,8 @@ from .exceptions import ( InvalidSecurityGroupDuplicateError, InvalidSecurityGroupNotFoundError, InvalidPermissionNotFoundError, + InvalidRouteTableIdError, + InvalidRouteError, InvalidInstanceIdError, MalformedAMIIdError, InvalidAMIIdError, @@ -55,6 +57,8 @@ from .utils import ( random_key_pair, random_reservation_id, random_route_table_id, + generate_route_id, + split_route_id, random_security_group_id, random_snapshot_id, random_spot_request_id, @@ -856,6 +860,10 @@ class VPCBackend(object): vpc_id = random_vpc_id() vpc = VPC(vpc_id, cidr_block) self.vpcs[vpc_id] = vpc + + # AWS creates a default main route table. + main_route_table = self.create_route_table(vpc_id, main=True) + return vpc def get_vpc(self, vpc_id): @@ -870,6 +878,7 @@ class VPCBackend(object): vpc = self.vpcs.pop(vpc_id, None) if not vpc: raise InvalidVPCIdError(vpc_id) + self.delete_route_table_for_vpc(vpc.id) if vpc.dhcp_options: vpc.dhcp_options.vpc = None self.delete_dhcp_options_set(vpc.dhcp_options.id) @@ -1057,9 +1066,13 @@ class SubnetRouteTableAssociationBackend(object): class RouteTable(object): - def __init__(self, route_table_id, vpc_id): + def __init__(self, route_table_id, vpc_id, main=False): self.id = route_table_id self.vpc_id = vpc_id + self.main = main + self.association_id = None + self.subnet_id = None + self.routes = {} @classmethod def create_from_cloudformation_json(cls, resource_name, cloudformation_json): @@ -1075,49 +1088,156 @@ class RouteTable(object): def physical_resource_id(self): return self.id + def get_filter_value(self, filter_name): + if filter_name == "association.main": + # Note: Boto only supports 'true'. + # https://github.com/boto/boto/issues/1742 + if self.main: + return 'true' + else: + return 'false' + elif filter_name == "vpc-id": + return self.vpc_id + else: + msg = "The filter '{0}' for DescribeRouteTables has not been" \ + " implemented in Moto yet. Feel free to open an issue at" \ + " https://github.com/spulec/moto/issues".format(filter_name) + raise NotImplementedError(msg) + class RouteTableBackend(object): def __init__(self): self.route_tables = {} super(RouteTableBackend, self).__init__() - def create_route_table(self, vpc_id): + def create_route_table(self, vpc_id, main=False): route_table_id = random_route_table_id() - route_table = RouteTable(route_table_id, vpc_id) + vpc = self.get_vpc(vpc_id) # Validate VPC exists + route_table = RouteTable(route_table_id, vpc_id, main=main) self.route_tables[route_table_id] = route_table + + # AWS creates a default local route. + self.create_route(route_table_id, vpc.cidr_block, local=True) + return route_table + def get_route_table(self, route_table_id): + route_table = self.route_tables.get(route_table_id, None) + if not route_table: + raise InvalidRouteTableIdError(route_table_id) + return route_table + + def get_all_route_tables(self, route_table_ids=None, filters=None): + route_tables = self.route_tables.values() + + if route_table_ids: + route_tables = [ route_table for route_table in route_tables if route_table.id in route_table_ids ] + if len(route_tables) != len(route_table_ids): + invalid_id = list(set(route_table_ids).difference(set([route_table.id for route_table in route_tables])))[0] + raise InvalidRouteTableIdError(invalid_id) + + if filters: + for (_filter, _filter_value) in filters.items(): + route_tables = [ route_table for route_table in route_tables if route_table.get_filter_value(_filter) in _filter_value ] + + return route_tables + + def delete_route_table(self, route_table_id): + deleted = self.route_tables.pop(route_table_id, None) + if not deleted: + raise InvalidRouteTableIdError(route_table_id) + return deleted + + def delete_route_table_for_vpc(self, vpc_id): + for route_table in self.route_tables.values(): + if route_table.vpc_id == vpc_id: + self.delete_route_table(route_table.id) + class Route(object): - def __init__(self, route_table_id, destination_cidr_block, gateway_id): - self.route_table_id = route_table_id + def __init__(self, route_table, destination_cidr_block, local=False, + internet_gateway=None, instance=None, interface=None, vpc_pcx=None): + self.id = generate_route_id(route_table.id, destination_cidr_block) + self.route_table = route_table self.destination_cidr_block = destination_cidr_block - self.gateway_id = gateway_id + self.local = local + self.internet_gateway = internet_gateway + self.instance = instance + self.interface = interface + self.vpc_pcx = vpc_pcx @classmethod def create_from_cloudformation_json(cls, resource_name, cloudformation_json): properties = cloudformation_json['Properties'] gateway_id = properties.get('GatewayId') + instance_id = properties.get('InstanceId') + interface_id = properties.get('NetworkInterfaceId') + pcx_id = properties.get('VpcPeeringConnectionId') + route_table_id = properties['RouteTableId'] route_table = ec2_backend.create_route( route_table_id=route_table_id, destination_cidr_block=properties['DestinationCidrBlock'], gateway_id=gateway_id, + instance_id=instance_id, + interface_id=interface_id, + vpc_peering_connection_id=pcx_id, ) return route_table class RouteBackend(object): def __init__(self): - self.routes = {} super(RouteBackend, self).__init__() - def create_route(self, route_table_id, destination_cidr_block, gateway_id): - route = Route(route_table_id, destination_cidr_block, gateway_id) - self.routes[destination_cidr_block] = route + def create_route(self, route_table_id, destination_cidr_block, local=False, + gateway_id=None, instance_id=None, interface_id=None, + vpc_peering_connection_id=None): + route_table = self.get_route_table(route_table_id) + + if interface_id: + ec2_backend.raise_not_implemented_error("CreateRoute to NetworkInterfaceId") + + route = Route(route_table, destination_cidr_block, local=local, + internet_gateway=self.get_internet_gateway(gateway_id) if gateway_id else None, + instance=self.get_instance(instance_id) if instance_id else None, + interface=None, + vpc_pcx=self.get_vpc_peering_connection(vpc_peering_connection_id) if vpc_peering_connection_id else None) + route_table.routes[route.id] = route return route + def replace_route(self, route_table_id, destination_cidr_block, + gateway_id=None, instance_id=None, interface_id=None, + vpc_peering_connection_id=None): + route_table = self.get_route_table(route_table_id) + route_id = generate_route_id(route_table.id, destination_cidr_block) + route = route_table.routes[route_id] + + if interface_id: + ec2_backend.raise_not_implemented_error("ReplaceRoute to NetworkInterfaceId") + + route.internet_gateway = self.get_internet_gateway(gateway_id) if gateway_id else None + route.instance = self.get_instance(instance_id) if instance_id else None + route.interface = None + route.vpc_pcx = self.get_vpc_peering_connection(vpc_peering_connection_id) if vpc_peering_connection_id else None + + route_table.routes[route.id] = route + return route + + def get_route(self, route_id): + route_table_id, destination_cidr_block = split_route_id(route_id) + route_table = self.get_route_table(route_table_id) + return route_table.get(route_id) + + def delete_route(self, route_table_id, destination_cidr_block): + route_table = self.get_route_table(route_table_id) + route_id = generate_route_id(route_table_id, destination_cidr_block) + deleted = route_table.routes.pop(route_id, None) + if not deleted: + raise InvalidRouteError(route_table_id, destination_cidr_block) + return deleted + class InternetGateway(TaggedEC2Instance): def __init__(self): @@ -1153,8 +1273,7 @@ class InternetGatewayBackend(object): return igws or self.internet_gateways.values() def delete_internet_gateway(self, internet_gateway_id): - igw_ids = [internet_gateway_id] - igw = self.describe_internet_gateways(internet_gateway_ids=igw_ids)[0] + igw = self.get_internet_gateway(internet_gateway_id) if igw.vpc: raise DependencyViolationError( "{0} is being utilized by {1}" @@ -1164,22 +1283,24 @@ class InternetGatewayBackend(object): return True def detach_internet_gateway(self, internet_gateway_id, vpc_id): - igw_ids = [internet_gateway_id] - igw = self.describe_internet_gateways(internet_gateway_ids=igw_ids)[0] + igw = self.get_internet_gateway(internet_gateway_id) if not igw.vpc or igw.vpc.id != vpc_id: raise GatewayNotAttachedError(internet_gateway_id, vpc_id) igw.vpc = None return True def attach_internet_gateway(self, internet_gateway_id, vpc_id): - igw_ids = [internet_gateway_id] - igw = self.describe_internet_gateways(internet_gateway_ids=igw_ids)[0] + igw = self.get_internet_gateway(internet_gateway_id) if igw.vpc: raise ResourceAlreadyAssociatedError(internet_gateway_id) vpc = self.get_vpc(vpc_id) igw.vpc = vpc return True + def get_internet_gateway(self, internet_gateway_id): + igw_ids = [internet_gateway_id] + return self.describe_internet_gateways(internet_gateway_ids=igw_ids)[0] + class VPCGatewayAttachment(object): def __init__(self, gateway_id, vpc_id): diff --git a/moto/ec2/responses/route_tables.py b/moto/ec2/responses/route_tables.py index 3b76a156d..65e9ad144 100644 --- a/moto/ec2/responses/route_tables.py +++ b/moto/ec2/responses/route_tables.py @@ -1,5 +1,9 @@ from __future__ import unicode_literals +from jinja2 import Template + from moto.core.responses import BaseResponse +from moto.ec2.models import ec2_backend +from moto.ec2.utils import route_table_ids_from_querystring, filters_from_querystring, optional_from_querystring class RouteTables(BaseResponse): @@ -7,25 +11,176 @@ class RouteTables(BaseResponse): raise NotImplementedError('RouteTables(AmazonVPC).associate_route_table is not yet implemented') def create_route(self): - raise NotImplementedError('RouteTables(AmazonVPC).create_route is not yet implemented') + route_table_id = self.querystring.get('RouteTableId')[0] + destination_cidr_block = self.querystring.get('DestinationCidrBlock')[0] + + internet_gateway_id = optional_from_querystring('GatewayId', self.querystring) + instance_id = optional_from_querystring('InstanceId', self.querystring) + interface_id = optional_from_querystring('NetworkInterfaceId', self.querystring) + pcx_id = optional_from_querystring('VpcPeeringConnectionId', self.querystring) + + route = ec2_backend.create_route(route_table_id, destination_cidr_block, + gateway_id=internet_gateway_id, + instance_id=instance_id, + interface_id=interface_id, + vpc_peering_connection_id=pcx_id) + + template = Template(CREATE_ROUTE_RESPONSE) + return template.render() def create_route_table(self): - raise NotImplementedError('RouteTables(AmazonVPC).create_route_table is not yet implemented') + vpc_id = self.querystring.get('VpcId')[0] + route_table = ec2_backend.create_route_table(vpc_id) + template = Template(CREATE_ROUTE_TABLE_RESPONSE) + return template.render(route_table=route_table) def delete_route(self): - raise NotImplementedError('RouteTables(AmazonVPC).delete_route is not yet implemented') + route_table_id = self.querystring.get('RouteTableId')[0] + destination_cidr_block = self.querystring.get('DestinationCidrBlock')[0] + ec2_backend.delete_route(route_table_id, destination_cidr_block) + template = Template(DELETE_ROUTE_RESPONSE) + return template.render() def delete_route_table(self): - raise NotImplementedError('RouteTables(AmazonVPC).delete_route_table is not yet implemented') + route_table_id = self.querystring.get('RouteTableId')[0] + ec2_backend.delete_route_table(route_table_id) + template = Template(DELETE_ROUTE_TABLE_RESPONSE) + return template.render() def describe_route_tables(self): - raise NotImplementedError('RouteTables(AmazonVPC).describe_route_tables is not yet implemented') + route_table_ids = route_table_ids_from_querystring(self.querystring) + filters = filters_from_querystring(self.querystring) + route_tables = ec2_backend.get_all_route_tables(route_table_ids, filters) + template = Template(DESCRIBE_ROUTE_TABLES_RESPONSE) + return template.render(route_tables=route_tables) def disassociate_route_table(self): raise NotImplementedError('RouteTables(AmazonVPC).disassociate_route_table is not yet implemented') def replace_route(self): - raise NotImplementedError('RouteTables(AmazonVPC).replace_route is not yet implemented') + route_table_id = self.querystring.get('RouteTableId')[0] + destination_cidr_block = self.querystring.get('DestinationCidrBlock')[0] + + internet_gateway_id = optional_from_querystring('GatewayId', self.querystring) + instance_id = optional_from_querystring('InstanceId', self.querystring) + interface_id = optional_from_querystring('NetworkInterfaceId', self.querystring) + pcx_id = optional_from_querystring('VpcPeeringConnectionId', self.querystring) + + route = ec2_backend.replace_route(route_table_id, destination_cidr_block, + gateway_id=internet_gateway_id, + instance_id=instance_id, + interface_id=interface_id, + vpc_peering_connection_id=pcx_id) + + template = Template(REPLACE_ROUTE_RESPONSE) + return template.render() def replace_route_table_association(self): raise NotImplementedError('RouteTables(AmazonVPC).replace_route_table_association is not yet implemented') + + +CREATE_ROUTE_RESPONSE = """ + + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + true + +""" + +REPLACE_ROUTE_RESPONSE = """ + + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + true + +""" + +CREATE_ROUTE_TABLE_RESPONSE = """ + + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + + {{ route_table.id }} + {{ route_table.vpc_id }} + + {% for route in route_table.routes.values() %} + {% if route.local %} + + {{ route.destination_cidr_block }} + local + active + + {% endif %} + {% endfor %} + + + + + +""" + +DESCRIBE_ROUTE_TABLES_RESPONSE = """ + + 6f570b0b-9c18-4b07-bdec-73740dcf861a + + {% for route_table in route_tables %} + + {{ route_table.id }} + {{ route_table.vpc_id }} + + {% for route in route_table.routes.values() %} + + {{ route.destination_cidr_block }} + {% if route.local %} + local + CreateRouteTable + active + {% endif %} + {% if route.internet_gateway %} + {{ route.internet_gateway.id }} + CreateRoute + active + {% endif %} + {% if route.instance %} + {{ route.instance.id }} + CreateRoute + active + {% endif %} + {% if route.vpc_pcx %} + CreateRoute + blackhole + {% endif %} + + {% endfor %} + + + {% if route_table.association_id %} + + {{ route_table.association_id }} + {{ route_table.id }} + {% if not route_table.subnet_id %} +
true
+ {% endif %} + {% if route_table.subnet_id %} + {{ route_table.subnet_id }} + {% endif %} +
+ {% endif %} +
+ +
+ {% endfor %} +
+
+""" + +DELETE_ROUTE_RESPONSE = """ + + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + true + +""" + +DELETE_ROUTE_TABLE_RESPONSE = """ + + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + true + +""" diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index 667d97f28..4789b1f30 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -80,6 +80,15 @@ def random_ip(): ) +def generate_route_id(route_table_id, cidr_block): + return "%s~%s" % (route_table_id, cidr_block) + + +def split_route_id(route_id): + values = string.split(route_id,'~') + return values[0], values[1] + + def instance_ids_from_querystring(querystring_dict): instance_ids = [] for key, value in querystring_dict.items(): @@ -96,6 +105,14 @@ def image_ids_from_querystring(querystring_dict): return image_ids +def route_table_ids_from_querystring(querystring_dict): + route_table_ids = [] + for key, value in querystring_dict.iteritems(): + if 'RouteTableId' in key: + route_table_ids.append(value[0]) + return route_table_ids + + def sequence_from_querystring(parameter, querystring_dict): parameter_values = [] for key, value in querystring_dict.items(): @@ -160,6 +177,11 @@ def dhcp_configuration_from_querystring(querystring, option=u'DhcpConfiguration' return response_values +def optional_from_querystring(parameter, querystring): + parameter_array = querystring.get(parameter) + return parameter_array[0] if parameter_array else None + + def filters_from_querystring(querystring_dict): response_values = {} for key, value in querystring_dict.items(): diff --git a/tests/test_ec2/test_route_tables.py b/tests/test_ec2/test_route_tables.py index 19434129e..e84051ea4 100644 --- a/tests/test_ec2/test_route_tables.py +++ b/tests/test_ec2/test_route_tables.py @@ -1,10 +1,224 @@ from __future__ import unicode_literals +# Ensure 'assert_raises' context manager support for Python 2.6 +import tests.backport_assert_raises +from nose.tools import assert_raises + import boto +from boto.exception import EC2ResponseError import sure # noqa from moto import mock_ec2 +from tests.helpers import requires_boto_gte @mock_ec2 -def test_route_tables(): - pass +def test_route_tables_defaults(): + conn = boto.connect_vpc('the_key', 'the_secret') + vpc = conn.create_vpc("10.0.0.0/16") + + all_route_tables = conn.get_all_route_tables() + all_route_tables.should.have.length_of(1) + + main_route_table = all_route_tables[0] + main_route_table.vpc_id.should.equal(vpc.id) + + routes = main_route_table.routes + routes.should.have.length_of(1) + + local_route = routes[0] + local_route.gateway_id.should.equal('local') + local_route.state.should.equal('active') + local_route.destination_cidr_block.should.equal(vpc.cidr_block) + + vpc.delete() + + all_route_tables = conn.get_all_route_tables() + all_route_tables.should.have.length_of(0) + + +@mock_ec2 +def test_route_tables_additional(): + conn = boto.connect_vpc('the_key', 'the_secret') + vpc = conn.create_vpc("10.0.0.0/16") + route_table = conn.create_route_table(vpc.id) + + all_route_tables = conn.get_all_route_tables() + all_route_tables.should.have.length_of(2) + all_route_tables[0].vpc_id.should.equal(vpc.id) + all_route_tables[1].vpc_id.should.equal(vpc.id) + + all_route_table_ids = [route_table.id for route_table in all_route_tables] + all_route_table_ids.should.contain(route_table.id) + + routes = route_table.routes + routes.should.have.length_of(1) + + local_route = routes[0] + local_route.gateway_id.should.equal('local') + local_route.state.should.equal('active') + local_route.destination_cidr_block.should.equal(vpc.cidr_block) + + conn.delete_route_table(route_table.id) + + all_route_tables = conn.get_all_route_tables() + all_route_tables.should.have.length_of(1) + + with assert_raises(EC2ResponseError) as cm: + conn.delete_route_table("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_route_tables_filters(): + conn = boto.connect_vpc('the_key', 'the_secret') + + vpc1 = conn.create_vpc("10.0.0.0/16") + route_table1 = conn.create_route_table(vpc1.id) + + vpc2 = conn.create_vpc("10.0.0.0/16") + route_table2 = conn.create_route_table(vpc2.id) + + all_route_tables = conn.get_all_route_tables() + all_route_tables.should.have.length_of(4) + + # Filter by main route table + main_route_tables = conn.get_all_route_tables(filters={'association.main':'true'}) + main_route_tables.should.have.length_of(2) + main_route_table_ids = [route_table.id for route_table in main_route_tables] + main_route_table_ids.should_not.contain(route_table1.id) + main_route_table_ids.should_not.contain(route_table2.id) + + # Filter by VPC + vpc1_route_tables = conn.get_all_route_tables(filters={'vpc-id':vpc1.id}) + vpc1_route_tables.should.have.length_of(2) + vpc1_route_table_ids = [route_table.id for route_table in vpc1_route_tables] + vpc1_route_table_ids.should.contain(route_table1.id) + vpc1_route_table_ids.should_not.contain(route_table2.id) + + # Filter by VPC and main route table + vpc2_main_route_tables = conn.get_all_route_tables(filters={'association.main':'true', 'vpc-id':vpc2.id}) + vpc2_main_route_tables.should.have.length_of(1) + vpc2_main_route_table_ids = [route_table.id for route_table in vpc2_main_route_tables] + vpc2_main_route_table_ids.should_not.contain(route_table1.id) + vpc2_main_route_table_ids.should_not.contain(route_table2.id) + + # Unsupported filter + conn.get_all_route_tables.when.called_with(filters={'not-implemented-filter': 'foobar'}).should.throw(NotImplementedError) + + +@mock_ec2 +def test_routes_additional(): + conn = boto.connect_vpc('the_key', 'the_secret') + vpc = conn.create_vpc("10.0.0.0/16") + main_route_table = conn.get_all_route_tables()[0] + local_route = main_route_table.routes[0] + igw = conn.create_internet_gateway() + ROUTE_CIDR = "10.0.0.4/24" + + conn.create_route(main_route_table.id, ROUTE_CIDR, gateway_id=igw.id) + + main_route_table = conn.get_all_route_tables()[0] # Refresh route table + + main_route_table.routes.should.have.length_of(2) + new_routes = [route for route in main_route_table.routes if route.destination_cidr_block != vpc.cidr_block] + new_routes.should.have.length_of(1) + + new_route = new_routes[0] + new_route.gateway_id.should.equal(igw.id) + new_route.instance_id.should.be.none + new_route.state.should.equal('active') + new_route.destination_cidr_block.should.equal(ROUTE_CIDR) + + conn.delete_route(main_route_table.id, ROUTE_CIDR) + + main_route_table = conn.get_all_route_tables()[0] # Refresh route table + + main_route_table.routes.should.have.length_of(1) + new_routes = [route for route in main_route_table.routes if route.destination_cidr_block != vpc.cidr_block] + new_routes.should.have.length_of(0) + + with assert_raises(EC2ResponseError) as cm: + conn.delete_route(main_route_table.id, ROUTE_CIDR) + cm.exception.code.should.equal('InvalidRoute.NotFound') + cm.exception.status.should.equal(400) + cm.exception.request_id.should_not.be.none + + conn.create_route.when.called_with(main_route_table.id, ROUTE_CIDR, interface_id='eni-1234abcd').should.throw(NotImplementedError) + + +@mock_ec2 +def test_routes_replace(): + conn = boto.connect_vpc('the_key', 'the_secret') + vpc = conn.create_vpc("10.0.0.0/16") + main_route_table = conn.get_all_route_tables(filters={'association.main':'true','vpc-id':vpc.id})[0] + local_route = main_route_table.routes[0] + ROUTE_CIDR = "10.0.0.4/24" + + # Various route targets + igw = conn.create_internet_gateway() + + reservation = conn.run_instances('ami-1234abcd') + instance = reservation.instances[0] + + # Create initial route + conn.create_route(main_route_table.id, ROUTE_CIDR, gateway_id=igw.id) + + # Replace... + def get_target_route(): + route_table = conn.get_all_route_tables(main_route_table.id)[0] + routes = [route for route in route_table.routes if route.destination_cidr_block != vpc.cidr_block] + routes.should.have.length_of(1) + return routes[0] + + conn.replace_route(main_route_table.id, ROUTE_CIDR, instance_id=instance.id) + + target_route = get_target_route() + target_route.gateway_id.should.be.none + target_route.instance_id.should.equal(instance.id) + target_route.state.should.equal('active') + target_route.destination_cidr_block.should.equal(ROUTE_CIDR) + + conn.replace_route(main_route_table.id, ROUTE_CIDR, gateway_id=igw.id) + + target_route = get_target_route() + target_route.gateway_id.should.equal(igw.id) + target_route.instance_id.should.be.none + target_route.state.should.equal('active') + target_route.destination_cidr_block.should.equal(ROUTE_CIDR) + + with assert_raises(EC2ResponseError) as cm: + conn.replace_route('rtb-1234abcd', ROUTE_CIDR, gateway_id=igw.id) + cm.exception.code.should.equal('InvalidRouteTableID.NotFound') + cm.exception.status.should.equal(400) + cm.exception.request_id.should_not.be.none + + conn.replace_route.when.called_with(main_route_table.id, ROUTE_CIDR, interface_id='eni-1234abcd').should.throw(NotImplementedError) + + +@requires_boto_gte("2.32.0") +@mock_ec2 +def test_routes_vpc_peering_connection(): + conn = boto.connect_vpc('the_key', 'the_secret') + vpc = conn.create_vpc("10.0.0.0/16") + main_route_table = conn.get_all_route_tables(filters={'association.main':'true','vpc-id':vpc.id})[0] + local_route = main_route_table.routes[0] + ROUTE_CIDR = "10.0.0.4/24" + + peer_vpc = conn.create_vpc("11.0.0.0/16") + vpc_pcx = conn.create_vpc_peering_connection(vpc.id, peer_vpc.id) + + conn.create_route(main_route_table.id, ROUTE_CIDR, vpc_peering_connection_id=vpc_pcx.id) + + # Refresh route table + main_route_table = conn.get_all_route_tables(main_route_table.id)[0] + new_routes = [route for route in main_route_table.routes if route.destination_cidr_block != vpc.cidr_block] + new_routes.should.have.length_of(1) + + new_route = new_routes[0] + new_route.gateway_id.should.be.none + new_route.instance_id.should.be.none + new_route.state.should.equal('blackhole') + new_route.destination_cidr_block.should.equal(ROUTE_CIDR) +