diff --git a/vmware_nsx/services/lbaas/nsx_v3/implementation/listener_mgr.py b/vmware_nsx/services/lbaas/nsx_v3/implementation/listener_mgr.py index 45706f1582..971b0a693f 100644 --- a/vmware_nsx/services/lbaas/nsx_v3/implementation/listener_mgr.py +++ b/vmware_nsx/services/lbaas/nsx_v3/implementation/listener_mgr.py @@ -109,6 +109,38 @@ class EdgeListenerManagerFromDict(base_mgr.Nsxv3LoadbalancerBaseManager): 'tag': listener['loadbalancer_id']}) return tags + def _validate_default_pool(self, context, listener, vs_id, completor): + if listener.get('default_pool_id'): + pool_binding = nsx_db.get_nsx_lbaas_pool_binding( + context.session, listener['loadbalancer']['id'], + listener['default_pool_id']) + if (pool_binding and pool_binding['lb_vs_id'] and + (vs_id is None or pool_binding['lb_vs_id'] != vs_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) + + def _update_default_pool_binding(self, context, listener, vs_id): + if listener.get('default_pool_id'): + lb_id = listener['loadbalancer']['id'] + pool_id = listener['default_pool_id'] + pool_binding = nsx_db.get_nsx_lbaas_pool_binding( + context.session, lb_id, pool_id) + if pool_binding: + nsx_db.update_nsx_lbaas_pool_binding( + context.session, lb_id, pool_id, vs_id) + + def _remove_default_pool_binding(self, context, listener): + if listener.get('default_pool_id'): + lb_id = listener['loadbalancer']['id'] + pool_id = listener['default_pool_id'] + pool_binding = nsx_db.get_nsx_lbaas_pool_binding( + context.session, lb_id, pool_id) + if pool_binding: + nsx_db.update_nsx_lbaas_pool_binding( + context.session, lb_id, pool_id, None) + @log_helpers.log_method_call def create(self, context, listener, completor, certificate=None): @@ -128,11 +160,14 @@ class EdgeListenerManagerFromDict(base_mgr.Nsxv3LoadbalancerBaseManager): listener['protocol'] == lb_const.LB_PROTOCOL_HTTPS): profile_type = lb_const.LB_TCP_PROFILE else: + completor(success=False) msg = (_('Cannot create listener %(listener)s with ' 'protocol %(protocol)s') % {'listener': listener['id'], 'protocol': listener['protocol']}) raise n_exc.BadRequest(resource='lbaas-listener', msg=msg) + # Validate default pool + self._validate_default_pool(context, listener, None, completor) try: app_profile = app_client.create( display_name=vs_name, resource_type=profile_type, tags=tags) @@ -167,6 +202,8 @@ class EdgeListenerManagerFromDict(base_mgr.Nsxv3LoadbalancerBaseManager): nsx_db.add_nsx_lbaas_listener_binding( context.session, lb_id, listener['id'], app_profile_id, virtual_server['id']) + self._update_default_pool_binding( + context, listener, virtual_server['id']) completor(success=True) @log_helpers.log_method_call @@ -190,6 +227,11 @@ class EdgeListenerManagerFromDict(base_mgr.Nsxv3LoadbalancerBaseManager): msg = (_('Cannot find listener %(listener)s binding on NSX ' 'backend'), {'listener': old_listener['id']}) raise n_exc.BadRequest(resource='lbaas-listener', msg=msg) + + # Validate default pool + self._validate_default_pool( + context, new_listener, binding['lb_vs_id'], completor) + try: vs_id = binding['lb_vs_id'] app_profile_id = binding['app_profile_id'] @@ -207,6 +249,10 @@ class EdgeListenerManagerFromDict(base_mgr.Nsxv3LoadbalancerBaseManager): LOG.error('Failed to update listener %(listener)s with ' 'error %(error)s', {'listener': old_listener['id'], 'error': e}) + if (old_listener.get('default_pool_id') != + new_listener.get('default_pool_id')): + self._remove_default_pool_binding(context, old_listener) + self._update_default_pool_binding(context, new_listener, vs_id) @log_helpers.log_method_call def delete(self, context, listener, completor): @@ -245,12 +291,7 @@ class EdgeListenerManagerFromDict(base_mgr.Nsxv3LoadbalancerBaseManager): if listener.get('default_pool_id'): vs_client.update(vs_id, pool_id='') # Update pool binding to disassociate virtual server - pool_binding = nsx_db.get_nsx_lbaas_pool_binding( - context.session, lb_id, listener['default_pool_id']) - if pool_binding: - nsx_db.update_nsx_lbaas_pool_binding( - context.session, lb_id, - listener['default_pool_id'], None) + self._remove_default_pool_binding(context, listener) vs_client.delete(vs_id) except nsx_exc.NsxResourceNotFound: msg = (_("virtual server not found on nsx: %(vs)s") % diff --git a/vmware_nsx/services/lbaas/nsx_v3/implementation/pool_mgr.py b/vmware_nsx/services/lbaas/nsx_v3/implementation/pool_mgr.py index db37159c7e..d234de3090 100644 --- a/vmware_nsx/services/lbaas/nsx_v3/implementation/pool_mgr.py +++ b/vmware_nsx/services/lbaas/nsx_v3/implementation/pool_mgr.py @@ -230,6 +230,12 @@ class EdgePoolManagerFromDict(base_mgr.Nsxv3LoadbalancerBaseManager): tags = self._get_pool_tags(context, pool) description = pool.get('description') lb_algorithm = lb_const.LB_POOL_ALGORITHM_MAP.get(pool['lb_algorithm']) + if pool.get('listeners') and len(pool['listeners']) > 1: + completor(success=False) + msg = (_('Failed to create pool: Multiple listeners are not ' + 'supported.')) + raise n_exc.BadRequest(resource='lbaas-pool', msg=msg) + # NOTE(salv-orlando): Guard against accidental compat breakages try: listener = pool['listener'] or pool['listeners'][0] @@ -268,6 +274,7 @@ class EdgePoolManagerFromDict(base_mgr.Nsxv3LoadbalancerBaseManager): nsx_db.update_nsx_lbaas_pool_binding( context.session, lb_id, pool['id'], vs_id) else: + completor(success=False) msg = (_("Couldn't find binding on the listener: %s") % listener['id']) raise nsx_exc.NsxPluginException(err_msg=msg) @@ -292,9 +299,15 @@ class EdgePoolManagerFromDict(base_mgr.Nsxv3LoadbalancerBaseManager): binding = nsx_db.get_nsx_lbaas_pool_binding( context.session, old_pool['loadbalancer_id'], old_pool['id']) if not binding: + completor(success=False) msg = (_('Cannot find pool %(pool)s binding on NSX db ' 'mapping') % {'pool': old_pool['id']}) raise n_exc.BadRequest(resource='lbaas-pool', msg=msg) + if new_pool.get('listeners') and len(new_pool['listeners']) > 1: + completor(success=False) + msg = (_('Failed to update pool %s: Multiple listeners are not ' + 'supported.') % new_pool['id']) + raise n_exc.BadRequest(resource='lbaas-pool', msg=msg) # NOTE(salv-orlando): Guard against accidental compat breakages try: listener = new_pool['listener'] or new_pool['listeners'][0] diff --git a/vmware_nsx/tests/unit/services/lbaas/test_nsxv3_driver.py b/vmware_nsx/tests/unit/services/lbaas/test_nsxv3_driver.py index 226bf8fd09..b88cbe94c8 100644 --- a/vmware_nsx/tests/unit/services/lbaas/test_nsxv3_driver.py +++ b/vmware_nsx/tests/unit/services/lbaas/test_nsxv3_driver.py @@ -46,6 +46,8 @@ LB_NETWORK = {'router:external': False, 'id': 'xxxxx', 'name': 'network-1'} LISTENER_ID = 'listener-x' +HTTP_LISTENER_ID = 'listener-http' +HTTPS_LISTENER_ID = 'listener-https' APP_PROFILE_ID = 'appp-x' LB_VS_ID = 'vs-x' LB_APP_PROFILE = { @@ -162,10 +164,10 @@ class BaseTestEdgeLbaasV2(base.BaseTestCase): 'HTTP', protocol_port=80, loadbalancer=self.lb) self.https_listener = lb_models.Listener( - LISTENER_ID, LB_TENANT_ID, 'listener1', '', None, LB_ID, + HTTP_LISTENER_ID, LB_TENANT_ID, 'listener2', '', None, LB_ID, 'HTTPS', protocol_port=443, loadbalancer=self.lb) self.terminated_https_listener = lb_models.Listener( - LISTENER_ID, LB_TENANT_ID, 'listener1', '', None, LB_ID, + HTTPS_LISTENER_ID, LB_TENANT_ID, 'listener3', '', None, LB_ID, 'TERMINATED_HTTPS', protocol_port=443, loadbalancer=self.lb) self.pool = lb_models.Pool(POOL_ID, LB_TENANT_ID, 'pool1', '', None, 'HTTP', 'ROUND_ROBIN', @@ -449,7 +451,7 @@ class TestEdgeLbaasV2Listener(BaseTestEdgeLbaasV2): mock_add_virtual_server.assert_called_with(LB_SERVICE_ID, LB_VS_ID) mock_add_listener_binding.assert_called_with( - self.context.session, LB_ID, LISTENER_ID, APP_PROFILE_ID, + self.context.session, LB_ID, listener.id, APP_PROFILE_ID, LB_VS_ID) mock_successful_completion = ( self.lbv2_driver.listener.successful_completion) @@ -489,7 +491,7 @@ class TestEdgeLbaasV2Listener(BaseTestEdgeLbaasV2): mock_add_virtual_server.assert_called_with(LB_SERVICE_ID, LB_VS_ID) mock_add_listener_binding.assert_called_with( - self.context.session, LB_ID, LISTENER_ID, APP_PROFILE_ID, + self.context.session, LB_ID, HTTPS_LISTENER_ID, APP_PROFILE_ID, LB_VS_ID) mock_successful_completion = ( @@ -498,6 +500,65 @@ class TestEdgeLbaasV2Listener(BaseTestEdgeLbaasV2): self.context, self.terminated_https_listener, delete=False) + def test_create_listener_with_default_pool(self): + listener = lb_models.Listener(LISTENER_ID, LB_TENANT_ID, + 'listener1', 'Dummy', self.pool.id, + LB_ID, 'HTTP', protocol_port=80, + loadbalancer=self.lb, + default_pool=self.pool) + with mock.patch.object(self.core_plugin, 'get_floatingips' + ) as mock_get_floatingips, \ + mock.patch.object(self.app_client, 'create' + ) as mock_create_app_profile, \ + mock.patch.object(self.vs_client, 'create' + ) as mock_create_virtual_server, \ + mock.patch.object(nsx_db, 'get_nsx_lbaas_loadbalancer_binding' + ) as mock_get_lb_binding, \ + mock.patch.object(self.service_client, 'add_virtual_server' + ) as mock_add_virtual_server, \ + mock.patch.object(nsx_db, 'add_nsx_lbaas_listener_binding' + ) as mock_add_listener_binding,\ + mock.patch.object(nsx_db, 'get_nsx_lbaas_pool_binding' + ) as mock_get_pool_binding: + mock_get_floatingips.return_value = [] + mock_create_app_profile.return_value = {'id': APP_PROFILE_ID} + mock_create_virtual_server.return_value = {'id': LB_VS_ID} + mock_get_lb_binding.return_value = LB_BINDING + mock_get_pool_binding.return_value = None + + self.edge_driver.listener.create(self.context, listener) + + mock_add_virtual_server.assert_called_with(LB_SERVICE_ID, + LB_VS_ID) + mock_add_listener_binding.assert_called_with( + self.context.session, LB_ID, LISTENER_ID, APP_PROFILE_ID, + LB_VS_ID) + mock_successful_completion = ( + self.lbv2_driver.listener.successful_completion) + mock_successful_completion.assert_called_with(self.context, + listener, + delete=False) + + def test_create_listener_with_used_default_pool(self): + listener = lb_models.Listener(LISTENER_ID, LB_TENANT_ID, + 'listener1', 'Dummy', self.pool.id, + LB_ID, 'HTTP', protocol_port=80, + loadbalancer=self.lb, + default_pool=self.pool) + with mock.patch.object(self.core_plugin, 'get_floatingips' + ) as mock_get_floatingips, \ + mock.patch.object(nsx_db, 'get_nsx_lbaas_loadbalancer_binding' + ) as mock_get_lb_binding, \ + mock.patch.object(nsx_db, 'get_nsx_lbaas_pool_binding' + ) as mock_get_pool_binding: + mock_get_floatingips.return_value = [] + mock_get_lb_binding.return_value = LB_BINDING + mock_get_pool_binding.return_value = POOL_BINDING + + self.assertRaises(n_exc.BadRequest, + self.edge_driver.listener.create, + self.context, listener) + def test_update(self): new_listener = lb_models.Listener(LISTENER_ID, LB_TENANT_ID, 'listener1-new', 'new-description', @@ -519,6 +580,32 @@ class TestEdgeLbaasV2Listener(BaseTestEdgeLbaasV2): new_listener, delete=False) + def test_update_with_default_pool(self): + new_listener = lb_models.Listener(LISTENER_ID, LB_TENANT_ID, + 'listener1-new', 'new-description', + self.pool, LB_ID, protocol_port=80, + loadbalancer=self.lb, + default_pool=self.pool) + with mock.patch.object(self.core_plugin, 'get_floatingips' + ) as mock_get_floatingips, \ + mock.patch.object(nsx_db, 'get_nsx_lbaas_listener_binding' + ) as mock_get_listener_binding,\ + mock.patch.object(nsx_db, 'get_nsx_lbaas_pool_binding' + ) as mock_get_pool_binding,\ + mock.patch.object(nsx_db, 'update_nsx_lbaas_pool_binding'): + mock_get_floatingips.return_value = [] + mock_get_listener_binding.return_value = LISTENER_BINDING + mock_get_pool_binding.return_value = POOL_BINDING + + self.edge_driver.listener.update(self.context, self.listener, + new_listener) + + mock_successful_completion = ( + self.lbv2_driver.listener.successful_completion) + mock_successful_completion.assert_called_with(self.context, + new_listener, + delete=False) + def test_delete(self): with mock.patch.object(nsx_db, 'get_nsx_lbaas_listener_binding' ) as mock_get_listener_binding, \ @@ -686,6 +773,18 @@ class TestEdgeLbaasV2Pool(BaseTestEdgeLbaasV2): self.pool_persistency.listeners = [] self._test_create_with_persistency(vs_data, verify_func) + def test_create_multiple_listeners(self): + """Verify creation will fail if multiple listeners are set""" + pool = lb_models.Pool(POOL_ID, LB_TENANT_ID, 'pool1', '', + None, 'HTTP', 'ROUND_ROBIN', + loadbalancer_id=LB_ID, + listeners=[self.listener, + self.https_listener], + loadbalancer=self.lb) + self.assertRaises(n_exc.BadRequest, + self.edge_driver.pool.create, + self.context, pool) + def test_update(self): new_pool = lb_models.Pool(POOL_ID, LB_TENANT_ID, 'pool-name', '', None, 'HTTP', 'LEAST_CONNECTIONS', @@ -702,6 +801,21 @@ class TestEdgeLbaasV2Pool(BaseTestEdgeLbaasV2): new_pool, delete=False) + def test_update_multiple_listeners(self): + """Verify update action will fail if multiple listeners are set""" + new_pool = lb_models.Pool(POOL_ID, LB_TENANT_ID, 'pool1', '', + None, 'HTTP', 'ROUND_ROBIN', + loadbalancer_id=LB_ID, + listeners=[self.listener, + self.https_listener], + loadbalancer=self.lb) + with mock.patch.object(nsx_db, 'get_nsx_lbaas_pool_binding' + ) as mock_get_pool_binding: + mock_get_pool_binding.return_value = POOL_BINDING + self.assertRaises(n_exc.BadRequest, + self.edge_driver.pool.update, + self.context, self.pool, new_pool) + def _test_update_with_persistency(self, vs_data, old_pool, new_pool, verify_func): with mock.patch.object(nsx_db, 'get_nsx_lbaas_pool_binding'