Merge "NSX|V3: Do not add SNAT rules if same address scope"

This commit is contained in:
Jenkins 2017-07-07 18:23:51 +00:00 committed by Gerrit Code Review
commit d0af9b5a9d
4 changed files with 165 additions and 33 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 db_base_plugin_v2
from neutron.db import l3_db
from neutron.db import models_v2
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 port as port_def
@ -126,3 +127,31 @@ class NsxPluginBase(db_base_plugin_v2.NeutronDbPluginV2,
port_filters = {'device_id': [router_id],
'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF]}
return self.get_ports(context, filters=port_filters)
def _find_router_subnets_cidrs(self, context, router_id):
"""Retrieve cidrs of subnets attached to the specified router."""
subnets = self._find_router_subnets_and_cidrs(context, router_id)
return [subnet['cidr'] for subnet in subnets]
def _get_port_by_device_id(self, context, device_id, device_owner):
"""Retrieve ports associated with a specific device id.
Used for retrieving all neutron ports attached to a given router.
"""
port_qry = context.session.query(models_v2.Port)
return port_qry.filter_by(
device_id=device_id,
device_owner=device_owner,).all()
def _find_router_subnets_and_cidrs(self, context, router_id):
"""Retrieve subnets attached to the specified router."""
ports = self._get_port_by_device_id(context, router_id,
l3_db.DEVICE_OWNER_ROUTER_INTF)
# No need to check for overlapping CIDRs
subnets = []
for port in ports:
for ip in port.get('fixed_ips', []):
subnet_qry = context.session.query(models_v2.Subnet)
subnet = subnet_qry.filter_by(id=ip.subnet_id).one()
subnets.append({'id': subnet.id, 'cidr': subnet.cidr})
return subnets

View File

@ -3248,34 +3248,6 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
address_groups.append(address_group)
return address_groups
def _get_port_by_device_id(self, context, device_id, device_owner):
"""Retrieve ports associated with a specific device id.
Used for retrieving all neutron ports attached to a given router.
"""
port_qry = context.session.query(models_v2.Port)
return port_qry.filter_by(
device_id=device_id,
device_owner=device_owner,).all()
def _find_router_subnets_cidrs(self, context, router_id):
"""Retrieve cidrs of subnets attached to the specified router."""
subnets = self._find_router_subnets_and_cidrs(context, router_id)
return [subnet['cidr'] for subnet in subnets]
def _find_router_subnets_and_cidrs(self, context, router_id):
"""Retrieve subnets attached to the specified router."""
ports = self._get_port_by_device_id(context, router_id,
l3_db.DEVICE_OWNER_ROUTER_INTF)
# No need to check for overlapping CIDRs
subnets = []
for port in ports:
for ip in port.get('fixed_ips', []):
subnet_qry = context.session.query(models_v2.Subnet)
subnet = subnet_qry.filter_by(id=ip.subnet_id).one()
subnets.append({'id': subnet.id, 'cidr': subnet.cidr})
return subnets
def _get_nat_rules(self, context, router):
fip_qry = context.session.query(l3_db_models.FloatingIP)
fip_db = fip_qry.filter_by(router_id=router['id']).all()

View File

@ -2686,7 +2686,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
# TODO(berlin): revocate bgp announce on org tier0 router
pass
if remove_snat_rules:
self._routerlib.delete_gw_snat_rule(nsx_router_id, orgaddr)
self._routerlib.delete_gw_snat_rules(nsx_router_id, orgaddr)
if remove_router_link_port:
self._routerlib.remove_router_link_port(
nsx_router_id, org_tier0_uuid)
@ -2701,8 +2701,15 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
self._routerlib.add_router_link_port(nsx_router_id, new_tier0_uuid,
tags=tags)
if add_snat_rules:
self._routerlib.add_gw_snat_rule(nsx_router_id, newaddr,
bypass_firewall=False)
# Add SNAT rules for all the subnets which are in different scope
# than the gw
gw_address_scope = self._get_network_address_scope(
context, router.gw_port.network_id)
subnets = self._find_router_subnets_and_cidrs(context.elevated(),
router_id)
for subnet in subnets:
self._add_subnet_snat_rule(context, router_id, nsx_router_id,
subnet, gw_address_scope, newaddr)
if bgp_announce:
# TODO(berlin): bgp announce on new tier0 router
pass
@ -2711,6 +2718,26 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
advertise_route_nat_flag,
advertise_route_connected_flag)
def _add_subnet_snat_rule(self, context, router_id, nsx_router_id, subnet,
gw_address_scope, gw_ip):
# if the subnets address scope is the same as the gateways:
# no need for SNAT
if gw_address_scope:
subnet_address_scope = self._get_subnet_address_scope(
context, subnet['id'])
if (gw_address_scope == subnet_address_scope):
LOG.info("No need for SNAT rule for router %(router)s "
"and subnet %(subnet)s because they use the "
"same address scope %(addr_scope)s.",
{'router': router_id,
'subnet': subnet['id'],
'addr_scope': gw_address_scope})
return
self._routerlib.add_gw_snat_rule(nsx_router_id, gw_ip,
source_net=subnet['cidr'],
bypass_firewall=False)
def _process_extra_attr_router_create(self, context, router_db, r):
for extra_attr in l3_attrs_db.get_attr_info().keys():
if extra_attr in r:
@ -3019,11 +3046,12 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
nsx_net_id, nsx_port_id = nsx_db.get_nsx_switch_and_port_id(
context.session, port['id'])
router_db = self._get_router(context, router_id)
gw_network_id = (router_db.gw_port.network_id if router_db.gw_port
else None)
# 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
if not router_db.enable_snat and gw_network_id:
self._validate_address_scope_for_router_interface(
context.elevated(), router_id, gw_network_id, subnet['id'])
@ -3055,6 +3083,15 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
# its DHCP port), by creating it if needed.
nsx_rpc.handle_router_metadata_access(self, context, router_id,
interface=info)
# add the SNAT rule for this interface
if (router_db.enable_snat and gw_network_id and
router_db.gw_port.get('fixed_ips')):
gw_ip = router_db.gw_port['fixed_ips'][0]['ip_address']
gw_address_scope = self._get_network_address_scope(
context, gw_network_id)
self._add_subnet_snat_rule(context, router_id, nsx_router_id,
subnet, gw_address_scope, gw_ip)
except Exception:
with excutils.save_and_reraise_exception():
LOG.error("Neutron failed to add_router_interface on "
@ -3125,6 +3162,14 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
else:
self.nsxlib.logical_router_port.delete_by_lswitch_id(
nsx_net_id)
# try to delete the SNAT rule of this subnet
if (router_db.gw_port and router_db.enable_snat and
router_db.gw_port.get('fixed_ips')):
gw_ip = router_db.gw_port['fixed_ips'][0]['ip_address']
self._routerlib.delete_gw_snat_rule_by_source(
nsx_router_id, gw_ip, subnet['cidr'],
skip_not_found=True)
except nsx_lib_exc.ResourceNotFound:
LOG.error("router port on router %(router_id)s for net "
"%(net_id)s not found at the backend",

View File

@ -891,6 +891,92 @@ class TestL3NatTestCase(L3NatTest,
int_subnet['subnet']['id'],
None)
def test_router_address_scope_snat_rules(self):
"""Test that if the router interface had the same address scope
as the gateway - snat rule is not added.
"""
# 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 router with this gateway
with self.router() as r:
self._add_external_gateway_to_router(
r['router']['id'],
ext_subnet['subnet']['network_id'])
with mock.patch("vmware_nsxlib.v3.router.RouterLib."
"add_gw_snat_rule") as add_nat:
# Add the interface
self._router_interface_action(
'add',
r['router']['id'],
int_subnet['subnet']['id'],
None)
# make sure snat rules are not added
add_nat.assert_not_called()
# create a regular network on a different 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 router with this gateway
with self.router() as r:
self._add_external_gateway_to_router(
r['router']['id'],
ext_subnet['subnet']['network_id'])
with mock.patch("vmware_nsxlib.v3.router.RouterLib."
"add_gw_snat_rule") as add_nat:
# Add the interface
self._router_interface_action(
'add',
r['router']['id'],
int_subnet['subnet']['id'],
None)
# make sure snat rules are added
add_nat.assert_called_once()
class ExtGwModeTestCase(test_ext_gw_mode.ExtGwModeIntTestCase,
L3NatTest):