From 30a8b2176f200886c58dd3f0457c6e234f3a99c5 Mon Sep 17 00:00:00 2001
From: Adit Sarfaty <asarfaty@vmware.com>
Date: Mon, 29 Jan 2018 11:48:25 +0200
Subject: [PATCH] NSX-v3: VPNaaS supports only No-SNAT routers

Prevent the creation of a vpn service for a rotuer with SNAT enabled,
and prevent updating the SNAT to enabled for a router with a vpn service.

Change-Id: Ib6bfd9e019b2161245ba4951ef48e84314e0b923
---
 vmware_nsx/plugins/nsx_v3/plugin.py           |  8 +++++++
 .../services/vpnaas/nsxv3/ipsec_driver.py     | 20 ++++++++++++++++
 .../services/vpnaas/nsxv3/ipsec_validator.py  |  5 ++++
 .../unit/services/vpnaas/test_nsxv3_vpnaas.py | 24 +++++++++++++++----
 4 files changed, 53 insertions(+), 4 deletions(-)

diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py
index 40ee815a16..cae758d5c0 100644
--- a/vmware_nsx/plugins/nsx_v3/plugin.py
+++ b/vmware_nsx/plugins/nsx_v3/plugin.py
@@ -3444,6 +3444,14 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
                         context.elevated(), router_id,
                         gw_info['network_id'], fip['subnet_id'])
 
+        # VPNaaS need to be notified on router GW changes (there is currently
+        # no matching upstream registration for this)
+        if validators.is_attr_set(gw_info):
+            vpn_plugin = directory.get_plugin(plugin_const.VPN)
+            if vpn_plugin:
+                vpn_driver = vpn_plugin.drivers[vpn_plugin.default_provider]
+                vpn_driver.validate_router_gw_info(context, router_id, gw_info)
+
         nsx_router_id = None
         routes_added = []
         routes_removed = []
diff --git a/vmware_nsx/services/vpnaas/nsxv3/ipsec_driver.py b/vmware_nsx/services/vpnaas/nsxv3/ipsec_driver.py
index a21a8a3eeb..6a5f07936a 100644
--- a/vmware_nsx/services/vpnaas/nsxv3/ipsec_driver.py
+++ b/vmware_nsx/services/vpnaas/nsxv3/ipsec_driver.py
@@ -14,6 +14,7 @@
 #    under the License.
 
 import netaddr
+from oslo_config import cfg
 from oslo_log import log as logging
 from oslo_utils import excutils
 
@@ -21,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 exceptions as nexception
 from neutron_lib.plugins import directory
 from neutron_vpnaas.services.vpn import service_drivers
 
@@ -37,6 +39,11 @@ LOG = logging.getLogger(__name__)
 IPSEC = 'ipsec'
 
 
+class RouterWithSNAT(nexception.BadRequest):
+    message = _("Router %(router_id)s has a VPN service and cannot enable "
+                "SNAT")
+
+
 class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
 
     def __init__(self, service_plugin):
@@ -354,6 +361,19 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
         if local_ep_id:
             self._nsx_vpn.local_endpoint.delete(local_ep_id)
 
+    def validate_router_gw_info(self, context, router_id, gw_info):
+        """Upon router gw update - verify no-snat"""
+        # ckeck if this router has a vpn service
+        filters = {'router_id': [router_id],
+                   'status': [constants.ACTIVE]}
+        services = self.vpn_plugin.get_vpnservices(
+            context.elevated(), filters=filters)
+        if services:
+            # do not allow enable-snat
+            if (gw_info and
+                gw_info.get('enable_snat', cfg.CONF.enable_snat_by_default)):
+                raise RouterWithSNAT(router_id=router_id)
+
     def _get_session_rules(self, context, connection, vpnservice):
         # TODO(asarfaty): support vpn-endpoint-groups too
         peer_cidrs = connection['peer_cidrs']
diff --git a/vmware_nsx/services/vpnaas/nsxv3/ipsec_validator.py b/vmware_nsx/services/vpnaas/nsxv3/ipsec_validator.py
index 7d0640d308..f1d5cd319d 100644
--- a/vmware_nsx/services/vpnaas/nsxv3/ipsec_validator.py
+++ b/vmware_nsx/services/vpnaas/nsxv3/ipsec_validator.py
@@ -323,6 +323,11 @@ class IPsecV3Validator(vpn_validator.VpnReferenceValidator):
                     "with ACTIVE_STANDBY HA mode")
             raise nsx_exc.NsxVpnValidationError(details=msg)
 
+        # Verify that this is a no-snat router
+        if router_db.enable_snat:
+            msg = _("VPN is supported only for routers with disabled SNAT")
+            raise nsx_exc.NsxVpnValidationError(details=msg)
+
     def validate_vpnservice(self, context, vpnservice):
         """Called upon create/update of a service"""
 
diff --git a/vmware_nsx/tests/unit/services/vpnaas/test_nsxv3_vpnaas.py b/vmware_nsx/tests/unit/services/vpnaas/test_nsxv3_vpnaas.py
index bc3ab7a6be..2fa26dec1c 100644
--- a/vmware_nsx/tests/unit/services/vpnaas/test_nsxv3_vpnaas.py
+++ b/vmware_nsx/tests/unit/services/vpnaas/test_nsxv3_vpnaas.py
@@ -14,6 +14,7 @@
 #    under the License.
 import mock
 
+from neutron.db.models import l3 as l3_models
 from neutron_lib import context as n_ctx
 from neutron_vpnaas.tests import base
 
@@ -156,16 +157,31 @@ class TestDriverValidation(base.BaseTestCase):
         self.validator.validate_ipsec_policy(self.context, policy_info)
 
     def test_vpn_service_validation_router(self):
-        router = {'high_availability_mode': 'ACITVE_ACTIVE'}
+        db_router = l3_models.Router()
+        nsx_router = {'high_availability_mode': 'ACITVE_ACTIVE'}
+        db_router.enable_snat = False
         with mock.patch.object(self.validator.nsxlib.logical_router, 'get',
-                               return_value=router):
+                               return_value=nsx_router):
             self.assertRaises(nsx_exc.NsxVpnValidationError,
                               self.validator.validate_vpnservice,
                               self.context, self.vpn_service)
 
-        router = {'high_availability_mode': 'ACTIVE_STANDBY'}
+        nsx_router = {'high_availability_mode': 'ACTIVE_STANDBY'}
+        db_router.enable_snat = True
         with mock.patch.object(self.validator.nsxlib.logical_router, 'get',
-                               return_value=router):
+                               return_value=nsx_router),\
+            mock.patch.object(self.validator._core_plugin, '_get_router',
+                              return_value=db_router):
+            self.assertRaises(nsx_exc.NsxVpnValidationError,
+                              self.validator.validate_vpnservice,
+                              self.context, self.vpn_service)
+
+        nsx_router = {'high_availability_mode': 'ACTIVE_STANDBY'}
+        db_router.enable_snat = False
+        with mock.patch.object(self.validator.nsxlib.logical_router, 'get',
+                               return_value=nsx_router),\
+            mock.patch.object(self.validator._core_plugin, '_get_router',
+                              return_value=db_router):
             self.validator.validate_vpnservice(self.context, self.vpn_service)
 
     def _test_conn_validation(self, conn_params=None, success=True,