Merge pull request #188 from DreadPirateShawn/ImplementRouteTables

Route Tables / Routes: Initial implementation.
This commit is contained in:
Steve Pulec 2014-09-09 20:48:43 -04:00
commit 1bbb7a70ad
5 changed files with 579 additions and 24 deletions

View file

@ -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__(

View file

@ -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,
@ -905,6 +909,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):
@ -916,9 +924,21 @@ class VPCBackend(object):
return self.vpcs.values()
def delete_vpc(self, vpc_id):
# Delete route table if only main route table remains.
route_tables = ec2_backend.get_all_route_tables(filters={'vpc-id':vpc_id})
if len(route_tables) > 1:
raise DependencyViolationError(
"The vpc {0} has dependencies and cannot be deleted."
.format(vpc_id)
)
for route_table in route_tables:
ec2_backend.delete_route_table(route_table.id)
# Now delete VPC.
vpc = self.vpcs.pop(vpc_id, None)
if not vpc:
raise InvalidVPCIdError(vpc_id)
if vpc.dhcp_options:
vpc.dhcp_options.vpc = None
self.delete_dhcp_options_set(vpc.dhcp_options.id)
@ -1106,9 +1126,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):
@ -1124,49 +1148,151 @@ 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
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):
@ -1202,8 +1328,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}"
@ -1213,22 +1338,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):

View file

@ -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 = """
<CreateRouteResponse xmlns="http://ec2.amazonaws.com/doc/2013-07-15/">
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
<return>true</return>
</CreateRouteResponse>
"""
REPLACE_ROUTE_RESPONSE = """
<ReplaceRouteResponse xmlns="http://ec2.amazonaws.com/doc/2013-07-15/">
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
<return>true</return>
</ReplaceRouteResponse>
"""
CREATE_ROUTE_TABLE_RESPONSE = """
<CreateRouteTableResponse xmlns="http://ec2.amazonaws.com/doc/2013-07-15/">
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
<routeTable>
<routeTableId>{{ route_table.id }}</routeTableId>
<vpcId>{{ route_table.vpc_id }}</vpcId>
<routeSet>
{% for route in route_table.routes.values() %}
{% if route.local %}
<item>
<destinationCidrBlock>{{ route.destination_cidr_block }}</destinationCidrBlock>
<gatewayId>local</gatewayId>
<state>active</state>
</item>
{% endif %}
{% endfor %}
</routeSet>
<associationSet/>
<tagSet/>
</routeTable>
</CreateRouteTableResponse>
"""
DESCRIBE_ROUTE_TABLES_RESPONSE = """
<DescribeRouteTablesResponse xmlns="http://ec2.amazonaws.com/doc/2013-08-15/">
<requestId>6f570b0b-9c18-4b07-bdec-73740dcf861a</requestId>
<routeTableSet>
{% for route_table in route_tables %}
<item>
<routeTableId>{{ route_table.id }}</routeTableId>
<vpcId>{{ route_table.vpc_id }}</vpcId>
<routeSet>
{% for route in route_table.routes.values() %}
<item>
<destinationCidrBlock>{{ route.destination_cidr_block }}</destinationCidrBlock>
{% if route.local %}
<gatewayId>local</gatewayId>
<origin>CreateRouteTable</origin>
<state>active</state>
{% endif %}
{% if route.internet_gateway %}
<gatewayId>{{ route.internet_gateway.id }}</gatewayId>
<origin>CreateRoute</origin>
<state>active</state>
{% endif %}
{% if route.instance %}
<instanceId>{{ route.instance.id }}</instanceId>
<origin>CreateRoute</origin>
<state>active</state>
{% endif %}
{% if route.vpc_pcx %}
<origin>CreateRoute</origin>
<state>blackhole</state>
{% endif %}
</item>
{% endfor %}
</routeSet>
<associationSet>
{% if route_table.association_id %}
<item>
<routeTableAssociationId>{{ route_table.association_id }}</routeTableAssociationId>
<routeTableId>{{ route_table.id }}</routeTableId>
{% if not route_table.subnet_id %}
<main>true</main>
{% endif %}
{% if route_table.subnet_id %}
<subnetId>{{ route_table.subnet_id }}</subnetId>
{% endif %}
</item>
{% endif %}
</associationSet>
<tagSet/>
</item>
{% endfor %}
</routeTableSet>
</DescribeRouteTablesResponse>
"""
DELETE_ROUTE_RESPONSE = """
<DeleteRouteResponse xmlns="http://ec2.amazonaws.com/doc/2013-07-15/">
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
<return>true</return>
</DeleteRouteResponse>
"""
DELETE_ROUTE_TABLE_RESPONSE = """
<DeleteRouteTableResponse xmlns="http://ec2.amazonaws.com/doc/2013-07-15/">
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
<return>true</return>
</DeleteRouteTableResponse>
"""

View file

@ -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.items():
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():