Implement storage of VPN gateway route propagation attribute

Change-Id: I09ea00b8160b92ed591bac8edd7d28e8f2df472a
This commit is contained in:
Feodor Tersin 2015-05-25 12:24:45 +03:00
parent 584985dd48
commit c8ca315277
7 changed files with 298 additions and 16 deletions

View File

@ -1502,6 +1502,36 @@ class VpcCloudController(CloudController):
true if the requests succeeds.
"""
@module_and_param_types(route_table, 'rtb_id',
'vgw_id')
def enable_vgw_route_propagation(self, context, route_table_id,
gateway_id):
"""Enables a VGW to propagate routes to the specified route table.
Args:
context (RequestContext): The request context.
route_table_id (str): The ID of the route table.
gateway_id (str): The ID of the virtual private gateway.
Returns:
true if the requests succeeds.
"""
@module_and_param_types(route_table, 'rtb_id',
'vgw_id')
def disable_vgw_route_propagation(self, context, route_table_id,
gateway_id):
"""Disables a (VGW) from propagating routes to a specified route table.
Args:
context (RequestContext): The request context.
route_table_id (str): The ID of the route table.
gateway_id (str): The ID of the virtual private gateway.
Returns:
true if the requests succeeds.
"""
@module_and_param_types(route_table, 'rtb_id', 'subnet_id')
def associate_route_table(self, context, route_table_id, subnet_id):
"""Associates a subnet with a route table.

View File

