From 2be77ef707d284a0b4739ff885e969936c388eeb Mon Sep 17 00:00:00 2001 From: Adit Sarfaty Date: Thu, 6 Jun 2019 14:52:59 +0300 Subject: [PATCH] NSX|P: Support listener default pool with session persistence Set/update/delete NSX session persistence profile, upon setting/removing lbaas listener default pool. Change-Id: I2a007a11ae30b166c0dd4e01525a5917da5ff1d4 --- .../nsx_p/implementation/l7policy_mgr.py | 3 +- .../lbaas/nsx_p/implementation/lb_utils.py | 7 +- .../nsx_p/implementation/listener_mgr.py | 89 ++++++++++++++++--- .../unit/services/lbaas/test_nsxp_driver.py | 39 +++----- 4 files changed, 92 insertions(+), 46 deletions(-) diff --git a/vmware_nsx/services/lbaas/nsx_p/implementation/l7policy_mgr.py b/vmware_nsx/services/lbaas/nsx_p/implementation/l7policy_mgr.py index fa0022108f..44cfde1880 100644 --- a/vmware_nsx/services/lbaas/nsx_p/implementation/l7policy_mgr.py +++ b/vmware_nsx/services/lbaas/nsx_p/implementation/l7policy_mgr.py @@ -19,7 +19,6 @@ from oslo_log import log as logging from oslo_utils import excutils from vmware_nsx._i18n import _ -from vmware_nsx.common import exceptions as nsx_exc from vmware_nsx.services.lbaas import base_mgr from vmware_nsx.services.lbaas.nsx_p.implementation import lb_utils from vmware_nsxlib.v3 import exceptions as nsxlib_exc @@ -79,7 +78,7 @@ class EdgeL7PolicyManagerFromDict(base_mgr.NsxpLoadbalancerBaseManager): try: vs_client.remove_lb_rule(policy['listener_id'], policy_name) - except nsx_exc.NsxResourceNotFound: + except nsxlib_exc.ResourceNotFound: pass except nsxlib_exc.ManagerError: completor(success=False) diff --git a/vmware_nsx/services/lbaas/nsx_p/implementation/lb_utils.py b/vmware_nsx/services/lbaas/nsx_p/implementation/lb_utils.py index 872c848d4c..68d9125aac 100644 --- a/vmware_nsx/services/lbaas/nsx_p/implementation/lb_utils.py +++ b/vmware_nsx/services/lbaas/nsx_p/implementation/lb_utils.py @@ -216,11 +216,9 @@ def setup_session_persistence(nsxpolicy, pool, pool_tags, listener, vs_data): else: pp_client = lb_client.lb_source_ip_persistence_profile pers_type = nsxlib_lb.PersistenceProfileTypes.SOURCE_IP - if pers_type: # There is a profile to create or update pp_kwargs = { - 'persistence_profile_id': pool['id'], 'name': "persistence_%s" % utils.get_name_and_uuid( pool['name'] or 'pool', pool['id'], maxlen=235), 'tags': lb_utils.build_persistence_profile_tags( @@ -230,8 +228,8 @@ def setup_session_persistence(nsxpolicy, pool, pool_tags, listener, vs_data): pp_kwargs['cookie_name'] = cookie_name pp_kwargs['cookie_mode'] = cookie_mode - persistence_profile_id = p_utils.path_to_id( - vs_data.get('lb_persistence_profile_path', '')) + profile_path = vs_data.get('lb_persistence_profile_path', '') + persistence_profile_id = p_utils.path_to_id(profile_path) if persistence_profile_id: # NOTE: removal of the persistence profile must be executed # after the virtual server has been updated @@ -246,7 +244,6 @@ def setup_session_persistence(nsxpolicy, pool, pool_tags, listener, vs_data): return persistence_profile_id, None else: # Prepare removal of persistence profile - profile_path = vs_data.get('lb_persistence_profile_path', '') return (None, functools.partial(delete_persistence_profile, nsxpolicy, profile_path)) elif pers_type: diff --git a/vmware_nsx/services/lbaas/nsx_p/implementation/listener_mgr.py b/vmware_nsx/services/lbaas/nsx_p/implementation/listener_mgr.py index a90b6a5e86..b636b9ee02 100644 --- a/vmware_nsx/services/lbaas/nsx_p/implementation/listener_mgr.py +++ b/vmware_nsx/services/lbaas/nsx_p/implementation/listener_mgr.py @@ -21,8 +21,8 @@ from oslo_log import log as logging from oslo_utils import excutils from vmware_nsx._i18n import _ -from vmware_nsx.common import exceptions as nsx_exc from vmware_nsx.services.lbaas import base_mgr +from vmware_nsx.services.lbaas import lb_common from vmware_nsx.services.lbaas import lb_const from vmware_nsx.services.lbaas.nsx_p.implementation import lb_utils from vmware_nsxlib.v3 import exceptions as nsxlib_exc @@ -135,19 +135,29 @@ class EdgeListenerManagerFromDict(base_mgr.NsxpLoadbalancerBaseManager): return app_client - def _validate_default_pool(self, listener, completor): - l_pool_id = listener.get('default_pool_id') - if l_pool_id: + def _validate_default_pool(self, listener, completor, + old_listener=None): + def_pool_id = listener.get('default_pool_id') + if def_pool_id: vs_client = self.core_plugin.nsxpolicy.load_balancer.virtual_server vs_list = vs_client.list() for vs in vs_list: pool_id = p_utils.path_to_id(vs.get('pool_path', '')) - if pool_id == l_pool_id: + if pool_id == def_pool_id: completor(success=False) msg = (_('Default pool %s is already used by another ' 'listener') % listener['default_pool_id']) raise n_exc.BadRequest(resource='lbaas-pool', msg=msg) + # Perform additional validation for session persistence before + # creating resources in the backend + old_pool = None + if old_listener: + old_pool = old_listener.get('default_pool') + lb_common.validate_session_persistence( + listener.get('default_pool'), listener, completor, + old_pool=old_pool) + @log_helpers.log_method_call def create(self, context, listener, completor, certificate=None): @@ -169,8 +179,54 @@ class EdgeListenerManagerFromDict(base_mgr.NsxpLoadbalancerBaseManager): msg = _('Failed to create virtual server at NSX backend') raise n_exc.BadRequest(resource='lbaas-listener', msg=msg) + self._update_default_pool(context, listener, completor) + completor(success=True) + def _get_pool_tags(self, context, pool): + return lb_utils.get_tags(self.core_plugin, pool['id'], + lb_const.LB_POOL_TYPE, pool['tenant_id'], + context.project_name) + + def _update_default_pool(self, context, listener, completor): + if not listener.get('default_pool_id'): + return + nsxlib_lb = self.core_plugin.nsxpolicy.load_balancer + vs_client = nsxlib_lb.virtual_server + vs_data = vs_client.get(listener['id']) + pool_id = listener['default_pool_id'] + pool = listener['default_pool'] + try: + (persistence_profile_id, + post_process_func) = lb_utils.setup_session_persistence( + self.core_plugin.nsxpolicy, + pool, + self._get_pool_tags(context, pool), + listener, vs_data) + except nsxlib_exc.ManagerError: + with excutils.save_and_reraise_exception(): + completor(success=False) + LOG.error("Failed to configure session persistence " + "profile for listener %s", listener['id']) + try: + # Update persistence profile and pool on virtual server + vs_client.update( + listener['id'], + pool_id=pool_id, + lb_persistence_profile_id=persistence_profile_id) + LOG.debug("Updated NSX virtual server %(vs_id)s with " + "persistence profile %(prof)s", + {'vs_id': listener['id'], + 'prof': persistence_profile_id}) + if post_process_func: + post_process_func() + except nsxlib_exc.ManagerError: + with excutils.save_and_reraise_exception(): + completor(success=False) + LOG.error("Failed to attach persistence profile %s to " + "virtual server %s", + persistence_profile_id, listener['id']) + @log_helpers.log_method_call def update(self, context, old_listener, new_listener, completor, certificate=None): @@ -180,7 +236,8 @@ class EdgeListenerManagerFromDict(base_mgr.NsxpLoadbalancerBaseManager): vs_name = None tags = None - self._validate_default_pool(new_listener, completor) + self._validate_default_pool(new_listener, completor, + old_listener=old_listener) if new_listener['name'] != old_listener['name']: vs_name = utils.get_name_and_uuid( new_listener['name'] or 'listener', @@ -201,6 +258,11 @@ class EdgeListenerManagerFromDict(base_mgr.NsxpLoadbalancerBaseManager): LOG.error('Failed to update listener %(listener)s with ' 'error %(error)s', {'listener': old_listener['id'], 'error': e}) + + # Update default pool and session persistence + if (old_listener.get('default_pool_id') != + new_listener.get('default_pool_id')): + self._update_default_pool(context, new_listener, completor) completor(success=True) @log_helpers.log_method_call @@ -213,8 +275,16 @@ class EdgeListenerManagerFromDict(base_mgr.NsxpLoadbalancerBaseManager): app_profile_id = listener['id'] try: + profile_path = None + if listener.get('default_pool_id'): + vs_data = vs_client.get(vs_id) + profile_path = vs_data.get('lb_persistence_profile_path', '') vs_client.delete(vs_id) - except nsx_exc.NsxResourceNotFound: + # Also delete the old session persistence profile + if profile_path: + lb_utils.delete_persistence_profile( + self.core_plugin.nsxpolicy, profile_path) + except nsxlib_exc.ResourceNotFound: LOG.error("virtual server not found on nsx: %(vs)s", {'vs': vs_id}) except nsxlib_exc.ManagerError: completor(success=False) @@ -224,10 +294,9 @@ class EdgeListenerManagerFromDict(base_mgr.NsxpLoadbalancerBaseManager): try: app_client.delete(app_profile_id) - except nsx_exc.NsxResourceNotFound: + except nsxlib_exc.ResourceNotFound: LOG.error("application profile not found on nsx: %s", app_profile_id) - except nsxlib_exc.ManagerError: completor(success=False) msg = (_('Failed to delete application profile: %(app)s') % @@ -239,7 +308,7 @@ class EdgeListenerManagerFromDict(base_mgr.NsxpLoadbalancerBaseManager): cert_client = self.core_plugin.nsxpolicy.certificate try: cert_client.delete(listener['id']) - except nsx_exc.NsxResourceNotFound: + except nsxlib_exc.ResourceNotFound: LOG.error("Certificate not found on nsx: %s", listener['id']) except nsxlib_exc.ManagerError: diff --git a/vmware_nsx/tests/unit/services/lbaas/test_nsxp_driver.py b/vmware_nsx/tests/unit/services/lbaas/test_nsxp_driver.py index e0e23cb5e7..e5601e5133 100644 --- a/vmware_nsx/tests/unit/services/lbaas/test_nsxp_driver.py +++ b/vmware_nsx/tests/unit/services/lbaas/test_nsxp_driver.py @@ -272,7 +272,7 @@ class BaseTestEdgeLbaasV2(base.BaseTestCase): self.pp_cookie_client = mock.patch.object( load_balancer, 'lb_cookie_persistence_profile').start() self.pp_generic_client = mock.patch.object( - load_balancer, 'lb_generic_persistence_profile').start() + load_balancer, 'lb_persistence_profile').start() self.tm_client = mock.patch.object(nsxpolicy, 'trust_management').start() self.nsxpolicy = nsxpolicy @@ -520,9 +520,6 @@ class TestEdgeLbaasV2Listener(BaseTestEdgeLbaasV2): self.completor) def test_create_listener_with_session_persistence(self): - # TODO(asarfaty): add this test after supporting default pool - # session persistence - return listener = lb_models.Listener(LISTENER_ID, LB_TENANT_ID, 'listener1', 'Dummy', self.pool_persistency.id, @@ -537,7 +534,9 @@ class TestEdgeLbaasV2Listener(BaseTestEdgeLbaasV2): return_value=(None, None)), \ mock.patch.object(self.vs_client, 'create_or_overwrite' ) as mock_add_virtual_server,\ - mock.patch.object(self.pp_client, 'create_or_overwrite' + mock.patch.object(self.vs_client, 'get', return_value={}),\ + mock.patch.object(self.edge_driver.listener, '_get_pool_tags'),\ + mock.patch.object(self.pp_cookie_client, 'create_or_overwrite' ) as mock_create_pp: mock_get_floatingips.return_value = [] @@ -559,9 +558,6 @@ class TestEdgeLbaasV2Listener(BaseTestEdgeLbaasV2): self.assertTrue(self.last_completor_succees) def test_create_listener_with_session_persistence_fail(self): - # TODO(asarfaty): add this test after supporting default pool - # session persistence - return listener = lb_models.Listener(LISTENER_ID, LB_TENANT_ID, 'listener1', 'Dummy', self.pool_persistency.id, @@ -623,9 +619,6 @@ class TestEdgeLbaasV2Listener(BaseTestEdgeLbaasV2): self.assertTrue(self.last_completor_succees) def test_update_with_session_persistence(self): - # TODO(asarfaty): add this test after supporting default pool - # session persistence - return new_listener = lb_models.Listener(LISTENER_ID, LB_TENANT_ID, 'listener1-new', 'new-description', self.pool_persistency.id, @@ -640,9 +633,11 @@ class TestEdgeLbaasV2Listener(BaseTestEdgeLbaasV2): mock.patch.object(self.core_plugin, 'get_waf_profile_path_and_mode', return_value=(None, None)), \ + mock.patch.object(self.edge_driver.listener, '_get_pool_tags'),\ + mock.patch.object(self.vs_client, 'get', return_value={}),\ mock.patch.object(self.vs_client, 'update', return_value={'id': LB_VS_ID}), \ - mock.patch.object(self.pp_client, 'create_or_overwrite' + mock.patch.object(self.pp_cookie_client, 'create_or_overwrite' ) as mock_create_pp: mock_get_floatingips.return_value = [] @@ -653,9 +648,6 @@ class TestEdgeLbaasV2Listener(BaseTestEdgeLbaasV2): self.assertTrue(self.last_completor_succees) def test_update_with_session_persistence_fail(self): - # TODO(asarfaty): add this test after supporting default pool - # session persistence - return old_listener = lb_models.Listener(LISTENER_ID, LB_TENANT_ID, 'listener1', 'description', self.pool_persistency.id, @@ -781,8 +773,7 @@ class TestEdgeLbaasV2Pool(BaseTestEdgeLbaasV2): cookie_mode='INSERT', cookie_name='meh_cookie', name=mock.ANY, - tags=mock.ANY, - persistence_profile_id=POOL_ID) + tags=mock.ANY) mock_vs_update.assert_called_once_with( LB_VS_ID, pool_id=LB_POOL_ID, lb_persistence_profile_id=LB_PP_ID) @@ -897,10 +888,6 @@ class TestEdgeLbaasV2Pool(BaseTestEdgeLbaasV2): cookie=True) def test_update_remove_persistency(self): - # TODO(asarfaty): add this test after supporting default pool - # session persistence - return - def verify_func(mock_create_pp, mock_update_pp, mock_delete_pp, mock_vs_update): mock_create_pp.assert_not_called() @@ -929,10 +916,6 @@ class TestEdgeLbaasV2Pool(BaseTestEdgeLbaasV2): self.assertTrue(self.last_completor_succees) def test_delete_with_persistency(self): - # TODO(asarfaty): add this test after supporting default pool - # session persistence - return - with mock.patch.object(self.vs_client, 'get' ) as mock_vs_get, \ mock.patch.object(self.vs_client, 'update', return_value=None @@ -982,14 +965,12 @@ class TestEdgeLbaasV2Pool(BaseTestEdgeLbaasV2): cookie_name=cookie_name, cookie_mode=cookie_mode, name=mock.ANY, - tags=mock.ANY, - persistence_profile_id=POOL_ID) + tags=mock.ANY) else: mock_update_pp.assert_called_once_with( LB_PP_ID, name=mock.ANY, - tags=mock.ANY, - persistence_profile_id=POOL_ID) + tags=mock.ANY) # Compare tags - kw args are the last item of a mock call tuple self.assertItemsEqual(mock_update_pp.mock_calls[0][-1]['tags'], [{'scope': 'os-lbaas-lb-id', 'tag': 'xxx-xxx'},