NSX-v3 VPNaaS: Use a local address from the external network

The local address of the local endpoint for the VPN should be an unused
address on the external GW network of the Tier1 router
(and not the GW address itself).
To make sure this IP will not be used for anything else, a neutron port is
created.
The port will be deleted once the router (or its gw) is deleted.
The ip will be used for all the vpn services & connection on this tier1 router.

Change-Id: If956fd08f5c9cfde5cba9326c18d1d489c47a505
This commit is contained in:
Adit Sarfaty 2018-02-15 11:13:16 +02:00
parent 5d6e3ee194
commit d97a333d21
4 changed files with 63 additions and 42 deletions

View File

@ -2498,6 +2498,12 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
self.nsxlib.ns_group.update_lport(
context, lport_id, nsx_origial, nsx_updated)
def base_create_port(self, context, port):
neutron_db = super(NsxV3Plugin, self).create_port(context, port)
self._extension_manager.process_create_port(
context, port['port'], neutron_db)
return neutron_db
def create_port(self, context, port, l2gw_port_check=False):
port_data = port['port']
dhcp_opts = port_data.get(ext_edo.EXTRADHCPOPTS)
@ -2518,9 +2524,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
self._assert_on_port_admin_state(
port_data, port_data.get('device_owner'))
neutron_db = super(NsxV3Plugin, self).create_port(context, port)
self._extension_manager.process_create_port(
context, port_data, neutron_db)
neutron_db = self.base_create_port(context, port)
port["port"].update(neutron_db)
(is_psec_on, has_ip, sgids, psgids) = (

View File

@ -22,6 +22,7 @@ from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from neutron_lib import constants
from neutron_lib import context as n_context
from neutron_lib import exceptions as nexception
from neutron_lib.plugins import directory
from neutron_vpnaas.services.vpn import service_drivers
@ -37,6 +38,7 @@ from vmware_nsxlib.v3 import vpn_ipsec
LOG = logging.getLogger(__name__)
IPSEC = 'ipsec'
VPN_PORT_OWNER = constants.DEVICE_OWNER_NEUTRON_PREFIX + 'vpnservice'
class RouterWithSNAT(nexception.BadRequest):
@ -348,18 +350,32 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
return local_ep_id
# create a new one
local_addr = self._get_router_ext_gw(context, router_id)
local_addr = vpnservice['external_v4_ip']
nsx_service_id = self._get_nsx_vpn_service(context, vpnservice)
local_ep_id = self._create_local_endpoint(
context, local_addr, nsx_service_id, router_id)
return local_ep_id
def _find_vpn_service_port(self, context, router_id):
"""Look for the neutron port created for the vpnservice of a router"""
filters = {'device_id': [router_id],
'device_owner': [VPN_PORT_OWNER]}
ports = self.l3_plugin.get_ports(context, filters=filters)
if ports:
return ports[0]
def _delete_local_endpoint(self, resource, event, trigger, **kwargs):
"""Upon router deletion / gw removal delete the matching endpoint"""
router_id = kwargs.get('router_id')
# delete the local endpoint from the NSX
local_ep_id = self._search_local_endpint(router_id)
if local_ep_id:
self._nsx_vpn.local_endpoint.delete(local_ep_id)
# delete the neutron port with this IP
ctx = n_context.get_admin_context()
port = self._find_vpn_service_port(ctx, router_id)
if port:
self.l3_plugin.delete_port(ctx, port['id'])
def validate_router_gw_info(self, context, router_id, gw_info):
"""Upon router gw update - verify no-snat"""
@ -568,22 +584,6 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
# No service updates. No need to update router advertisement rules
def _get_gateway_ips(self, router):
"""Obtain the IPv4 and/or IPv6 GW IP for the router.
If there are multiples, (arbitrarily) use the first one.
"""
v4_ip = v6_ip = None
for fixed_ip in router.gw_port['fixed_ips']:
addr = fixed_ip['ip_address']
vers = netaddr.IPAddress(addr).version
if vers == 4:
if v4_ip is None:
v4_ip = addr
elif v6_ip is None:
v6_ip = addr
return v4_ip, v6_ip
def _create_vpn_service(self, tier0_uuid):
try:
service = self._nsx_vpn.service.create(
@ -635,29 +635,52 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
tier0_uuid = self._get_tier0_uuid(context, router_id)
return self._find_vpn_service(tier0_uuid)
def _get_service_local_address(self, context, vpnservice):
"""Find/Allocate a port on the external network
to save the ip to be used as the local ip of this service
"""
router_id = vpnservice.router['id']
# check if this router already have an IP
port = self._find_vpn_service_port(context, router_id)
if not port:
# create a new port, on the external network of the router
ext_net = vpnservice.router.gw_port['network_id']
port_data = {
'port': {
'network_id': ext_net,
'name': None,
'admin_state_up': True,
'device_id': vpnservice.router['id'],
'device_owner': VPN_PORT_OWNER,
'fixed_ips': constants.ATTR_NOT_SPECIFIED,
'mac_address': constants.ATTR_NOT_SPECIFIED,
'port_security_enabled': False,
'tenant_id': vpnservice['tenant_id']}}
port = self.l3_plugin.base_create_port(context, port_data)
# return the port ip as the local address
return port['fixed_ips'][0]['ip_address']
def create_vpnservice(self, context, vpnservice):
#TODO(asarfaty) support vpn-endpoint-group-create for local & peer
# cidrs too
LOG.debug('Creating VPN service %(vpn)s', {'vpn': vpnservice})
vpnservice_id = vpnservice['id']
vpnservice = self.service_plugin._get_vpnservice(context,
vpnservice_id)
try:
self.validator.validate_vpnservice(context, vpnservice)
local_address = self._get_service_local_address(
context, vpnservice)
except Exception:
with excutils.save_and_reraise_exception():
# Rolling back change on the neutron
self.service_plugin.delete_vpnservice(context, vpnservice_id)
vpnservice = self.service_plugin._get_vpnservice(context,
vpnservice_id)
v4_ip, v6_ip = self._get_gateway_ips(vpnservice.router)
if v4_ip:
vpnservice['external_v4_ip'] = v4_ip
if v6_ip:
vpnservice['external_v6_ip'] = v6_ip
vpnservice['external_v4_ip'] = local_address
self.service_plugin.set_external_tunnel_ips(context,
vpnservice_id,
v4_ip=v4_ip, v6_ip=v6_ip)
v4_ip=local_address)
self._create_vpn_service_if_needed(context, vpnservice)
def update_vpnservice(self, context, old_vpnservice, vpnservice):

View File

@ -184,7 +184,7 @@ class IPsecV3Validator(vpn_validator.VpnReferenceValidator):
"as connection %(id)s") % {'local': local_cidrs,
'peer': peer_cidrs,
'id': conn['id']})
raise nsx_exc.NsxVpnValidationError(details=msg)
raise nsx_exc.NsxVpnValidationError(details=msg)
def _check_unique_addresses(self, context, ipsec_site_conn):
"""Validate no repeating local & peer addresses (of all tenants)
@ -302,12 +302,13 @@ class IPsecV3Validator(vpn_validator.VpnReferenceValidator):
#TODO(asarfaty): IPv6 is not yet supported. add validation
def _get_service_local_address(self, context, vpnservice_id):
"""The local address of the service is assigned upon creation
From the attached external network pool
"""
vpnservice = self.vpn_plugin._get_vpnservice(context,
vpnservice_id)
router_id = vpnservice['router_id']
router_db = self._core_plugin.get_router(context, router_id)
gw = router_db['external_gateway_info']
return gw['external_fixed_ips'][0]['ip_address']
return vpnservice['external_v4_ip']
def _validate_router(self, context, router_id):
# Verify that the router gw network is connected to an active-standby

View File

@ -192,12 +192,6 @@ class TestDriverValidation(base.BaseTestCase):
if router_subnets is None:
router_subnets = []
def mock_get_router(context, router_id):
return {'id': router_id,
'external_gateway_info': {
'external_fixed_ips': [{
'ip_address': '1.1.1.%s' % router_id}]}}
def mock_get_routers(context, filters=None, fields=None):
return [{'id': 'no-snat',
'external_gateway_info': {'enable_snat': False}}]
@ -211,6 +205,7 @@ class TestDriverValidation(base.BaseTestCase):
return {'id': service_id,
'router_id': service_id,
'subnet_id': 'dummy_subnet',
'external_v4_ip': '1.1.1.%s' % service_id,
'subnet': {'id': 'dummy_subnet',
'cidr': subnet_cidr}}
@ -223,8 +218,6 @@ class TestDriverValidation(base.BaseTestCase):
with mock.patch.object(self.validator.vpn_plugin, '_get_vpnservice',
side_effect=mock_get_service),\
mock.patch.object(self.validator._core_plugin, 'get_router',
side_effect=mock_get_router),\
mock.patch.object(self.validator._core_plugin, 'get_routers',
side_effect=mock_get_routers),\
mock.patch.object(self.validator._core_plugin,