da6f97f291
remove ca_certs using for incoming requests do small refactoring for keystone client using Change-Id: I83dbb71248835cfc361eca691647deaa99023c8a
503 lines
21 KiB
Python
503 lines
21 KiB
Python
# Copyright 2014
|
|
# The Cloudscaling Group, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import random
|
|
import string
|
|
|
|
from lxml import etree
|
|
import netaddr
|
|
from neutronclient.common import exceptions as neutron_exception
|
|
from oslo_log import log as logging
|
|
import six
|
|
|
|
from ec2api.api import common
|
|
from ec2api.api import ec2utils
|
|
from ec2api import clients
|
|
from ec2api.db import api as db_api
|
|
from ec2api import exception
|
|
from ec2api.i18n import _
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
"""VPN connections related API implementation
|
|
"""
|
|
|
|
|
|
Validator = common.Validator
|
|
|
|
|
|
SHARED_KEY_CHARS = string.ascii_letters + '_.' + string.digits
|
|
AWS_MSS = 1387
|
|
MTU_MSS_DELTA = 40 # 20 byte IP and 20 byte TCP headers
|
|
|
|
|
|
def create_vpn_connection(context, customer_gateway_id, vpn_gateway_id,
|
|
type, options=None):
|
|
if not options or options.get('static_routes_only') is not True:
|
|
raise exception.Unsupported('BGP dynamic routing is unsupported')
|
|
customer_gateway = ec2utils.get_db_item(context, customer_gateway_id)
|
|
vpn_gateway = ec2utils.get_db_item(context, vpn_gateway_id)
|
|
vpn_connection = next(
|
|
(vpn for vpn in db_api.get_items(context, 'vpn')
|
|
if vpn['customer_gateway_id'] == customer_gateway_id),
|
|
None)
|
|
if vpn_connection:
|
|
if vpn_connection['vpn_gateway_id'] == vpn_gateway_id:
|
|
ec2_vpn_connections = describe_vpn_connections(
|
|
context, vpn_connection_id=[vpn_connection['id']])
|
|
return {
|
|
'vpnConnection': ec2_vpn_connections['vpnConnectionSet'][0]}
|
|
else:
|
|
raise exception.InvalidCustomerGatewayDuplicateIpAddress()
|
|
neutron = clients.neutron(context)
|
|
with common.OnCrashCleaner() as cleaner:
|
|
os_ikepolicy = {'ike_version': 'v1',
|
|
'auth_algorithm': 'sha1',
|
|
'encryption_algorithm': 'aes-128',
|
|
'pfs': 'group2',
|
|
'phase1_negotiation_mode': 'main',
|
|
'lifetime': {'units': 'seconds',
|
|
'value': 28800}}
|
|
os_ikepolicy = neutron.create_ikepolicy(
|
|
{'ikepolicy': os_ikepolicy})['ikepolicy']
|
|
cleaner.addCleanup(neutron.delete_ikepolicy, os_ikepolicy['id'])
|
|
|
|
os_ipsecpolicy = {'transform_protocol': 'esp',
|
|
'auth_algorithm': 'sha1',
|
|
'encryption_algorithm': 'aes-128',
|
|
'pfs': 'group2',
|
|
'encapsulation_mode': 'tunnel',
|
|
'lifetime': {'units': 'seconds',
|
|
'value': 3600}}
|
|
os_ipsecpolicy = neutron.create_ipsecpolicy(
|
|
{'ipsecpolicy': os_ipsecpolicy})['ipsecpolicy']
|
|
cleaner.addCleanup(neutron.delete_ipsecpolicy, os_ipsecpolicy['id'])
|
|
|
|
psk = ''.join(random.choice(SHARED_KEY_CHARS) for _x in range(32))
|
|
vpn_connection = db_api.add_item(
|
|
context, 'vpn',
|
|
{'customer_gateway_id': customer_gateway['id'],
|
|
'vpn_gateway_id': vpn_gateway['id'],
|
|
'pre_shared_key': psk,
|
|
'os_ikepolicy_id': os_ikepolicy['id'],
|
|
'os_ipsecpolicy_id': os_ipsecpolicy['id'],
|
|
'cidrs': [],
|
|
'os_ipsec_site_connections': {}})
|
|
cleaner.addCleanup(db_api.delete_item, context, vpn_connection['id'])
|
|
|
|
neutron.update_ikepolicy(
|
|
os_ikepolicy['id'], {'ikepolicy': {'name': vpn_connection['id']}})
|
|
neutron.update_ipsecpolicy(
|
|
os_ipsecpolicy['id'],
|
|
{'ipsecpolicy': {'name': vpn_connection['id']}})
|
|
|
|
_reset_vpn_connections(context, neutron, cleaner,
|
|
vpn_gateway, vpn_connections=[vpn_connection])
|
|
|
|
ec2_vpn_connections = describe_vpn_connections(
|
|
context, vpn_connection_id=[vpn_connection['id']])
|
|
return {
|
|
'vpnConnection': ec2_vpn_connections['vpnConnectionSet'][0]}
|
|
|
|
|
|
def create_vpn_connection_route(context, vpn_connection_id,
|
|
destination_cidr_block):
|
|
vpn_connection = ec2utils.get_db_item(context, vpn_connection_id)
|
|
if destination_cidr_block in vpn_connection['cidrs']:
|
|
return True
|
|
neutron = clients.neutron(context)
|
|
vpn_gateway = db_api.get_item_by_id(context,
|
|
vpn_connection['vpn_gateway_id'])
|
|
with common.OnCrashCleaner() as cleaner:
|
|
_add_cidr_to_vpn_connection_item(context, vpn_connection,
|
|
destination_cidr_block)
|
|
cleaner.addCleanup(_remove_cidr_from_vpn_connection_item,
|
|
context, vpn_connection, destination_cidr_block)
|
|
|
|
_reset_vpn_connections(context, neutron, cleaner,
|
|
vpn_gateway, vpn_connections=[vpn_connection])
|
|
|
|
return True
|
|
|
|
|
|
def delete_vpn_connection_route(context, vpn_connection_id,
|
|
destination_cidr_block):
|
|
vpn_connection = ec2utils.get_db_item(context, vpn_connection_id)
|
|
if destination_cidr_block not in vpn_connection['cidrs']:
|
|
raise exception.InvalidRouteNotFound(
|
|
_('The specified route %(destination_cidr_block)s does not exist')
|
|
% {'destination_cidr_block': destination_cidr_block})
|
|
neutron = clients.neutron(context)
|
|
vpn_gateway = db_api.get_item_by_id(context,
|
|
vpn_connection['vpn_gateway_id'])
|
|
with common.OnCrashCleaner() as cleaner:
|
|
_remove_cidr_from_vpn_connection_item(context, vpn_connection,
|
|
destination_cidr_block)
|
|
cleaner.addCleanup(_add_cidr_to_vpn_connection_item,
|
|
context, vpn_connection, destination_cidr_block)
|
|
|
|
_reset_vpn_connections(context, neutron, cleaner,
|
|
vpn_gateway, vpn_connections=[vpn_connection])
|
|
|
|
return True
|
|
|
|
|
|
def delete_vpn_connection(context, vpn_connection_id):
|
|
vpn_connection = ec2utils.get_db_item(context, vpn_connection_id)
|
|
with common.OnCrashCleaner() as cleaner:
|
|
db_api.delete_item(context, vpn_connection['id'])
|
|
cleaner.addCleanup(db_api.restore_item, context, 'vpn', vpn_connection)
|
|
neutron = clients.neutron(context)
|
|
_stop_vpn_connection(neutron, vpn_connection)
|
|
try:
|
|
neutron.delete_ipsecpolicy(vpn_connection['os_ipsecpolicy_id'])
|
|
except neutron_exception.Conflict as ex:
|
|
LOG.warning(
|
|
_('Failed to delete ipsecoplicy %(os_id)s during deleting '
|
|
'VPN connection %(id)s. Reason: %(reason)s'),
|
|
{'id': vpn_connection['id'],
|
|
'os_id': vpn_connection['os_ipsecpolicy_id'],
|
|
'reason': ex.message})
|
|
except neutron_exception.NotFound:
|
|
pass
|
|
try:
|
|
neutron.delete_ikepolicy(vpn_connection['os_ikepolicy_id'])
|
|
except neutron_exception.Conflict as ex:
|
|
LOG.warning(
|
|
_('Failed to delete ikepolicy %(os_id)s during deleting '
|
|
'VPN connection %(id)s. Reason: %(reason)s'),
|
|
{'id': vpn_connection['id'],
|
|
'os_id': vpn_connection['os_ikepolicy_id'],
|
|
'reason': ex.message})
|
|
except neutron_exception.NotFound:
|
|
pass
|
|
return True
|
|
|
|
|
|
def describe_vpn_connections(context, vpn_connection_id=None, filter=None):
|
|
formatted_vpn_connections = VpnConnectionDescriber().describe(
|
|
context, ids=vpn_connection_id, filter=filter)
|
|
return {'vpnConnectionSet': formatted_vpn_connections}
|
|
|
|
|
|
class VpnConnectionDescriber(common.TaggableItemsDescriber,
|
|
common.NonOpenstackItemsDescriber):
|
|
|
|
KIND = 'vpn'
|
|
FILTER_MAP = {'customer-gateway-configuration': (
|
|
'customerGatewayConfiguration'),
|
|
'customer-gateway-id': 'customerGatewayId',
|
|
'state': 'state',
|
|
'option.static-routes-only': ('options', 'staticRoutesOnly'),
|
|
'route.destination-cidr-block': ['routes',
|
|
'destinationCidrBlock'],
|
|
'type': 'type',
|
|
'vpn-connection-id': 'vpnConnectionId',
|
|
'vpn-gateway-id': 'vpnGatewayId'}
|
|
|
|
def get_db_items(self):
|
|
self.customer_gateways = {
|
|
cgw['id']: cgw
|
|
for cgw in db_api.get_items(self.context, 'cgw')}
|
|
neutron = clients.neutron(self.context)
|
|
self.os_ikepolicies = {
|
|
ike['id']: ike
|
|
for ike in neutron.list_ikepolicies(
|
|
tenant_id=self.context.project_id)['ikepolicies']}
|
|
self.os_ipsecpolicies = {
|
|
ipsec['id']: ipsec
|
|
for ipsec in neutron.list_ipsecpolicies(
|
|
tenant_id=self.context.project_id)['ipsecpolicies']}
|
|
self.os_ipsec_site_connections = {
|
|
conn['id']: conn
|
|
for conn in neutron.list_ipsec_site_connections(
|
|
tenant_id=self.context.project_id)['ipsec_site_connections']}
|
|
self.external_ips = _get_vpn_gateways_external_ips(
|
|
self.context, neutron)
|
|
return super(VpnConnectionDescriber, self).get_db_items()
|
|
|
|
def format(self, vpn_connection):
|
|
return _format_vpn_connection(
|
|
vpn_connection, self.customer_gateways, self.os_ikepolicies,
|
|
self.os_ipsecpolicies, self.os_ipsec_site_connections,
|
|
self.external_ips)
|
|
|
|
|
|
def _format_vpn_connection(vpn_connection, customer_gateways, os_ikepolicies,
|
|
os_ipsecpolicies, os_ipsec_site_connections,
|
|
external_ips):
|
|
config_dict = _format_customer_config(
|
|
vpn_connection, customer_gateways, os_ikepolicies, os_ipsecpolicies,
|
|
os_ipsec_site_connections, external_ips)
|
|
config = ec2utils.dict_to_xml(config_dict, 'vpn_connection')
|
|
config.attrib['id'] = vpn_connection['id']
|
|
config_str = etree.tostring(config, xml_declaration=True, encoding='UTF-8',
|
|
pretty_print=True)
|
|
return {'vpnConnectionId': vpn_connection['id'],
|
|
'vpnGatewayId': vpn_connection['vpn_gateway_id'],
|
|
'customerGatewayId': vpn_connection['customer_gateway_id'],
|
|
'state': 'available',
|
|
'type': 'ipsec.1',
|
|
'routes': [{'destinationCidrBlock': cidr,
|
|
'state': 'available'}
|
|
for cidr in vpn_connection['cidrs']],
|
|
'vgwTelemetry': [],
|
|
'options': {'staticRoutesOnly': True},
|
|
'customerGatewayConfiguration': config_str}
|
|
|
|
|
|
def _format_customer_config(vpn_connection, customer_gateways, os_ikepolicies,
|
|
os_ipsecpolicies, os_ipsec_site_connections,
|
|
external_ips):
|
|
customer_gateway = customer_gateways[vpn_connection['customer_gateway_id']]
|
|
os_connections_ids = vpn_connection['os_ipsec_site_connections'].values()
|
|
if os_connections_ids:
|
|
os_ipsec_site_connection = next(
|
|
(os_ipsec_site_connections[conn_id]
|
|
for conn_id in os_connections_ids
|
|
if os_ipsec_site_connections.get(conn_id)),
|
|
None)
|
|
else:
|
|
os_ipsec_site_connection = None
|
|
|
|
# TODO(ft): figure out and add to the output tunnel internal addresses
|
|
config_dict = {
|
|
'customer_gateway_id': vpn_connection['customer_gateway_id'],
|
|
'vpn_gateway_id': vpn_connection['vpn_gateway_id'],
|
|
'vpn_connection_type': 'ipsec.1',
|
|
'vpn_connection_attributes': 'NoBGPVPNConnection',
|
|
'ipsec_tunnel': {
|
|
'customer_gateway': {
|
|
'tunnel_outside_address': {
|
|
'ip_address': (
|
|
os_ipsec_site_connection['peer_address']
|
|
if os_ipsec_site_connection else
|
|
customer_gateway['ip_address'])}},
|
|
'vpn_gateway': {
|
|
'tunnel_outside_address': {
|
|
'ip_address': external_ips.get(
|
|
vpn_connection['vpn_gateway_id'])}}},
|
|
}
|
|
os_ikepolicy = os_ikepolicies.get(vpn_connection['os_ikepolicy_id'])
|
|
if os_ikepolicy:
|
|
config_dict['ipsec_tunnel']['ike'] = {
|
|
'authentication_protocol': os_ikepolicy['auth_algorithm'],
|
|
'encryption_protocol': os_ikepolicy['encryption_algorithm'],
|
|
'lifetime': os_ikepolicy['lifetime']['value'],
|
|
'perfect_forward_secrecy': os_ikepolicy['pfs'],
|
|
'mode': os_ikepolicy['phase1_negotiation_mode'],
|
|
'pre_shared_key': (
|
|
os_ipsec_site_connection['psk']
|
|
if os_ipsec_site_connection else
|
|
vpn_connection['pre_shared_key']),
|
|
}
|
|
os_ipsecpolicy = os_ipsecpolicies.get(vpn_connection['os_ipsecpolicy_id'])
|
|
if os_ipsecpolicy:
|
|
config_dict['ipsec_tunnel']['ipsec'] = {
|
|
'protocol': os_ipsecpolicy['transform_protocol'],
|
|
'authentication_protocol': os_ipsecpolicy['auth_algorithm'],
|
|
'encryption_protocol': os_ipsecpolicy['encryption_algorithm'],
|
|
'lifetime': os_ipsecpolicy['lifetime']['value'],
|
|
'perfect_forward_secrecy': os_ipsecpolicy['pfs'],
|
|
'mode': os_ipsecpolicy['encapsulation_mode'],
|
|
'tcp_mss_adjustment': (
|
|
os_ipsec_site_connection['mtu'] - MTU_MSS_DELTA
|
|
if os_ipsec_site_connection else
|
|
AWS_MSS),
|
|
}
|
|
return config_dict
|
|
|
|
|
|
def _stop_vpn_connection(neutron, vpn_connection):
|
|
connection_ids = vpn_connection['os_ipsec_site_connections']
|
|
for os_connection_id in six.itervalues(connection_ids):
|
|
try:
|
|
neutron.delete_ipsec_site_connection(os_connection_id)
|
|
except neutron_exception.NotFound:
|
|
pass
|
|
|
|
|
|
def _stop_gateway_vpn_connections(context, neutron, cleaner, vpn_gateway):
|
|
def undo_vpn_connection(context, vpn_connection, connections_ids):
|
|
vpn_connection['os_ipsec_site_connections'] = connections_ids
|
|
db_api.update_item(context, vpn_connection)
|
|
|
|
for vpn_connection in db_api.get_items(context, 'vpn'):
|
|
if vpn_connection['vpn_gateway_id'] == vpn_gateway['id']:
|
|
_stop_vpn_connection(neutron, vpn_connection)
|
|
|
|
connection_ids = vpn_connection['os_ipsec_site_connections']
|
|
vpn_connection['os_ipsec_site_connections'] = {}
|
|
db_api.update_item(context, vpn_connection)
|
|
cleaner.addCleanup(undo_vpn_connection, context, vpn_connection,
|
|
connection_ids)
|
|
|
|
|
|
def _update_vpn_routes(context, neutron, cleaner, route_table, subnets):
|
|
vpn_gateway = ec2utils.get_attached_gateway(
|
|
context, route_table['vpc_id'], 'vgw')
|
|
if not vpn_gateway:
|
|
return
|
|
_reset_vpn_connections(context, neutron, cleaner, vpn_gateway,
|
|
route_tables=[route_table], subnets=subnets)
|
|
|
|
|
|
def _reset_vpn_connections(context, neutron, cleaner, vpn_gateway,
|
|
subnets=None, route_tables=None,
|
|
vpn_connections=None):
|
|
if not vpn_gateway['vpc_id']:
|
|
return
|
|
# TODO(ft): implement search filters in DB api
|
|
vpn_connections = (vpn_connections or
|
|
[vpn for vpn in db_api.get_items(context, 'vpn')
|
|
if vpn['vpn_gateway_id'] == vpn_gateway['id']])
|
|
if not vpn_connections:
|
|
return
|
|
subnets = (subnets or
|
|
[subnet for subnet in db_api.get_items(context, 'subnet')
|
|
if subnet['vpc_id'] == vpn_gateway['vpc_id']])
|
|
if not subnets:
|
|
return
|
|
vpc = db_api.get_item_by_id(context, vpn_gateway['vpc_id'])
|
|
customer_gateways = {cgw['id']: cgw
|
|
for cgw in db_api.get_items(context, 'cgw')}
|
|
route_tables = route_tables or db_api.get_items(context, 'rtb')
|
|
route_tables = {rtb['id']: rtb
|
|
for rtb in route_tables
|
|
if rtb['vpc_id'] == vpc['id']}
|
|
route_tables_cidrs = {}
|
|
for subnet in subnets:
|
|
route_table_id = subnet.get('route_table_id', vpc['route_table_id'])
|
|
if route_table_id not in route_tables_cidrs:
|
|
route_tables_cidrs[route_table_id] = (
|
|
_get_route_table_vpn_cidrs(route_tables[route_table_id],
|
|
vpn_gateway, vpn_connections))
|
|
cidrs = route_tables_cidrs[route_table_id]
|
|
for vpn_conn in vpn_connections:
|
|
if vpn_conn['id'] in cidrs:
|
|
_set_subnet_vpn(
|
|
context, neutron, cleaner, subnet, vpn_conn,
|
|
customer_gateways[vpn_conn['customer_gateway_id']],
|
|
cidrs[vpn_conn['id']])
|
|
else:
|
|
_delete_subnet_vpn(context, neutron, cleaner, subnet, vpn_conn)
|
|
|
|
|
|
def _set_subnet_vpn(context, neutron, cleaner, subnet, vpn_connection,
|
|
customer_gateway, cidrs):
|
|
subnets_connections = vpn_connection['os_ipsec_site_connections']
|
|
os_connection_id = subnets_connections.get(subnet['id'])
|
|
if os_connection_id:
|
|
# TODO(ft): restore original peer_cidrs on crash
|
|
neutron.update_ipsec_site_connection(
|
|
os_connection_id,
|
|
{'ipsec_site_connection': {'peer_cidrs': cidrs}})
|
|
else:
|
|
os_connection = {
|
|
'vpnservice_id': subnet['os_vpnservice_id'],
|
|
'ikepolicy_id': vpn_connection['os_ikepolicy_id'],
|
|
'ipsecpolicy_id': vpn_connection['os_ipsecpolicy_id'],
|
|
'peer_address': customer_gateway['ip_address'],
|
|
'peer_cidrs': cidrs,
|
|
'psk': vpn_connection['pre_shared_key'],
|
|
'name': '%s/%s' % (vpn_connection['id'], subnet['id']),
|
|
'peer_id': customer_gateway['ip_address'],
|
|
'mtu': AWS_MSS + MTU_MSS_DELTA,
|
|
'initiator': 'response-only',
|
|
}
|
|
os_connection = (neutron.create_ipsec_site_connection(
|
|
{'ipsec_site_connection': os_connection})
|
|
['ipsec_site_connection'])
|
|
cleaner.addCleanup(neutron.delete_ipsec_site_connection,
|
|
os_connection['id'])
|
|
|
|
_add_subnet_connection_to_vpn_connection_item(
|
|
context, vpn_connection, subnet['id'], os_connection['id'])
|
|
cleaner.addCleanup(_remove_subnet_connection_from_vpn_connection_item,
|
|
context, vpn_connection, subnet['id'])
|
|
|
|
|
|
def _delete_subnet_vpn(context, neutron, cleaner, subnet, vpn_connection):
|
|
subnets_connections = vpn_connection['os_ipsec_site_connections']
|
|
os_connection_id = subnets_connections.get(subnet['id'])
|
|
if not os_connection_id:
|
|
return
|
|
|
|
_remove_subnet_connection_from_vpn_connection_item(
|
|
context, vpn_connection, subnet['id'])
|
|
cleaner.addCleanup(_add_subnet_connection_to_vpn_connection_item,
|
|
context, vpn_connection, subnet['id'], os_connection_id)
|
|
try:
|
|
neutron.delete_ipsec_site_connection(os_connection_id)
|
|
except neutron_exception.NotFound:
|
|
pass
|
|
|
|
|
|
def _get_route_table_vpn_cidrs(route_table, vpn_gateway, vpn_connections):
|
|
static_cidrs = [route['destination_cidr_block']
|
|
for route in route_table['routes']
|
|
if route.get('gateway_id') == vpn_gateway['id']]
|
|
is_propagation_enabled = (
|
|
vpn_gateway['id'] in route_table.get('propagating_gateways', []))
|
|
vpn_cidrs = {}
|
|
for vpn in vpn_connections:
|
|
if is_propagation_enabled:
|
|
cidrs = list(set(static_cidrs + vpn['cidrs']))
|
|
else:
|
|
cidrs = static_cidrs
|
|
if cidrs:
|
|
vpn_cidrs[vpn['id']] = cidrs
|
|
return vpn_cidrs
|
|
|
|
|
|
def _get_vpn_gateways_external_ips(context, neutron):
|
|
vpcs = {vpc['id']: vpc
|
|
for vpc in db_api.get_items(context, 'vpc')}
|
|
external_ips = {}
|
|
routers = neutron.list_routers(
|
|
tenant_id=context.project_id)['routers']
|
|
for router in routers:
|
|
info = router['external_gateway_info']
|
|
if info:
|
|
for ip in info['external_fixed_ips']:
|
|
if netaddr.valid_ipv4(ip['ip_address']):
|
|
external_ips[router['id']] = ip['ip_address']
|
|
return {vgw['id']: external_ips.get(vpcs[vgw['vpc_id']]['os_id'])
|
|
for vgw in db_api.get_items(context, 'vgw')
|
|
if vgw['vpc_id']}
|
|
|
|
|
|
def _add_cidr_to_vpn_connection_item(context, vpn_connection, cidr):
|
|
vpn_connection['cidrs'].append(cidr)
|
|
db_api.update_item(context, vpn_connection)
|
|
|
|
|
|
def _remove_cidr_from_vpn_connection_item(context, vpn_connection, cidr):
|
|
vpn_connection['cidrs'].remove(cidr)
|
|
db_api.update_item(context, vpn_connection)
|
|
|
|
|
|
def _add_subnet_connection_to_vpn_connection_item(context, vpn_connection,
|
|
subnet_id, os_connection_id):
|
|
vpn_connection['os_ipsec_site_connections'][subnet_id] = os_connection_id
|
|
db_api.update_item(context, vpn_connection)
|
|
|
|
|
|
def _remove_subnet_connection_from_vpn_connection_item(context, vpn_connection,
|
|
subnet_id):
|
|
del vpn_connection['os_ipsec_site_connections'][subnet_id]
|
|
db_api.update_item(context, vpn_connection)
|