NSX|v: Handle address scope change on subnetpool
Address scope is mutable on subnetpool after subnets were allocated. When address scope changes, neutron fires AFTER_CHANGE event. Upon catching this notification: 1. Log warning if no-nat router resulted with mixed address scopes 2. Recalculate snat rules if needed 3. Recalculate firewall rules A fix for nsx-v3 was already introduced in commit I1de25868db0d77fbcb7ebc588c6ab9493a3dadf4 Change-Id: I2696f0ba0edf0409993455b7a9e6e478383f3276
This commit is contained in:
parent
803a6bffe4
commit
345e5ea10b
@ -202,6 +202,13 @@ class NsxPluginBase(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
"""
|
||||
pass
|
||||
|
||||
def recalculate_fw_rules_for_router(self, context, router, subnets):
|
||||
"""Method to recalculate router FW rules for specific subnets.
|
||||
Invoked when subnetpool address scope changes.
|
||||
Implemented in child plugin classes
|
||||
"""
|
||||
pass
|
||||
|
||||
def _filter_subnets_by_subnetpool(self, subnets, subnetpool_id):
|
||||
return [subnet for subnet in subnets
|
||||
if subnet['subnetpool_id'] == subnetpool_id]
|
||||
@ -236,7 +243,10 @@ class NsxPluginBase(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
# (all router subnets were allocated from subnetpool_id)
|
||||
continue
|
||||
|
||||
# TODO(annak): handle east-west FW rules
|
||||
# Update east-west FW rules
|
||||
self.recalculate_fw_rules_for_router(context, rtr,
|
||||
affected_subnets)
|
||||
|
||||
if not rtr['external_gateway_info']:
|
||||
continue
|
||||
|
||||
|
@ -294,6 +294,11 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
||||
fc_utils.SERVICE_INSERTION_RESOURCE,
|
||||
events.AFTER_CREATE)
|
||||
|
||||
# Subscribe to subnet pools changes
|
||||
registry.subscribe(
|
||||
self.on_subnetpool_address_scope_updated,
|
||||
resources.SUBNETPOOL_ADDRESS_SCOPE, events.AFTER_UPDATE)
|
||||
|
||||
if c_utils.is_nsxv_version_6_2(self.nsx_v.vcns.get_version()):
|
||||
self.supported_extension_aliases.append("provider-security-group")
|
||||
|
||||
@ -3455,6 +3460,22 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
||||
edge_utils.update_nat_rules(
|
||||
self.nsx_v, context, router_id, snat, dnat)
|
||||
|
||||
def recalculate_snat_rules_for_router(self, context, router, subnets):
|
||||
"""Recalculate router snat rules for specific subnets.
|
||||
Invoked when subnetpool address scope changes.
|
||||
"""
|
||||
# Recalculate all nat rules for all subnets of the router
|
||||
router_db = self._get_router(context, router['id'])
|
||||
self._update_nat_rules(context, router_db)
|
||||
|
||||
def recalculate_fw_rules_for_router(self, context, router, subnets):
|
||||
"""Recalculate router fw rules for specific subnets.
|
||||
Invoked when subnetpool address scope changes.
|
||||
"""
|
||||
# Recalculate all fw rules for all subnets of the router
|
||||
router_db = self._get_router(context, router['id'])
|
||||
self._update_subnets_and_dnat_firewall(context, router_db)
|
||||
|
||||
def _check_intf_number_of_router(self, context, router_id):
|
||||
intf_ports = self._get_port_by_device_id(
|
||||
context, router_id, l3_db.DEVICE_OWNER_ROUTER_INTF)
|
||||
|
@ -3731,7 +3731,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
|
||||
net_res[az_ext.AVAILABILITY_ZONES] = [az_name]
|
||||
|
||||
def recalculate_snat_rules_for_router(self, context, router, subnets):
|
||||
"""Rrecalculate router snat rules for specific subnets.
|
||||
"""Recalculate router snat rules for specific subnets.
|
||||
Invoked when subnetpool address scope changes.
|
||||
"""
|
||||
nsx_router_id = nsx_db.get_nsx_router_id(context.session,
|
||||
|
@ -3490,6 +3490,26 @@ class TestExclusiveRouterTestCase(L3NatTest, L3NatTestCaseBase,
|
||||
None,
|
||||
err_code)
|
||||
|
||||
def _create_subnet_and_add_to_router(self, subnetpool_id, router_id):
|
||||
# create a regular network on the given subnet pool
|
||||
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))
|
||||
|
||||
# Add the interface to the router
|
||||
self._router_interface_action(
|
||||
'add',
|
||||
router_id,
|
||||
int_subnet['subnet']['id'],
|
||||
None)
|
||||
return int_subnet
|
||||
|
||||
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.
|
||||
@ -3514,31 +3534,19 @@ class TestExclusiveRouterTestCase(L3NatTest, L3NatTestCaseBase,
|
||||
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))
|
||||
# and 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)
|
||||
|
||||
# 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)
|
||||
# should succeed adding the interface to the router
|
||||
self._create_subnet_and_add_to_router(
|
||||
subnetpool_id, r['router']['id'])
|
||||
|
||||
def test_router_address_scope_snat_rules(self):
|
||||
"""Test that if the router interface had the same address scope
|
||||
@ -3564,48 +3572,37 @@ class TestExclusiveRouterTestCase(L3NatTest, L3NatTestCaseBase,
|
||||
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))
|
||||
# and 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'])
|
||||
|
||||
# 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'])
|
||||
# Add the interface to the router
|
||||
with mock.patch.object(
|
||||
edge_utils, 'update_nat_rules') as update_nat,\
|
||||
mock.patch.object(
|
||||
edge_utils, 'update_firewall') as update_fw:
|
||||
|
||||
# Add the interface to the router
|
||||
with mock.patch.object(
|
||||
edge_utils, 'update_nat_rules') as update_nat,\
|
||||
mock.patch.object(
|
||||
edge_utils, 'update_firewall') as update_fw:
|
||||
self._router_interface_action(
|
||||
'add',
|
||||
r['router']['id'],
|
||||
int_subnet['subnet']['id'],
|
||||
None)
|
||||
# make sure snat rules are not added
|
||||
update_nat.assert_called_once_with(
|
||||
mock.ANY, mock.ANY, r['router']['id'], [], [])
|
||||
int_subnet = self._create_subnet_and_add_to_router(
|
||||
subnetpool_id, r['router']['id'])
|
||||
|
||||
# check fw rules
|
||||
fw_rules = update_fw.call_args[0][3][
|
||||
'firewall_rule_list']
|
||||
self.assertEqual(2, len(fw_rules))
|
||||
self.assertEqual('Allocation Pool Rule',
|
||||
fw_rules[1]['name'])
|
||||
self.assertEqual('allow', fw_rules[1]['action'])
|
||||
self.assertEqual(
|
||||
int_subnet['subnet']['cidr'],
|
||||
fw_rules[1]['destination_ip_address'][0])
|
||||
self.assertEqual('external',
|
||||
fw_rules[1]['source_vnic_groups'][0])
|
||||
# make sure snat rules are not added
|
||||
update_nat.assert_called_once_with(
|
||||
mock.ANY, mock.ANY, r['router']['id'], [], [])
|
||||
|
||||
# check fw rules
|
||||
fw_rules = update_fw.call_args[0][3][
|
||||
'firewall_rule_list']
|
||||
self.assertEqual(2, len(fw_rules))
|
||||
self.assertEqual('Allocation Pool Rule',
|
||||
fw_rules[1]['name'])
|
||||
self.assertEqual('allow', fw_rules[1]['action'])
|
||||
self.assertEqual(
|
||||
int_subnet['subnet']['cidr'],
|
||||
fw_rules[1]['destination_ip_address'][0])
|
||||
self.assertEqual('external',
|
||||
fw_rules[1]['source_vnic_groups'][0])
|
||||
|
||||
def test_router_address_scope_fw_rules(self):
|
||||
"""Test that if the router interfaces has different address scope
|
||||
@ -3613,9 +3610,8 @@ class TestExclusiveRouterTestCase(L3NatTest, L3NatTestCaseBase,
|
||||
"""
|
||||
# create a router, networks, and address scopes
|
||||
with self.address_scope(name='as1') as addr_scope1, \
|
||||
self.address_scope(name='as2') as addr_scope2,\
|
||||
self.network() as net1, self.network() as net2,\
|
||||
self.network() as net3, self.router() as r:
|
||||
self.address_scope(name='as2') as addr_scope2, \
|
||||
self.router() as r:
|
||||
|
||||
as1_id = addr_scope1['address_scope']['id']
|
||||
as2_id = addr_scope2['address_scope']['id']
|
||||
@ -3630,61 +3626,31 @@ class TestExclusiveRouterTestCase(L3NatTest, L3NatTestCaseBase,
|
||||
subnetpool_id1 = subnetpool1['subnetpool']['id']
|
||||
subnetpool_id2 = subnetpool2['subnetpool']['id']
|
||||
|
||||
# create subnets on the 2 subnet pools
|
||||
data = {'subnet': {
|
||||
'network_id': net1['network']['id'],
|
||||
'subnetpool_id': subnetpool_id1,
|
||||
'ip_version': 4,
|
||||
'tenant_id': net1['network']['tenant_id']}}
|
||||
req = self.new_create_request('subnets', data)
|
||||
subnet1 = self.deserialize(
|
||||
self.fmt, req.get_response(self.api))
|
||||
|
||||
data = {'subnet': {
|
||||
'network_id': net2['network']['id'],
|
||||
'subnetpool_id': subnetpool_id2,
|
||||
'ip_version': 4,
|
||||
'tenant_id': net2['network']['tenant_id']}}
|
||||
req = self.new_create_request('subnets', data)
|
||||
subnet2 = self.deserialize(
|
||||
self.fmt, req.get_response(self.api))
|
||||
|
||||
data = {'subnet': {
|
||||
'network_id': net3['network']['id'],
|
||||
'subnetpool_id': subnetpool_id2,
|
||||
'ip_version': 4,
|
||||
'tenant_id': net3['network']['tenant_id']}}
|
||||
req = self.new_create_request('subnets', data)
|
||||
subnet3 = self.deserialize(
|
||||
self.fmt, req.get_response(self.api))
|
||||
|
||||
expected_rules = [
|
||||
{'enabled': True,
|
||||
'destination_ip_address': [subnet1['subnet']['cidr']],
|
||||
'action': 'allow',
|
||||
'name': 'Subnet Rule',
|
||||
'source_ip_address': [subnet1['subnet']['cidr']]},
|
||||
{'enabled': True,
|
||||
'destination_ip_address': [subnet2['subnet']['cidr'],
|
||||
subnet3['subnet']['cidr']],
|
||||
'action': 'allow',
|
||||
'name': 'Subnet Rule',
|
||||
'source_ip_address': [subnet2['subnet']['cidr'],
|
||||
subnet3['subnet']['cidr']]}]
|
||||
|
||||
# Add the interfaces to the router
|
||||
with mock.patch.object(
|
||||
edge_utils, 'update_nat_rules'),\
|
||||
mock.patch.object(edge_utils, 'update_firewall') as update_fw:
|
||||
self._router_interface_action(
|
||||
'add', r['router']['id'],
|
||||
subnet1['subnet']['id'], None)
|
||||
self._router_interface_action(
|
||||
'add', r['router']['id'],
|
||||
subnet2['subnet']['id'], None)
|
||||
self._router_interface_action(
|
||||
'add', r['router']['id'],
|
||||
subnet3['subnet']['id'], None)
|
||||
# create subnets on the 2 subnet pools, and attach to router
|
||||
subnet1 = self._create_subnet_and_add_to_router(
|
||||
subnetpool_id1, r['router']['id'])
|
||||
subnet2 = self._create_subnet_and_add_to_router(
|
||||
subnetpool_id2, r['router']['id'])
|
||||
subnet3 = self._create_subnet_and_add_to_router(
|
||||
subnetpool_id2, r['router']['id'])
|
||||
|
||||
expected_rules = [
|
||||
{'enabled': True,
|
||||
'destination_ip_address': [subnet1['subnet']['cidr']],
|
||||
'action': 'allow',
|
||||
'name': 'Subnet Rule',
|
||||
'source_ip_address': [subnet1['subnet']['cidr']]},
|
||||
{'enabled': True,
|
||||
'destination_ip_address': [subnet2['subnet']['cidr'],
|
||||
subnet3['subnet']['cidr']],
|
||||
'action': 'allow',
|
||||
'name': 'Subnet Rule',
|
||||
'source_ip_address': [subnet2['subnet']['cidr'],
|
||||
subnet3['subnet']['cidr']]}]
|
||||
|
||||
# check the final fw rules
|
||||
fw_rules = update_fw.call_args[0][3][
|
||||
@ -3693,6 +3659,84 @@ class TestExclusiveRouterTestCase(L3NatTest, L3NatTestCaseBase,
|
||||
self.assertEqual(self._recursive_sort_list(expected_rules),
|
||||
self._recursive_sort_list(fw_rules))
|
||||
|
||||
def _prepare_external_subnet_on_address_scope(self,
|
||||
ext_net,
|
||||
address_scope):
|
||||
|
||||
self._set_net_external(ext_net['network']['id'])
|
||||
as_id = address_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))
|
||||
return ext_subnet['subnet']
|
||||
|
||||
def _test_router_address_scope_change(self, change_gw=False):
|
||||
"""When subnetpool address scope changes, and router that was
|
||||
originally under same address scope, results having different
|
||||
address scopes, relevant snat rules are added.
|
||||
"""
|
||||
# create an external network on one address scope
|
||||
with self.address_scope(name='as1') as addr_scope, \
|
||||
self.network() as ext_net:
|
||||
ext_subnet = self._prepare_external_subnet_on_address_scope(
|
||||
ext_net, addr_scope)
|
||||
|
||||
# create a router with this gateway
|
||||
with self.router() as r:
|
||||
self._add_external_gateway_to_router(
|
||||
r['router']['id'],
|
||||
ext_subnet['network_id'])
|
||||
|
||||
# create a regular network on same address scope
|
||||
# and verify no snat change
|
||||
as_id = addr_scope['address_scope']['id']
|
||||
subnet2 = netaddr.IPNetwork('40.10.10.0/24')
|
||||
subnetpool2 = self._test_create_subnetpool(
|
||||
[subnet2.cidr], name='sp2',
|
||||
min_prefixlen='24', address_scope_id=as_id)
|
||||
subnetpool2_id = subnetpool2['subnetpool']['id']
|
||||
|
||||
self._create_subnet_and_add_to_router(
|
||||
subnetpool2_id, r['router']['id'])
|
||||
|
||||
# change address scope of the first subnetpool
|
||||
with self.address_scope(name='as2') as addr_scope2,\
|
||||
mock.patch.object(edge_utils, 'update_nat_rules') as update_nat,\
|
||||
mock.patch.object(edge_utils, 'update_firewall') as update_fw:
|
||||
|
||||
as2_id = addr_scope2['address_scope']['id']
|
||||
data = {'subnetpool': {
|
||||
'address_scope_id': as2_id}}
|
||||
|
||||
if change_gw:
|
||||
subnetpool_to_update = ext_subnet['subnetpool_id']
|
||||
else:
|
||||
subnetpool_to_update = subnetpool2_id
|
||||
|
||||
req = self.new_update_request('subnetpools', data,
|
||||
subnetpool_to_update)
|
||||
req.get_response(self.api)
|
||||
|
||||
# Verify that the snat & fw rule are being updated
|
||||
update_nat.assert_called_once()
|
||||
update_fw.assert_called_once()
|
||||
|
||||
def test_router_address_scope_change(self):
|
||||
self._test_router_address_scope_change()
|
||||
|
||||
def test_router_address_scope_gw_change(self):
|
||||
self._test_router_address_scope_change(change_gw=True)
|
||||
|
||||
|
||||
class ExtGwModeTestCase(NsxVPluginV2TestCase,
|
||||
test_ext_gw_mode.ExtGwModeIntTestCase):
|
||||
|
@ -1026,7 +1026,7 @@ class TestL3NatTestCase(L3NatTest,
|
||||
router_id,
|
||||
assert_snat_deleted=False,
|
||||
assert_snat_added=False):
|
||||
# create a regular network on the same address scope
|
||||
# create a regular network on the given subnet pool
|
||||
with self.network() as net:
|
||||
data = {'subnet': {
|
||||
'network_id': net['network']['id'],
|
||||
@ -1239,8 +1239,8 @@ class TestL3NatTestCase(L3NatTest,
|
||||
def test_3leg_router_address_scope_change(self):
|
||||
self._test_3leg_router_address_scope_change()
|
||||
|
||||
def test_3leg_router_address_scope_change_to_gw(self, change_2gw=True):
|
||||
self._test_3leg_router_address_scope_change()
|
||||
def test_3leg_router_address_scope_change_to_gw(self):
|
||||
self._test_3leg_router_address_scope_change(change_2gw=True)
|
||||
|
||||
def test_3leg_router_gw_address_scope_change(self):
|
||||
self._test_3leg_router_address_scope_change(change_gw=True)
|
||||
|
Loading…
Reference in New Issue
Block a user