Merge "NSX|v3: Enforce address scopes for no-NAT routers"

This commit is contained in:
Jenkins 2017-07-03 05:34:47 +00:00 committed by Gerrit Code Review
commit 706154c181
4 changed files with 187 additions and 34 deletions

View File

@ -20,6 +20,7 @@ from neutron.db import address_scope_db
from neutron.db import api as db_api from neutron.db import api as db_api
from neutron.db import db_base_plugin_v2 from neutron.db import db_base_plugin_v2
from neutron.db import l3_db from neutron.db import l3_db
from neutron.extensions import address_scope as ext_address_scope
from neutron_lib.api.definitions import network as net_def from neutron_lib.api.definitions import network as net_def
from neutron_lib.api.definitions import port as port_def from neutron_lib.api.definitions import port as port_def
from neutron_lib.api.definitions import subnet as subnet_def from neutron_lib.api.definitions import subnet as subnet_def
@ -28,6 +29,7 @@ from neutron_lib import exceptions as n_exc
from neutron_lib.plugins import directory from neutron_lib.plugins import directory
from vmware_nsx._i18n import _ from vmware_nsx._i18n import _
from vmware_nsx.common import exceptions as nsx_exc
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -93,3 +95,34 @@ class NsxPluginBase(db_base_plugin_v2.NeutronDbPluginV2,
"has SNAT disabled") "has SNAT disabled")
raise n_exc.InvalidInput(error_message=msg) raise n_exc.InvalidInput(error_message=msg)
return router_id return router_id
def _get_network_address_scope(self, context, net_id):
network = self.get_network(context, net_id)
return network.get(ext_address_scope.IPV4_ADDRESS_SCOPE)
def _get_subnet_address_scope(self, context, subnet_id):
subnet = self.get_subnet(context, subnet_id)
if not subnet['subnetpool_id']:
return
subnetpool = self.get_subnetpool(context, subnet['subnetpool_id'])
return subnetpool.get('address_scope_id', '')
# TODO(asarfaty): the NSX-V3 needs a very similar code too
def _validate_address_scope_for_router_interface(self, context, router_id,
gw_network_id, subnet_id):
"""Validate that the GW address scope is the same as the interface"""
gw_address_scope = self._get_network_address_scope(context,
gw_network_id)
if not gw_address_scope:
return
subnet_address_scope = self._get_subnet_address_scope(context,
subnet_id)
if (not subnet_address_scope or
subnet_address_scope != gw_address_scope):
raise nsx_exc.NsxRouterInterfaceDoesNotMatchAddressScope(
router_id=router_id, address_scope_id=gw_address_scope)
def _get_router_interfaces(self, context, router_id):
port_filters = {'device_id': [router_id],
'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF]}
return self.get_ports(context, filters=port_filters)

View File

@ -65,7 +65,6 @@ from neutron.db import portsecurity_db
from neutron.db import quota_db # noqa from neutron.db import quota_db # noqa
from neutron.db import securitygroups_db from neutron.db import securitygroups_db
from neutron.db import vlantransparent_db from neutron.db import vlantransparent_db
from neutron.extensions import address_scope as ext_address_scope
from neutron.extensions import allowedaddresspairs as addr_pair from neutron.extensions import allowedaddresspairs as addr_pair
from neutron.extensions import availability_zone as az_ext from neutron.extensions import availability_zone as az_ext
from neutron.extensions import external_net as ext_net_extn from neutron.extensions import external_net as ext_net_extn
@ -3234,11 +3233,6 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
intf_net_ids = list(set([port['network_id'] for port in intf_ports])) intf_net_ids = list(set([port['network_id'] for port in intf_ports]))
return intf_net_ids return intf_net_ids
def _get_router_interfaces(self, context, router_id):
port_filters = {'device_id': [router_id],
'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF]}
return self.get_ports(context, filters=port_filters)
def _get_address_groups(self, context, router_id, network_id): def _get_address_groups(self, context, router_id, network_id):
address_groups = [] address_groups = []
ports = self._get_router_interface_ports_by_network( ports = self._get_router_interface_ports_by_network(
@ -3422,31 +3416,6 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
context, subnet_id)['network_id'] context, subnet_id)['network_id']
return net_id, subnet_id return net_id, subnet_id
def _get_network_address_scope(self, context, net_id):
network = self.get_network(context, net_id)
return network.get(ext_address_scope.IPV4_ADDRESS_SCOPE)
def _get_subnet_address_scope(self, context, subnet_id):
subnet = self.get_subnet(context, subnet_id)
if not subnet['subnetpool_id']:
return
subnetpool = self.get_subnetpool(context, subnet['subnetpool_id'])
return subnetpool.get('address_scope_id', '')
# TODO(asarfaty): the NSX-V3 needs a very similar code too
def _validate_address_scope_for_router_interface(self, context, router_id,
gw_network_id, subnet_id):
gw_address_scope = self._get_network_address_scope(context,
gw_network_id)
if not gw_address_scope:
return
subnet_address_scope = self._get_subnet_address_scope(context,
subnet_id)
if (not subnet_address_scope or
subnet_address_scope != gw_address_scope):
raise nsx_exc.NsxRouterInterfaceDoesNotMatchAddressScope(
router_id=router_id, address_scope_id=gw_address_scope)
def add_router_interface(self, context, router_id, interface_info): def add_router_interface(self, context, router_id, interface_info):
router = self.get_router(context, router_id) router = self.get_router(context, router_id)
net_id, subnet_id = self._get_interface_info(context, interface_info) net_id, subnet_id = self._get_interface_info(context, interface_info)

View File

@ -2890,6 +2890,18 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
# TODO(berlin): admin_state_up support # TODO(berlin): admin_state_up support
gw_info = self._extract_external_gw(context, router, is_extract=False) gw_info = self._extract_external_gw(context, router, is_extract=False)
router_data = router['router'] router_data = router['router']
# if setting this router as no-snat, make sure gw address scope match
# those of the subnets
if (validators.is_attr_set(gw_info) and
not gw_info.get('enable_snat', cfg.CONF.enable_snat_by_default)):
router_ports = self._get_router_interfaces(context, router_id)
for port in router_ports:
for fip in port['fixed_ips']:
self._validate_address_scope_for_router_interface(
context, router_id,
gw_info['network_id'], fip['subnet_id'])
nsx_router_id = None nsx_router_id = None
routes_added = [] routes_added = []
routes_removed = [] routes_removed = []
@ -3050,6 +3062,14 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
network_id = subnet['network_id'] network_id = subnet['network_id']
nsx_net_id, nsx_port_id = nsx_db.get_nsx_switch_and_port_id( nsx_net_id, nsx_port_id = nsx_db.get_nsx_switch_and_port_id(
context.session, port['id']) context.session, port['id'])
router_db = self._get_router(context, router_id)
# If it is a no-snat router, interface address scope must be the
# same as the gateways
if not router_db.enable_snat:
gw_network_id = router_db.gw_port.network_id
self._validate_address_scope_for_router_interface(
context, router_id, gw_network_id, subnet['id'])
nsx_router_id = nsx_db.get_nsx_router_id(context.session, nsx_router_id = nsx_db.get_nsx_router_id(context.session,
router_id) router_id)
@ -3069,7 +3089,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
logical_switch_port_id=nsx_port_id, logical_switch_port_id=nsx_port_id,
address_groups=address_groups) address_groups=address_groups)
router_db = self._get_router(context, router_id)
if router_db.gw_port and not router_db.enable_snat: if router_db.gw_port and not router_db.enable_snat:
# TODO(berlin): Announce the subnet on tier0 if enable_snat # TODO(berlin): Announce the subnet on tier0 if enable_snat
# is False # is False

