From f0fb8eab09eba960f39e7a6038800397799f0aca Mon Sep 17 00:00:00 2001
From: Adit Sarfaty <asarfaty@vmware.com>
Date: Mon, 5 Mar 2018 15:57:25 +0200
Subject: [PATCH] NSX-V3: verify router transport zones

The tier0 router should belong to the same transport nodes as
the subnets attached to the tier1 router which uses this tier0 as a GW

Change-Id: Idf216699ce110e8cd790779d3c4e63522800a845
---
 vmware_nsx/plugins/nsx_v3/plugin.py | 51 ++++++++++++++++++++++++-----
 1 file changed, 42 insertions(+), 9 deletions(-)

diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py
index f156752a77..5ad5a8c953 100644
--- a/vmware_nsx/plugins/nsx_v3/plugin.py
+++ b/vmware_nsx/plugins/nsx_v3/plugin.py
@@ -2077,19 +2077,22 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
             port_data.get(provider_sg.PROVIDER_SECURITYGROUPS) != [])
         return provider_sgs_specified
 
-    def _is_ens_tz_net(self, context, net_id):
-        #Check the host-switch-mode of the TZ connected to network
+    def _get_net_tz(self, context, net_id):
         mappings = nsx_db.get_nsx_switch_ids(context.session, net_id)
         if mappings:
             nsx_net_id = nsx_net_id = mappings[0]
             if nsx_net_id:
                 nsx_net = self.nsxlib.logical_switch.get(nsx_net_id)
-                if nsx_net and nsx_net.get('transport_zone_id'):
-                    # Check the mode of this TZ
-                    mode = self.nsxlib.transport_zone.get_host_switch_mode(
-                        nsx_net['transport_zone_id'])
-                    return (mode ==
-                            self.nsxlib.transport_zone.HOST_SWITCH_MODE_ENS)
+                return nsx_net.get('transport_zone_id')
+
+    def _is_ens_tz_net(self, context, net_id):
+        #Check the host-switch-mode of the TZ connected to network
+        tz_id = self._get_net_tz(context, net_id)
+        if tz_id:
+            # Check the mode of this TZ
+            mode = self.nsxlib.transport_zone.get_host_switch_mode(tz_id)
+            return (mode ==
+                    self.nsxlib.transport_zone.HOST_SWITCH_MODE_ENS)
         return False
 
     def _is_ens_tz_port(self, context, port_data):
@@ -3227,6 +3230,24 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
         else:
             return network.get(pnet.PHYSICAL_NETWORK)
 
+    def _validate_router_tz(self, context, tier0_uuid, subnets):
+        # make sure the related GW (Tier0 router) belongs to the same TZ
+        # as the subnets attached to the Tier1 router
+        if not subnets:
+            return
+        tier0_tzs = self.nsxlib.router.get_tier0_router_tz(tier0_uuid)
+        if not tier0_tzs:
+            return
+        for sub in subnets:
+            tz_uuid = self._get_net_tz(context, sub['network_id'])
+            if tz_uuid not in tier0_tzs:
+                msg = (_("Tier0 router %(rtr)s transport zone should match "
+                         "transport zone %(tz)s of the network %(net)s") % {
+                    'rtr': tier0_uuid,
+                    'tz': tz_uuid,
+                    'net': sub['network_id']})
+                raise n_exc.InvalidInput(error_message=msg)
+
     def _update_router_gw_info(self, context, router_id, info):
         router = self._get_router(context, router_id)
         org_tier0_uuid = self._get_tier0_uuid_by_router(context, router)
@@ -3304,7 +3325,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
         advertise_route_nat_flag = True if new_enable_snat else False
         advertise_route_connected_flag = True if not new_enable_snat else False
 
-        if add_no_dnat_rules or remove_no_dnat_rules or add_snat_rules:
+        if (add_no_dnat_rules or remove_no_dnat_rules or add_snat_rules or
+            add_router_link_port):
             subnets = self._find_router_subnets(context.elevated(),
                                                 router_id)
 
@@ -3317,6 +3339,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
             for subnet in subnets:
                 self._del_subnet_no_dnat_rule(context, nsx_router_id, subnet)
         if remove_router_link_port:
+            # remove the link port and reset the router edge cluster
             self.nsxlib.router.remove_router_link_port(
                 nsx_router_id, org_tier0_uuid)
             self.nsxlib.router.update_router_edge_cluster(
@@ -3326,6 +3349,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
                 self.nsxlib.router.update_router_transport_zone(
                     nsx_router_id, None)
         if add_router_link_port:
+            self._validate_router_tz(context, new_tier0_uuid, subnets)
             # First update edge cluster info for router
             edge_cluster_uuid = self._get_edge_cluster(new_tier0_uuid)
             self.nsxlib.router.update_router_edge_cluster(
@@ -3943,6 +3967,15 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
                             "must have a external network assigned.")
                     raise n_exc.InvalidInput(error_message=msg)
                 resource_type = nsxlib_consts.LROUTERPORT_CENTRALIZED
+
+            # IF this is an ENS case - check GW & subnets
+            subnets = self._find_router_subnets(context.elevated(),
+                                                router_id)
+            tier0_uuid = self._get_tier0_uuid_by_router(context.elevated(),
+                                                        router_db)
+            self._validate_router_tz(context.elevated(), tier0_uuid, subnets)
+
+            # create the interface ports on the NSX
             self.nsxlib.router.create_logical_router_intf_port_by_ls_id(
                 logical_router_id=nsx_router_id,
                 display_name=display_name,