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:
parent
5d6e3ee194
commit
d97a333d21
@ -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) = (
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user