View File

@ -14,11 +14,13 @@
# limitations under the License. # limitations under the License.
import mock import mock
import netaddr
import six import six
from webob import exc from webob import exc
from neutron.api.v2 import attributes from neutron.api.v2 import attributes
from neutron.db import models_v2 from neutron.db import models_v2
from neutron.extensions import address_scope
from neutron.extensions import external_net from neutron.extensions import external_net
from neutron.extensions import extraroute from neutron.extensions import extraroute
from neutron.extensions import l3 from neutron.extensions import l3
@ -26,6 +28,7 @@ from neutron.extensions import l3_ext_gw_mode
from neutron.extensions import securitygroup as secgrp from neutron.extensions import securitygroup as secgrp
from neutron.tests.unit import _test_extension_portbindings as test_bindings from neutron.tests.unit import _test_extension_portbindings as test_bindings
from neutron.tests.unit.db import test_db_base_plugin_v2 as test_plugin from neutron.tests.unit.db import test_db_base_plugin_v2 as test_plugin
from neutron.tests.unit.extensions import test_address_scope
from neutron.tests.unit.extensions import test_extra_dhcp_opt as test_dhcpopts from neutron.tests.unit.extensions import test_extra_dhcp_opt as test_dhcpopts
from neutron.tests.unit.extensions import test_extraroute as test_ext_route from neutron.tests.unit.extensions import test_extraroute as test_ext_route
from neutron.tests.unit.extensions import test_l3 as test_l3_plugin from neutron.tests.unit.extensions import test_l3 as test_l3_plugin
@ -535,7 +538,10 @@ class TestL3ExtensionManager(object):
# Finally add l3 resources to the global attribute map # Finally add l3 resources to the global attribute map
attributes.RESOURCE_ATTRIBUTE_MAP.update( attributes.RESOURCE_ATTRIBUTE_MAP.update(
l3.RESOURCE_ATTRIBUTE_MAP) l3.RESOURCE_ATTRIBUTE_MAP)
return l3.L3.get_resources() attributes.RESOURCE_ATTRIBUTE_MAP.update(
address_scope.RESOURCE_ATTRIBUTE_MAP)
return (l3.L3.get_resources() +
address_scope.Address_scope.get_resources())
def get_actions(self): def get_actions(self):
return [] return []
@ -555,7 +561,8 @@ def restore_l3_attribute_map(map_to_restore):
l3.RESOURCE_ATTRIBUTE_MAP = map_to_restore l3.RESOURCE_ATTRIBUTE_MAP = map_to_restore
class L3NatTest(test_l3_plugin.L3BaseForIntTests, NsxV3PluginTestCaseMixin): class L3NatTest(test_l3_plugin.L3BaseForIntTests, NsxV3PluginTestCaseMixin,
test_address_scope.AddressScopeTestCase):
def _restore_l3_attribute_map(self): def _restore_l3_attribute_map(self):
l3.RESOURCE_ATTRIBUTE_MAP = self._l3_attribute_map_bk l3.RESOURCE_ATTRIBUTE_MAP = self._l3_attribute_map_bk
@ -759,6 +766,131 @@ class TestL3NatTestCase(L3NatTest,
def test_floatingip_update_to_same_port_id_twice(self): def test_floatingip_update_to_same_port_id_twice(self):
self.skipTest('Plugin changes floating port status') self.skipTest('Plugin changes floating port status')
def _test_create_subnetpool(self, prefixes, expected=None,
admin=False, **kwargs):
keys = kwargs.copy()
keys.setdefault('tenant_id', self._tenant_id)
with self.subnetpool(prefixes, admin, **keys) as subnetpool:
self._validate_resource(subnetpool, keys, 'subnetpool')
if expected:
self._compare_resource(subnetpool, expected, 'subnetpool')
return subnetpool
def _update_router_enable_snat(self, router_id, network_id, enable_snat):
return self._update('routers', router_id,
{'router': {'external_gateway_info':
{'network_id': network_id,
'enable_snat': enable_snat}}})
def test_router_no_snat_with_different_address_scope(self):
"""Test that if the router has no snat, you cannot add an interface
from a different address scope than the gateway.
"""
# create an external network on one address scope
with self.address_scope(name='as1') as addr_scope, \
self.network() as ext_net:
self._set_net_external(ext_net['network']['id'])
as_id = addr_scope['address_scope']['id']
subnet = netaddr.IPNetwork('10.10.10.0/24')
subnetpool = self._test_create_subnetpool(
[subnet.cidr], name='sp1',
min_prefixlen='24', address_scope_id=as_id)
subnetpool_id = subnetpool['subnetpool']['id']
data = {'subnet': {
'network_id': ext_net['network']['id'],
'subnetpool_id': subnetpool_id,
'ip_version': 4,
'enable_dhcp': False,
'tenant_id': ext_net['network']['tenant_id']}}
req = self.new_create_request('subnets', data)
ext_subnet = self.deserialize(self.fmt, req.get_response(self.api))
# create a regular network on another address scope
with self.address_scope(name='as2') as addr_scope2, \
self.network() as net:
as_id2 = addr_scope2['address_scope']['id']
subnet2 = netaddr.IPNetwork('20.10.10.0/24')
subnetpool2 = self._test_create_subnetpool(
[subnet2.cidr], name='sp2',
min_prefixlen='24', address_scope_id=as_id2)
subnetpool_id2 = subnetpool2['subnetpool']['id']
data = {'subnet': {
'network_id': net['network']['id'],
'subnetpool_id': subnetpool_id2,
'ip_version': 4,
'tenant_id': net['network']['tenant_id']}}
req = self.new_create_request('subnets', data)
int_subnet = self.deserialize(
self.fmt, req.get_response(self.api))
# create a no snat router with this gateway
with self.router() as r:
self._add_external_gateway_to_router(
r['router']['id'],
ext_subnet['subnet']['network_id'])
self._update_router_enable_snat(
r['router']['id'],
ext_subnet['subnet']['network_id'],
False)
# should fail adding the interface to the router
err_code = exc.HTTPBadRequest.code
self._router_interface_action('add',
r['router']['id'],
int_subnet['subnet']['id'],
None,
err_code)
def test_router_no_snat_with_same_address_scope(self):
"""Test that if the router has no snat, you can add an interface
from the same address scope as the gateway.
"""
# create an external network on one address scope
with self.address_scope(name='as1') as addr_scope, \
self.network() as ext_net:
self._set_net_external(ext_net['network']['id'])
as_id = addr_scope['address_scope']['id']
subnet = netaddr.IPNetwork('10.10.10.0/21')
subnetpool = self._test_create_subnetpool(
[subnet.cidr], name='sp1',
min_prefixlen='24', address_scope_id=as_id)
subnetpool_id = subnetpool['subnetpool']['id']
data = {'subnet': {
'network_id': ext_net['network']['id'],
'subnetpool_id': subnetpool_id,
'ip_version': 4,
'enable_dhcp': False,
'tenant_id': ext_net['network']['tenant_id']}}
req = self.new_create_request('subnets', data)
ext_subnet = self.deserialize(self.fmt, req.get_response(self.api))
# create a regular network on the same address scope
with self.network() as net:
data = {'subnet': {
'network_id': net['network']['id'],
'subnetpool_id': subnetpool_id,
'ip_version': 4,
'tenant_id': net['network']['tenant_id']}}
req = self.new_create_request('subnets', data)
int_subnet = self.deserialize(
self.fmt, req.get_response(self.api))
# create a no snat router with this gateway
with self.router() as r:
self._add_external_gateway_to_router(
r['router']['id'],
ext_subnet['subnet']['network_id'])
self._update_router_enable_snat(
r['router']['id'],
ext_subnet['subnet']['network_id'],
False)
# should succeed adding the interface to the router
self._router_interface_action('add',
r['router']['id'],
int_subnet['subnet']['id'],
None)
class ExtGwModeTestCase(test_ext_gw_mode.ExtGwModeIntTestCase, class ExtGwModeTestCase(test_ext_gw_mode.ExtGwModeIntTestCase,
L3NatTest): L3NatTest):