NSX|V3: Fix LB VIP advertisement

The LB VIP should be advertised by the Tier1 router only if it is on the
external network.
To do that, the global advertise vp flag will not be set, and instead a rule with a
filter to advertise only the VIPs on the external network is added.
In addition, an admin utility is added to update already existing routers with
loadbalancers.
Since VPNaaS also uses the router advertisement rules, its code was also updated so
that each application will handle only its own rules.

Change-Id: Ibfac0406a8c3009c323828cc42c96012e70cb0a9
This commit is contained in:
Adit Sarfaty 2018-10-17 10:23:28 +03:00
parent b024fb250d
commit b263b592b9
9 changed files with 172 additions and 10 deletions

View File

@ -534,6 +534,11 @@ LBaaS
nsxadmin -r lb-monitors -o list
- Update advertisement of LB vips on routers::
nsxadmin -r lb-advertisement -o nsx-update
Rate Limit
~~~~~~~~~~
- Show the current NSX rate limit::

View File

@ -20,6 +20,9 @@ from neutron_lib import exceptions as n_exc
from vmware_nsx._i18n import _
from vmware_nsx.db import db as nsx_db
from vmware_nsx.services.lbaas import lb_const
from vmware_nsxlib.v3 import nsx_constants
ADV_RULE_NAME = 'LB external VIP advertisement'
def get_tags(plugin, resource_id, resource_type, project_id, project_name):
@ -208,3 +211,19 @@ def remove_rule_from_policy(rule):
def update_rule_in_policy(rule):
remove_rule_from_policy(rule)
rule['policy']['rules'].append(rule)
def update_router_lb_vip_advertisement(context, core_plugin, router,
nsx_router_id):
# Add a rule to advertise external vips on the router
external_subnets = core_plugin._find_router_gw_subnets(context, router)
external_cidrs = [s['cidr'] for s in external_subnets]
if external_cidrs:
adv_rule = {
'display_name': ADV_RULE_NAME,
'action': nsx_constants.FW_ACTION_ALLOW,
'networks': external_cidrs,
'rule_filter': {'prefix_operator': 'EQ',
'match_route_types': ['T1_LB_VIP']}}
core_plugin.nsxlib.logical_router.update_advertisement_rules(
nsx_router_id, [adv_rule], name_prefix=ADV_RULE_NAME)

View File