@ -75,7 +75,7 @@ def detach_internet_gateway(context, internet_gateway_id, vpc_id):
igw = ec2utils.get_db_item(context, internet_gateway_id)
vpc = ec2utils.get_db_item(context, vpc_id)
if igw.get('vpc_id') != vpc['id']:
raise exception.GatewayNotAttached(igw_id=igw['id'],
raise exception.GatewayNotAttached(gw_id=igw['id'],
vpc_id=vpc['id'])
remove_os_gateway_router = (

View File

@ -85,6 +85,35 @@ def delete_route(context, route_table_id, destination_cidr_block):
return True
def enable_vgw_route_propagation(context, route_table_id, gateway_id):
route_table = ec2utils.get_db_item(context, route_table_id)
# NOTE(ft): AWS returns GatewayNotAttached for all invalid cases of
# gateway_id value
vpn_gateway = ec2utils.get_db_item(context, gateway_id)
if vpn_gateway['vpc_id'] != route_table['vpc_id']:
raise exception.GatewayNotAttached(gw_id=vpn_gateway['id'],
vpc_id=route_table['vpc_id'])
if vpn_gateway['id'] in route_table.setdefault('propagating_gateways', []):
return True
vgws = route_table.setdefault('propagating_gateways', [])
vgws.append(gateway_id)
db_api.update_item(context, route_table)
return True
def disable_vgw_route_propagation(context, route_table_id, gateway_id):
route_table = ec2utils.get_db_item(context, route_table_id)
if gateway_id not in route_table.get('propagating_gateways', []):
return True
vgws = route_table['propagating_gateways']
vgws.remove(gateway_id)
if not vgws:
del route_table['propagating_gateways']
db_api.update_item(context, route_table)
return True
def associate_route_table(context, route_table_id, subnet_id):
route_table = ec2utils.get_db_item(context, route_table_id)
subnet = ec2utils.get_db_item(context, subnet_id)
@ -210,7 +239,8 @@ class RouteTableDescriber(common.TaggableItemsDescriber,
is_main=(self.vpcs[route_table['vpc_id']]['route_table_id'] ==
route_table['id']),
gateways=self.gateways,
network_interfaces=self.network_interfaces)
network_interfaces=self.network_interfaces,
vpn_connections_by_gateway_id=self.vpn_connections_by_gateway_id)
def get_db_items(self):
associations = collections.defaultdict(list)
@ -228,6 +258,12 @@ class RouteTableDescriber(common.TaggableItemsDescriber,
network_interfaces = db_api.get_items(self.context, 'eni')
self.network_interfaces = {eni['id']: eni
for eni in network_interfaces}
vpn_connections = db_api.get_items(self.context, 'vpn')
vpns_by_gateway_id = {}
for vpn in vpn_connections:
vpns = vpns_by_gateway_id.setdefault(vpn['vpn_gateway_id'], [])
vpns.append(vpn)
self.vpn_connections_by_gateway_id = vpns_by_gateway_id
return super(RouteTableDescriber, self).get_db_items()
@ -373,14 +409,20 @@ def _set_route(context, route_table_id, destination_cidr_block,
def _format_route_table(context, route_table, is_main=False,
associated_subnet_ids=[],
gateways={},
network_interfaces={}):
network_interfaces={},
vpn_connections_by_gateway_id={}):
vpc_id = route_table['vpc_id']
ec2_route_table = {'routeTableId': route_table['id'],
'vpcId': vpc_id,
'routeSet': [],
# NOTE(ft): AWS returns empty tag set for a route table
# if no tag exists
'tagSet': []}
ec2_route_table = {
'routeTableId': route_table['id'],
'vpcId': vpc_id,
'routeSet': [],
'propagatingVgwSet': [
{'gatewayId': vgw_id}
for vgw_id in route_table.get('propagating_gateways', [])],
# NOTE(ft): AWS returns empty tag set for a route table
# if no tag exists
'tagSet': [],
}
# TODO(ft): refactor to get Nova instances outside of this function
nova = clients.nova(context)
for route in route_table['routes']:
@ -424,6 +466,21 @@ def _format_route_table(context, route_table, is_main=False,
'state': state})
ec2_route_table['routeSet'].append(ec2_route)
for vgw_id in route_table.get('propagating_gateways', []):
vgw = gateways.get(vgw_id)
if vgw and vgw_id in vpn_connections_by_gateway_id:
cidrs = set()
vpn_connections = vpn_connections_by_gateway_id[vgw_id]
for vpn_connection in vpn_connections:
cidrs.update(vpn_connection['cidrs'])
state = 'active' if vgw['vpc_id'] == vpc_id else 'blackhole'
for cidr in cidrs:
ec2_route = {'gatewayId': vgw_id,
'destinationCidrBlock': cidr,
'state': state,
'origin': 'EnableVgwRoutePropagation'}
ec2_route_table['routeSet'].append(ec2_route)
associations = []
if is_main:
associations.append({
@ -510,6 +567,12 @@ def _get_subnet_host_routes(context, route_table, gateway_ip,
return '127.0.0.1'
return network_interface['private_ip_address']
# TODO(ft): perhaps we should consider vpn routes here.
# For example if no internet gateway is attached, but vpn gateway is,
# a host should route trafic related to vpn gateways to Neutron router.
# Otherwise if internet gateway is attached, but vpn gateway is dead,
# vpn related trafic should be terminated to 127.0.0.1.
# Next question is route precedence in overlapping case.
host_routes = [{'destination': route['destination_cidr_block'],
'nexthop': get_nexthop(route)}
for route in route_table['routes']

View File

@ -258,7 +258,7 @@ class ResourceAlreadyAssociated(EC2IncorrectStateException):
class GatewayNotAttached(EC2IncorrectStateException):
ec2_code = 'Gateway.NotAttached'
msg_fmt = _("resource %(igw_id)s is not attached to network %(vpc_id)s")
msg_fmt = _("resource %(gw_id)s is not attached to network %(vpc_id)s")
class IncorrectInstanceState(EC2IncorrectStateException):

View File

@ -265,6 +265,7 @@ ID_OS_IPSECPOLICY_2 = random_os_id()
PRE_SHARED_KEY_1 = 'Z54kLbANio5A1.XmkjwYvWuSfVx3_xuG'
PRE_SHARED_KEY_2 = 'FSbXpA.G9306W.BQ2n6W9JZJsyZcMN2G'
CIDR_VPN_1_STATIC = '192.168.101.0/24'
CIDR_VPN_1_PROPAGATED_1 = '192.168.110.0/24'
CIDR_VPN_2_PROPAGATED_1 = '192.168.210.0/24'
CIDR_VPN_2_PROPAGATED_2 = '192.168.220.0/24'
@ -1068,6 +1069,7 @@ DB_ROUTE_TABLE_2 = {
'network_interface_id': ID_EC2_NETWORK_INTERFACE_2},
{'destination_cidr_block': '0.0.0.0/0',
'gateway_id': ID_EC2_IGW_1}],
'propagating_gateways': [ID_EC2_VPN_GATEWAY_1],
}
DB_ROUTE_TABLE_3 = {
'id': ID_EC2_ROUTE_TABLE_3,
@ -1089,6 +1091,7 @@ EC2_ROUTE_TABLE_1 = {
{'routeTableAssociationId': ID_EC2_ROUTE_TABLE_ASSOCIATION_1,
'routeTableId': ID_EC2_ROUTE_TABLE_1,
'main': True}],
'propagatingVgwSet': [],
'tagSet': [],
}
EC2_ROUTE_TABLE_2 = {
@ -1105,10 +1108,15 @@ EC2_ROUTE_TABLE_2 = {
'networkInterfaceId': ID_EC2_NETWORK_INTERFACE_2,
'state': 'active',
'origin': 'CreateRoute'},
{'destinationCidrBlock': CIDR_VPN_1_PROPAGATED_1,
'gatewayId': ID_EC2_VPN_GATEWAY_1,
'state': 'active',
'origin': 'EnableVgwRoutePropagation'},
{'destinationCidrBlock': '0.0.0.0/0',
'gatewayId': ID_EC2_IGW_1,
'state': 'active',
'origin': 'CreateRoute'}],
'propagatingVgwSet': [{'gatewayId': ID_EC2_VPN_GATEWAY_1}],
'tagSet': [],
}
EC2_ROUTE_TABLE_3 = {
@ -1128,6 +1136,7 @@ EC2_ROUTE_TABLE_3 = {
'routeTableId': ID_EC2_ROUTE_TABLE_3,
'subnetId': ID_EC2_SUBNET_2,
'main': False}],
'propagatingVgwSet': [],
'tagSet': [],
}
@ -1625,7 +1634,7 @@ DB_VPN_CONNECTION_1 = {
'pre_shared_key': PRE_SHARED_KEY_1,
'os_ikepolicy_id': ID_OS_IKEPOLICY_1,
'os_ipsecpolicy_id': ID_OS_IPSECPOLICY_1,
'cidrs': [],
'cidrs': [CIDR_VPN_1_PROPAGATED_1],
}
DB_VPN_CONNECTION_2 = {
'id': ID_EC2_VPN_CONNECTION_2,
@ -1644,7 +1653,8 @@ EC2_VPN_CONNECTION_1 = {
'customerGatewayId': ID_EC2_CUSTOMER_GATEWAY_1,
'state': 'available',
'type': 'ipsec.1',
'routes': None,
'routes': [{'destinationCidrBlock': CIDR_VPN_1_PROPAGATED_1,
'state': 'available'}],
'vgwTelemetry': None,
'options': {'staticRoutesOnly': True},
}

View File

@ -338,6 +338,106 @@ class RouteTableTestCase(base.ApiTestCase):
self.db_api.update_item.assert_any_call(
mock.ANY, fakes.DB_ROUTE_TABLE_2)
def test_enable_vgw_route_propagation(self):
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_1, fakes.DB_VPN_GATEWAY_1)
resp = self.execute('EnableVgwRoutePropagation',
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
'GatewayId': fakes.ID_EC2_VPN_GATEWAY_1})
self.assertEqual({'return': True}, resp)
self.db_api.update_item.assert_called_once_with(
mock.ANY,
tools.update_dict(
fakes.DB_ROUTE_TABLE_1,
{'propagating_gateways': [fakes.ID_EC2_VPN_GATEWAY_1]}))
self.db_api.reset_mock()
self.set_mock_db_items(
fakes.DB_ROUTE_TABLE_2,
tools.update_dict(fakes.DB_VPN_GATEWAY_2,
{'vpc_id': fakes.ID_EC2_VPC_1}))
resp = self.execute('EnableVgwRoutePropagation',
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
'GatewayId': fakes.ID_EC2_VPN_GATEWAY_2})
self.assertEqual({'return': True}, resp)
db_route_table_2 = copy.deepcopy(fakes.DB_ROUTE_TABLE_2)
db_route_table_2['propagating_gateways'].append(
fakes.ID_EC2_VPN_GATEWAY_2)
self.db_api.update_item.assert_called_once_with(
mock.ANY, db_route_table_2)
def test_enable_vgw_route_propagation_idempotent(self):
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_2, fakes.DB_VPN_GATEWAY_1)
resp = self.execute('EnableVgwRoutePropagation',
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
'GatewayId': fakes.ID_EC2_VPN_GATEWAY_1})
self.assertEqual({'return': True}, resp)
self.assertFalse(self.db_api.update_item.called)
def test_enable_vgw_route_propagation_invalid_parameters(self):
self.set_mock_db_items(fakes.DB_VPN_GATEWAY_1)
self.assert_execution_error(
'InvalidRouteTableID.NotFound', 'EnableVgwRoutePropagation',
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
'GatewayId': fakes.ID_EC2_VPN_GATEWAY_1})
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_1)
self.assert_execution_error(
'InvalidVpnGatewayID.NotFound', 'EnableVgwRoutePropagation',
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
'GatewayId': fakes.ID_EC2_VPN_GATEWAY_1})
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_1, fakes.DB_VPN_GATEWAY_2)
self.assert_execution_error(
'Gateway.NotAttached', 'EnableVgwRoutePropagation',
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
'GatewayId': fakes.ID_EC2_VPN_GATEWAY_2})
self.set_mock_db_items(
fakes.DB_ROUTE_TABLE_1,
tools.update_dict(fakes.DB_VPN_GATEWAY_2,
{'vpc_id': fakes.ID_EC2_VPC_2}))
self.assert_execution_error(
'Gateway.NotAttached', 'EnableVgwRoutePropagation',
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_1,
'GatewayId': fakes.ID_EC2_VPN_GATEWAY_2})
def test_disable_vgw_route_propagation(self):
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_2)
resp = self.execute('DisableVgwRoutePropagation',
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
'GatewayId': fakes.ID_EC2_VPN_GATEWAY_1})
self.assertEqual({'return': True}, resp)
self.db_api.update_item.assert_called_once_with(
mock.ANY, tools.purge_dict(fakes.DB_ROUTE_TABLE_2,
('propagating_gateways',)))
self.db_api.reset_mock()
db_route_table_2 = copy.deepcopy(fakes.DB_ROUTE_TABLE_2)
db_route_table_2['propagating_gateways'].append(
fakes.ID_EC2_VPN_GATEWAY_2)
self.set_mock_db_items(db_route_table_2)
resp = self.execute('DisableVgwRoutePropagation',
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
'GatewayId': fakes.ID_EC2_VPN_GATEWAY_2})
self.assertEqual({'return': True}, resp)
self.db_api.update_item.assert_called_once_with(
mock.ANY, fakes.DB_ROUTE_TABLE_2)
def test_disable_vgw_route_propagation_idempotent(self):
self.set_mock_db_items(fakes.DB_ROUTE_TABLE_2)
resp = self.execute('DisableVgwRoutePropagation',
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
'GatewayId': fakes.ID_EC2_VPN_GATEWAY_2})
self.assertEqual({'return': True}, resp)
self.assertFalse(self.db_api.update_item.called)
def test_disable_vgw_route_propagation_invalid_parameters(self):
self.set_mock_db_items()
self.assert_execution_error(
'InvalidRouteTableID.NotFound', 'DisableVgwRoutePropagation',
{'RouteTableId': fakes.ID_EC2_ROUTE_TABLE_2,
'GatewayId': fakes.ID_EC2_VPN_GATEWAY_2})
@mock.patch('ec2api.api.route_table._update_subnet_routes')
def test_associate_route_table(self, routes_updater):
self.set_mock_db_items(fakes.DB_VPC_1, fakes.DB_ROUTE_TABLE_1,
@ -592,7 +692,8 @@ class RouteTableTestCase(base.ApiTestCase):
fakes.DB_ROUTE_TABLE_3, fakes.DB_SUBNET_1, fakes.DB_SUBNET_2,
fakes.DB_VPC_1, fakes.DB_VPC_2, fakes.DB_IGW_1, fakes.DB_IGW_2,
fakes.DB_NETWORK_INTERFACE_1, fakes.DB_NETWORK_INTERFACE_2,
fakes.DB_INSTANCE_1, fakes.DB_VPN_GATEWAY_1)
fakes.DB_INSTANCE_1, fakes.DB_VPN_GATEWAY_1,
fakes.DB_VPN_CONNECTION_1)
self.nova.servers.get.return_value = (
mock.NonCallableMock(status='ACTIVE'))
@ -600,7 +701,8 @@ class RouteTableTestCase(base.ApiTestCase):
self.assertThat(resp['routeTableSet'],
matchers.ListMatches([fakes.EC2_ROUTE_TABLE_1,
fakes.EC2_ROUTE_TABLE_2,
fakes.EC2_ROUTE_TABLE_3]))
fakes.EC2_ROUTE_TABLE_3],
orderless_lists=True))
resp = self.execute('DescribeRouteTables',
{'RouteTableId.1': fakes.ID_EC2_ROUTE_TABLE_1})
@ -690,6 +792,7 @@ class RouteTableTestCase(base.ApiTestCase):
'main': False})
ec2_route_table_2 = copy.deepcopy(fakes.EC2_ROUTE_TABLE_2)
ec2_route_table_2['routeSet'][1]['state'] = 'blackhole'
del ec2_route_table_2['routeSet'][2]
ec2_route_table_2['routeSet'][2]['state'] = 'blackhole'
ec2_route_table_2['routeSet'].append({
'destinationCidrBlock': '192.168.88.0/24',
@ -718,6 +821,68 @@ class RouteTableTestCase(base.ApiTestCase):
matchers.ListMatches([ec2_route_table_1,
ec2_route_table_2]))
def test_format_route_table(self):
id_db_ec2_vpn_gateway_3 = fakes.random_ec2_id('vgw')
db_route_table_1 = tools.update_dict(
fakes.DB_ROUTE_TABLE_1,
{'propagating_gateways': [fakes.ID_EC2_VPN_GATEWAY_1,
fakes.ID_EC2_VPN_GATEWAY_2,
id_db_ec2_vpn_gateway_3]})
db_route_table_1['routes'].extend(
[{'gateway_id': fakes.ID_EC2_VPN_GATEWAY_1,
'destination_cidr_block': fakes.CIDR_VPN_1_STATIC},
{'gateway_id': fakes.ID_EC2_VPN_GATEWAY_2,
'destination_cidr_block': '192.168.201.0/24'}])
vpn_connection_3 = tools.update_dict(
fakes.DB_VPN_CONNECTION_1,
{'customer_gateway_id': fakes.random_ec2_id('cgw')})
vpn_connection_3['cidrs'].append('192.168.120.0/24')
ec2_route_table_1 = tools.patch_dict(
fakes.EC2_ROUTE_TABLE_1,
{'propagatingVgwSet': [{'gatewayId': fakes.ID_EC2_VPN_GATEWAY_1},
{'gatewayId': fakes.ID_EC2_VPN_GATEWAY_2},
{'gatewayId': id_db_ec2_vpn_gateway_3}]},
('associationSet',))
ec2_route_table_1['routeSet'].extend(
[{'gatewayId': fakes.ID_EC2_VPN_GATEWAY_1,
'destinationCidrBlock': fakes.CIDR_VPN_1_STATIC,
'origin': 'CreateRoute',
'state': 'active'},
{'gatewayId': fakes.ID_EC2_VPN_GATEWAY_2,
'destinationCidrBlock': '192.168.201.0/24',
'origin': 'CreateRoute',
'state': 'blackhole'},
{'gatewayId': fakes.ID_EC2_VPN_GATEWAY_1,
'destinationCidrBlock': fakes.CIDR_VPN_1_PROPAGATED_1,
'origin': 'EnableVgwRoutePropagation',
'state': 'active'},
{'gatewayId': fakes.ID_EC2_VPN_GATEWAY_1,
'destinationCidrBlock': '192.168.120.0/24',
'origin': 'EnableVgwRoutePropagation',
'state': 'active'},
{'gatewayId': fakes.ID_EC2_VPN_GATEWAY_2,
'destinationCidrBlock': fakes.CIDR_VPN_2_PROPAGATED_1,
'origin': 'EnableVgwRoutePropagation',
'state': 'blackhole'},
{'gatewayId': fakes.ID_EC2_VPN_GATEWAY_2,
'destinationCidrBlock': fakes.CIDR_VPN_2_PROPAGATED_2,
'origin': 'EnableVgwRoutePropagation',
'state': 'blackhole'}])
self.assertThat(
route_table._format_route_table(
self._create_context(), db_route_table_1,
gateways={gw['id']: gw
for gw in (fakes.DB_VPN_GATEWAY_1,
fakes.DB_VPN_GATEWAY_2,
fakes.DB_IGW_1)},
vpn_connections_by_gateway_id={
fakes.ID_EC2_VPN_GATEWAY_1: [fakes.DB_VPN_CONNECTION_1,
vpn_connection_3],
fakes.ID_EC2_VPN_GATEWAY_2: [fakes.DB_VPN_CONNECTION_2]}),
matchers.DictMatches(ec2_route_table_1, orderless_lists=True),
verbose=True)
def test_get_subnet_host_routes(self):
self.set_mock_db_items(
fakes.DB_NETWORK_INTERFACE_1, fakes.DB_NETWORK_INTERFACE_2,

View File

@ -16,6 +16,7 @@ import copy
import mock
from ec2api.api import vpn_connection
from ec2api.tests.unit import base
from ec2api.tests.unit import fakes
from ec2api.tests.unit import matchers
@ -46,7 +47,9 @@ class VpnConnectionTestCase(base.ApiTestCase):
self.assertThat(
resp,
matchers.DictMatches(
{'vpnConnection': fakes.EC2_VPN_CONNECTION_1}))
{'vpnConnection': (
tools.update_dict(fakes.EC2_VPN_CONNECTION_1,
{'routes': None}))}))
self.neutron.create_ikepolicy.assert_called_once_with(
{'ikepolicy': tools.purge_dict(fakes.OS_IKEPOLICY_1, ('id',))})
@ -54,7 +57,8 @@ class VpnConnectionTestCase(base.ApiTestCase):
{'ipsecpolicy': tools.purge_dict(fakes.OS_IPSECPOLICY_1, ('id',))})
self.db_api.add_item.assert_called_once_with(
mock.ANY, 'vpn',
tools.purge_dict(fakes.DB_VPN_CONNECTION_1, ('id',)),
tools.patch_dict(fakes.DB_VPN_CONNECTION_1,
{'cidrs': []}, ('id', )),
project_id=None)
self.neutron.update_ikepolicy.assert_called_once_with(
fakes.ID_OS_IKEPOLICY_1,
@ -267,3 +271,13 @@ class VpnConnectionTestCase(base.ApiTestCase):
self.check_tag_support(
'DescribeVpnConnections', 'vpnConnectionSet',
fakes.ID_EC2_VPN_CONNECTION_1, 'vpnConnectionId')
def test_format_vpn_connection(self):
db_vpn_connection_1 = tools.update_dict(fakes.DB_VPN_CONNECTION_1,
{'cidrs': []})
ec2_vpn_connection_1 = tools.update_dict(fakes.EC2_VPN_CONNECTION_1,
{'routes': [],
'vgwTelemetry': []})
self.assertEqual(
ec2_vpn_connection_1,
vpn_connection._format_vpn_connection(db_vpn_connection_1))