From c8ca315277f9da2074bf2a755159a9c7bf9ecd8a Mon Sep 17 00:00:00 2001 From: Feodor Tersin Date: Mon, 25 May 2015 12:24:45 +0300 Subject: [PATCH] Implement storage of VPN gateway route propagation attribute Change-Id: I09ea00b8160b92ed591bac8edd7d28e8f2df472a --- ec2api/api/cloud.py | 30 ++++ ec2api/api/internet_gateway.py | 2 +- ec2api/api/route_table.py | 79 +++++++++-- ec2api/exception.py | 2 +- ec2api/tests/unit/fakes.py | 14 +- ec2api/tests/unit/test_route_table.py | 169 ++++++++++++++++++++++- ec2api/tests/unit/test_vpn_connection.py | 18 ++- 7 files changed, 298 insertions(+), 16 deletions(-) diff --git a/ec2api/api/cloud.py b/ec2api/api/cloud.py index a3273be3..fb41d734 100644 --- a/ec2api/api/cloud.py +++ b/ec2api/api/cloud.py @@ -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. diff --git a/ec2api/api/internet_gateway.py b/ec2api/api/internet_gateway.py index 47053242..bc81c2f0 100644 --- a/ec2api/api/internet_gateway.py +++ b/ec2api/api/internet_gateway.py @@ -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 = ( diff --git a/ec2api/api/route_table.py b/ec2api/api/route_table.py index ba085314..43ca24de 100644 --- a/ec2api/api/route_table.py +++ b/ec2api/api/route_table.py @@ -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'] diff --git a/ec2api/exception.py b/ec2api/exception.py index 9055522f..fa92d67e 100644 --- a/ec2api/exception.py +++ b/ec2api/exception.py @@ -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): diff --git a/ec2api/tests/unit/fakes.py b/ec2api/tests/unit/fakes.py index 01fbfd63..064068f0 100644 --- a/ec2api/tests/unit/fakes.py +++ b/ec2api/tests/unit/fakes.py @@ -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}, } diff --git a/ec2api/tests/unit/test_route_table.py b/ec2api/tests/unit/test_route_table.py index aa912150..d9aa4696 100644 --- a/ec2api/tests/unit/test_route_table.py +++ b/ec2api/tests/unit/test_route_table.py @@ -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, diff --git a/ec2api/tests/unit/test_vpn_connection.py b/ec2api/tests/unit/test_vpn_connection.py index f920b7d2..cb01ebe6 100644 --- a/ec2api/tests/unit/test_vpn_connection.py +++ b/ec2api/tests/unit/test_vpn_connection.py @@ -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))