@ -93,10 +93,11 @@ class EdgeLoadBalancerManagerFromDict(base_mgr.Nsxv3LoadbalancerBaseManager):
try:
service_client.delete(lb_service_id)
# If there is no lb service attached to the router,
# update the router advertise_lb_vip flag to false.
# delete the router advertise_lb_vip rule.
router_client = self.core_plugin.nsxlib.logical_router
router_client.update_advertisement(
nsx_router_id, advertise_lb_vip=False)
router_client.update_advertisement_rules(
nsx_router_id, [],
name_prefix=lb_utils.ADV_RULE_NAME)
except nsxlib_exc.ManagerError:
completor(success=False)
msg = (_('Failed to delete lb service %(lbs)s from nsx'

View File

@ -74,9 +74,10 @@ class EdgeMemberManagerFromDict(base_mgr.Nsxv3LoadbalancerBaseManager):
LOG.error("Failed to create LB service: %s", e)
return
# Update router to enable advertise_lb_vip flag
self.core_plugin.nsxlib.logical_router.update_advertisement(
nsx_router_id, advertise_lb_vip=True)
# Add rule to advertise external vips
lb_utils.update_router_lb_vip_advertisement(
context, self.core_plugin, router, nsx_router_id)
return lb_service
def _get_updated_pool_members(self, context, lb_pool, member):

View File

@ -148,6 +148,7 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
filters = {'router_id': [router_id], 'status': [constants.ACTIVE]}
services = self.vpn_plugin.get_vpnservices(
context.elevated(), filters=filters)
rule_name_pref = 'VPN advertisement service'
for srv in services:
# use only services with active connections
filters = {'vpnservice_id': [srv['id']],
@ -159,13 +160,15 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
subnet = self.l3_plugin.get_subnet(
context.elevated(), srv['subnet_id'])
rules.append({
'display_name': 'VPN advertisement service ' + srv['id'],
'display_name': "%s %s" % (rule_name_pref, srv['id']),
'action': consts.FW_ACTION_ALLOW,
'networks': [subnet['cidr']]})
logical_router_id = db.get_nsx_router_id(context.session, router_id)
self._nsxlib.logical_router.update_advertisement_rules(
logical_router_id, rules)
if rules:
logical_router_id = db.get_nsx_router_id(context.session,
router_id)
self._nsxlib.logical_router.update_advertisement_rules(
logical_router_id, rules, name_prefix=rule_name_pref)
def _update_status(self, context, vpn_service_id, ipsec_site_conn_id,
status, updated_pending_status=True):

View File

@ -44,6 +44,7 @@ LB_SERVICES = 'lb-services'
LB_VIRTUAL_SERVERS = 'lb-virtual-servers'
LB_POOLS = 'lb-pools'
LB_MONITORS = 'lb-monitors'
LB_ADVERTISEMENT = 'lb-advertisement'
RATE_LIMIT = 'rate-limit'
CLUSTER = 'cluster'
ORPHANED_FIREWALL_SECTIONS = 'orphaned-firewall-sections'

View File

@ -14,10 +14,16 @@
from oslo_log import log as logging
from neutron_lib.callbacks import registry
from neutron_lib import context as neutron_context
from vmware_nsx.db import db as nsx_db
from vmware_nsx.services.lbaas.nsx_v3.implementation import lb_utils
from vmware_nsx.shell.admin.plugins.common import constants
from vmware_nsx.shell.admin.plugins.common import formatters
from vmware_nsx.shell.admin.plugins.common import utils as admin_utils
from vmware_nsx.shell.admin.plugins.nsxv3.resources import utils
from vmware_nsx.shell import resources as shell
from vmware_nsxlib.v3 import nsx_constants as consts
LOG = logging.getLogger(__name__)
@ -91,3 +97,54 @@ def nsx_list_lb_monitors(resource, event, trigger, **kwargs):
constants.LB_MONITORS, lb_monitors['results'],
['display_name', 'id', 'resource_type']))
return bool(lb_monitors)
@admin_utils.output_header
def nsx_update_router_lb_advertisement(resource, event, trigger, **kwargs):
"""The implementation of the VIP advertisement changed.
This utility will update existing LB/routers
"""
nsxlib = utils.get_connected_nsxlib()
if not nsxlib.feature_supported(consts.FEATURE_LOAD_BALANCER):
LOG.error("This utility is not available for NSX version %s",
nsxlib.get_version())
return
# Get the list of neutron routers used by LB
lb_services = nsxlib.load_balancer.service.list()['results']
lb_routers = []
for lb_srv in lb_services:
for tag in lb_srv.get('tags', []):
if tag['scope'] == 'os-neutron-router-id':
lb_routers.append(tag['tag'])
lb_routers = set(lb_routers)
LOG.info("Going to update LB advertisement on %(num)s router(s): "
"%(routers)s",
{'num': len(lb_routers), 'routers': lb_routers})
context = neutron_context.get_admin_context()
with utils.NsxV3PluginWrapper() as plugin:
for rtr_id in lb_routers:
nsx_router_id = nsx_db.get_nsx_router_id(context.session, rtr_id)
if not nsx_router_id:
LOG.error("Router %s NSX Id was not found.", rtr_id)
continue
try:
# disable the global vip advertisement flag
plugin.nsxlib.logical_router.update_advertisement(
nsx_router_id, advertise_lb_vip=False)
# Add an advertisement rule for the external network
router = plugin.get_router(context, rtr_id)
lb_utils.update_router_lb_vip_advertisement(
context, plugin, router, nsx_router_id)
except Exception as e:
LOG.error("Failed updating router %(id)s: %(e)s",
{'id': rtr_id, 'e': e})
LOG.info("Done.")
registry.subscribe(nsx_update_router_lb_advertisement,
constants.LB_ADVERTISEMENT,
shell.Operations.NSX_UPDATE.value)

View File

@ -142,6 +142,8 @@ nsxv3_resources = {
constants.RATE_LIMIT: Resource(constants.RATE_LIMIT,
[Operations.SHOW.value,
Operations.NSX_UPDATE.value]),
constants.LB_ADVERTISEMENT: Resource(constants.LB_ADVERTISEMENT,
[Operations.NSX_UPDATE.value]),
constants.CLUSTER: Resource(constants.CLUSTER,
[Operations.SHOW.value])
}

View File

@ -601,6 +601,79 @@ class TestEdgeLbaasV2Member(BaseTestEdgeLbaasV2):
def test_create_existing_binding(self):
self._test_create(LB_BINDING, POOL_BINDING)
def test_create_with_service(self):
ext_cidr = '1.1.1.0/24'
with mock.patch.object(lb_utils, 'validate_lb_member_subnet'
) as mock_validate_lb_subnet, \
mock.patch.object(self.lbv2_driver.plugin, 'get_pool_members'
) as mock_get_pool_members, \
mock.patch.object(lb_utils, 'get_network_from_subnet'
) as mock_get_network, \
mock.patch.object(lb_utils, 'get_router_from_network'
) as mock_get_router, \
mock.patch.object(nsx_db, 'get_nsx_lbaas_pool_binding'
) as mock_get_pool_binding, \
mock.patch.object(nsx_db, 'get_nsx_lbaas_loadbalancer_binding'
) as mock_get_lb_binding, \
mock.patch.object(nsx_db, 'get_nsx_router_id'
) as mock_get_nsx_router_id, \
mock.patch.object(self.service_client, 'get_router_lb_service'
) as mock_get_lb_service, \
mock.patch.object(self.service_client, 'create'
) as mock_create_lb_service, \
mock.patch.object(nsx_db, 'add_nsx_lbaas_loadbalancer_binding'
) as mock_add_loadbalancer_bidning, \
mock.patch.object(self.service_client,
'add_virtual_server'
) as mock_add_vs_to_service, \
mock.patch.object(self.pool_client, 'get'
) as mock_get_pool, \
mock.patch.object(self.pool_client, 'update_pool_with_members'
) as mock_update_pool_with_members,\
mock.patch.object(self.core_plugin.nsxlib.logical_router,
'update_advertisement_rules') as update_adv,\
mock.patch.object(self.core_plugin, '_find_router_gw_subnets'
) as mock_get_subnets,\
mock.patch.object(self.core_plugin, 'get_router'
) as mock_core_get_router:
mock_validate_lb_subnet.return_value = True
mock_get_pool_members.return_value = [self.member]
mock_get_network.return_value = LB_NETWORK
mock_get_router.return_value = LB_ROUTER_ID
mock_get_pool_binding.return_value = POOL_BINDING
mock_get_lb_binding.return_value = None
mock_get_nsx_router_id.return_value = LB_ROUTER_ID
mock_get_lb_service.return_value = {}
mock_create_lb_service.return_value = {'id': LB_SERVICE_ID}
mock_get_pool.return_value = LB_POOL
mock_core_get_router.return_value = {
'id': LB_ROUTER_ID,
'name': 'router1',
'external_gateway_info': 'dummy'}
mock_get_subnets.return_value = [{'cidr': ext_cidr}]
self.edge_driver.member.create(self.context, self.member)
mock_add_loadbalancer_bidning.assert_called_with(
self.context.session, LB_ID, LB_SERVICE_ID, LB_ROUTER_ID,
LB_VIP)
mock_add_vs_to_service.assert_called_with(LB_SERVICE_ID, LB_VS_ID)
mock_update_pool_with_members.assert_called_with(LB_POOL_ID,
[LB_MEMBER])
update_adv.assert_called_with(
LB_ROUTER_ID,
[{'networks': [ext_cidr],
'display_name': lb_utils.ADV_RULE_NAME,
'rule_filter': {'match_route_types': ['T1_LB_VIP'],
'prefix_operator': 'EQ'},
'action': 'ALLOW'}],
name_prefix=lb_utils.ADV_RULE_NAME)
mock_successful_completion = (
self.lbv2_driver.member.successful_completion)
mock_successful_completion.assert_called_with(self.context,
self.member,
delete=False)
def test_create_lbs_no_router_gateway(self):
with mock.patch.object(lb_utils, 'validate_lb_member_subnet'
) as mock_validate_lb_subnet, \