diff --git a/vmware_nsx/plugins/nsx_p/plugin.py b/vmware_nsx/plugins/nsx_p/plugin.py index 7ecc252d0c..d876d9cb2d 100644 --- a/vmware_nsx/plugins/nsx_p/plugin.py +++ b/vmware_nsx/plugins/nsx_p/plugin.py @@ -1177,6 +1177,42 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): LOG.error(msg) raise n_exc.InvalidInput(error_message=msg) + def _validate_segment_subnets_num(self, context, net_id, subnet_data): + """Validate no multiple segment subnets on the NSX + The NSX cannot support more than 1 segment subnet of the same ip + version. This include dhcp subnets and overlay router interfaces + """ + if ('enable_dhcp' not in subnet_data or + not subnet_data.get('enable_dhcp')): + # NO DHCP so no new segment subnet + return + + ip_ver = subnet_data.get('ip_version', 4) + if ip_ver == 6: + # Since the plugin does not allow multiple ipv6 subnets, + # this can be ignored. + return + + overlay_net = self._is_overlay_network(context, net_id) + if not overlay_net: + # Since the plugin allows only 1 DHCP subnet, if this is not an + # overlay network, no problem. + return + + interface_ports = self._get_network_interface_ports( + context, net_id) + if interface_ports: + # Should have max 1 router interface per network + if_port = interface_ports[0] + if if_port['fixed_ips']: + if_subnet = interface_ports[0]['fixed_ips'][0]['subnet_id'] + if subnet_data.get('id') != if_subnet: + msg = (_("Can not create a DHCP subnet on network %(net)s " + "as another %(ver)s subnet is attached to a " + "router") % {'net': net_id, 'ver': ip_ver}) + LOG.error(msg) + raise n_exc.InvalidInput(error_message=msg) + @nsx_plugin_common.api_replay_mode_wrapper def create_subnet(self, context, subnet): if not self.use_policy_dhcp: @@ -1207,6 +1243,8 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): "subnet in network %s") % net_id) LOG.error(msg) raise n_exc.InvalidInput(error_message=msg) + self._validate_segment_subnets_num( + context, net_id, subnet['subnet']) # Create the neutron subnet. # Any failure from here and on will require rollback. @@ -1302,6 +1340,9 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): LOG.error(msg) raise n_exc.InvalidInput(error_message=msg) + self._validate_segment_subnets_num( + context, net_id, subnet_data) + updated_subnet = super(NsxPolicyPlugin, self).update_subnet( context, subnet_id, subnet) self._extension_manager.process_update_subnet( @@ -2527,6 +2568,38 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): cidr_prefix = int(subnet['cidr'].split('/')[1]) return "%s/%s" % (subnet['gateway_ip'], cidr_prefix) + def _validate_router_segment_subnets(self, context, network_id, + overlay_net, subnet): + """Validate that adding an interface to a router will not cause + multiple segments subnets which is not allowed + """ + if not overlay_net: + # Only interfaces for overlay networks create segment subnets + return + + if subnet.get('ip_version', 4) != 4: + # IPv6 is not relevant here since plugin allow only 1 ipv6 subnet + # per network + return + + if subnet['enable_dhcp']: + # This subnet is with dhcp, so there cannot be any other with dhcp + return + + if not self.use_policy_dhcp: + # Only policy DHCP creates segment subnets + return + + # Look for another subnet with DHCP + network = self._get_network(context.elevated(), network_id) + for subnet in network.subnets: + if subnet.enable_dhcp and subnet.ip_version == 4: + msg = (_("Can not add router interface on network %(net)s " + "as another %(ver)s subnet has enabled DHCP") % + {'net': network_id, 'ver': subnet.ip_version}) + LOG.error(msg) + raise n_exc.InvalidInput(error_message=msg) + @nsx_plugin_common.api_replay_mode_wrapper def add_router_interface(self, context, router_id, interface_info): # NOTE: In dual stack case, neutron would create a separate interface @@ -2564,6 +2637,10 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): self._validate_gw_overlap_interfaces(context, gw_network_id, [network_id]) + if subnet: + self._validate_router_segment_subnets(context, network_id, + overlay_net, subnet) + # Update the interface of the neutron router info = super(NsxPolicyPlugin, self).add_router_interface( context, router_id, interface_info) diff --git a/vmware_nsx/tests/unit/nsx_p/test_plugin.py b/vmware_nsx/tests/unit/nsx_p/test_plugin.py index d491ad3968..f4ac756718 100644 --- a/vmware_nsx/tests/unit/nsx_p/test_plugin.py +++ b/vmware_nsx/tests/unit/nsx_p/test_plugin.py @@ -2266,3 +2266,14 @@ class NsxPTestL3NatTestCase(NsxPTestL3NatTest, def test_nat_rules_firewall_match_external(self): self._test_nat_rules_firewall_match( False, pol_const.NAT_FIREWALL_MATCH_EXTERNAL) + + def test_router_interface_with_dhcp_subnet(self): + with self.router() as r,\ + self.network() as net,\ + self.subnet(cidr='20.0.0.0/24', network=net),\ + self.subnet(cidr='30.0.0.0/24', network=net, + enable_dhcp=False) as if_subnet: + self._router_interface_action( + 'add', r['router']['id'], + if_subnet['subnet']['id'], None, + expected_code=exc.HTTPBadRequest.code) diff --git a/vmware_nsx/tests/unit/nsx_p/test_policy_dhcp_metadata.py b/vmware_nsx/tests/unit/nsx_p/test_policy_dhcp_metadata.py index 02543a7114..1b2b371e34 100644 --- a/vmware_nsx/tests/unit/nsx_p/test_policy_dhcp_metadata.py +++ b/vmware_nsx/tests/unit/nsx_p/test_policy_dhcp_metadata.py @@ -844,6 +844,36 @@ class NsxPolicyDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin): ports[0]['network_id']) self.assertEqual(False, ports[0]['port_security_enabled']) + def test_create_dhcp_subnet_with_rtr_if(self): + # Test that cannot create a DHCP subnet if a router interface exists + dummy_port = {'fixed_ips': [{'subnet_id': 'dummy'}]} + with mock.patch.object(self.plugin, 'get_ports', + return_value=[dummy_port]),\ + self.network() as net: + subnet = self._make_subnet_data( + network_id=net['network']['id'], cidr='10.0.0.0/24', + tenant_id=net['network']['tenant_id']) + self.assertRaises( + n_exc.InvalidInput, self.plugin.create_subnet, + context.get_admin_context(), subnet) + + def test_update_dhcp_subnet_with_rtr_if(self): + # Test that cannot enable a DHCP on a subnet if a router interface + # exists + dummy_port = {'fixed_ips': [{'subnet_id': 'dummy'}]} + with mock.patch.object(self.plugin, 'get_ports', + return_value=[dummy_port]),\ + self.network() as net: + subnet = self._make_subnet_data( + network_id=net['network']['id'], cidr='10.0.0.0/24', + tenant_id=net['network']['tenant_id'], enable_dhcp=False) + neutron_subnet = self.plugin.create_subnet( + context.get_admin_context(), subnet) + self.assertRaises( + n_exc.InvalidInput, self.plugin.update_subnet, + context.get_admin_context(), neutron_subnet['id'], + {'subnet': {'enable_dhcp': True}}) + class NsxPolicyMetadataTestCase(test_plugin.NsxPPluginTestCaseMixin): """Test native metadata config when using MP MDProxy"""