LBaaS: Session persistence for NSX-v3
Implement pool session persistence in the NSX-v3 driver. This patch does also a certain amount of refactoring to ensure still a single call is made for updating the NSX virtual server. Persitence profile deletion happens via a deferred function as the virtual server must be updated first to not use anymore the persistence profile. Updating a pool from cookie-based to source-ip based session persistence is currently not allowed and will result in a driver error. Change-Id: Id9fc4e664c5a212a411b4d4eda688bad5c74e869
This commit is contained in:
parent
eb8218c4ea
commit
2e010fd9b2
@ -57,6 +57,8 @@ SESSION_PERSISTENCE_COOKIE_MAP = {
|
||||
LB_SESSION_PERSISTENCE_APP_COOKIE: 'app',
|
||||
LB_SESSION_PERSISTENCE_HTTP_COOKIE: 'insert'}
|
||||
|
||||
SESSION_PERSISTENCE_DEFAULT_COOKIE_NAME = 'default_cookie_name'
|
||||
|
||||
L7_POLICY_ACTION_REJECT = 'REJECT'
|
||||
L7_POLICY_ACTION_REDIRECT_TO_POOL = 'REDIRECT_TO_POOL'
|
||||
L7_POLICY_ACTION_REDIRECT_TO_URL = 'REDIRECT_TO_URL'
|
||||
|
@ -64,7 +64,7 @@ def listener_to_edge_app_profile(listener, edge_cert_id):
|
||||
lb_const.SESSION_PERSISTENCE_COOKIE_MAP):
|
||||
cookie_name = pool_sess_persist.get('cookie_name', None)
|
||||
if cookie_name is None:
|
||||
cookie_name = 'default_cookie_name'
|
||||
cookie_name = lb_const.SESSION_PERSISTENCE_DEFAULT_COOKIE_NAME
|
||||
persistence.update({
|
||||
'cookieName': cookie_name,
|
||||
'cookieMode': lb_const.SESSION_PERSISTENCE_COOKIE_MAP[
|
||||
|
@ -13,6 +13,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import functools
|
||||
|
||||
from neutron_lib import exceptions as n_exc
|
||||
from oslo_log import helpers as log_helpers
|
||||
from oslo_log import log as logging
|
||||
@ -25,6 +27,7 @@ from vmware_nsx.services.lbaas import base_mgr
|
||||
from vmware_nsx.services.lbaas import lb_const
|
||||
from vmware_nsx.services.lbaas.nsx_v3.implementation import lb_utils
|
||||
from vmware_nsxlib.v3 import exceptions as nsxlib_exc
|
||||
from vmware_nsxlib.v3 import load_balancer as nsxlib_lb
|
||||
from vmware_nsxlib.v3 import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -49,6 +52,163 @@ class EdgePoolManagerFromDict(base_mgr.Nsxv3LoadbalancerBaseManager):
|
||||
kwargs['snat_translation'] = {'type': "LbSnatAutoMap"}
|
||||
return kwargs
|
||||
|
||||
def _build_persistence_profile_tags(self, pool_tags, listener):
|
||||
tags = pool_tags[:]
|
||||
tags.append({
|
||||
'scope': 'os-lbaas-lb-name',
|
||||
'tag': listener['loadbalancer']['name'][:utils.MAX_TAG_LEN]})
|
||||
tags.append({
|
||||
'scope': 'os-lbaas-lb-id',
|
||||
'tag': listener['loadbalancer_id']})
|
||||
tags.append({
|
||||
'scope': 'os-lbaas-listener-id',
|
||||
'tag': listener['id']})
|
||||
return tags
|
||||
|
||||
def _validate_session_persistence(self, pool, listener, completor,
|
||||
old_pool=None):
|
||||
sp = pool.get('session_persistence')
|
||||
if not listener or not sp:
|
||||
# safety first!
|
||||
return
|
||||
# L4 listeners only allow source IP persistence
|
||||
if (listener['protocol'] == lb_const.LB_PROTOCOL_TCP and
|
||||
sp['type'] != lb_const.LB_SESSION_PERSISTENCE_SOURCE_IP):
|
||||
completor(success=False)
|
||||
msg = (_("Invalid session persistence type %(sp_type)s for "
|
||||
"pool on listener %(lst_id)s with %(proto)s protocol") %
|
||||
{'sp_type': sp['type'],
|
||||
'lst_id': listener['id'],
|
||||
'proto': listener['protocol']})
|
||||
raise n_exc.BadRequest(resource='lbaas-pool', msg=msg)
|
||||
# Cannot switch (yet) on update from source IP to cookie based, and
|
||||
# vice versa
|
||||
cookie_pers_types = (lb_const.LB_SESSION_PERSISTENCE_HTTP_COOKIE,
|
||||
lb_const.LB_SESSION_PERSISTENCE_APP_COOKIE)
|
||||
if old_pool:
|
||||
oldsp = old_pool.get('session_persistence')
|
||||
if not oldsp:
|
||||
return
|
||||
if ((sp['type'] == lb_const.LB_SESSION_PERSISTENCE_SOURCE_IP and
|
||||
oldsp['type'] in cookie_pers_types) or
|
||||
(sp['type'] in cookie_pers_types and
|
||||
oldsp['type'] == lb_const.LB_SESSION_PERSISTENCE_SOURCE_IP)):
|
||||
completor(success=False)
|
||||
msg = (_("Cannot update session persistence type to "
|
||||
"%(sp_type)s for pool on listener %(lst_id)s "
|
||||
"from %(old_sp_type)s") %
|
||||
{'sp_type': sp['type'],
|
||||
'lst_id': listener['id'],
|
||||
'old_sp_type': oldsp['type']})
|
||||
raise n_exc.BadRequest(resource='lbaas-pool', msg=msg)
|
||||
|
||||
def _setup_session_persistence(self, pool, pool_tags,
|
||||
listener, vs_data):
|
||||
sp = pool.get('session_persistence')
|
||||
pers_type = None
|
||||
cookie_name = None
|
||||
cookie_mode = None
|
||||
if not sp:
|
||||
LOG.debug("No session persistence info for pool %s", pool['id'])
|
||||
elif sp['type'] == lb_const.LB_SESSION_PERSISTENCE_HTTP_COOKIE:
|
||||
pers_type = nsxlib_lb.PersistenceProfileTypes.COOKIE
|
||||
cookie_name = sp.get('cookie_name')
|
||||
if not cookie_name:
|
||||
cookie_name = lb_const.SESSION_PERSISTENCE_DEFAULT_COOKIE_NAME
|
||||
cookie_mode = "INSERT"
|
||||
elif sp['type'] == lb_const.LB_SESSION_PERSISTENCE_APP_COOKIE:
|
||||
pers_type = nsxlib_lb.PersistenceProfileTypes.COOKIE
|
||||
# In this case cookie name is mandatory
|
||||
cookie_name = sp['cookie_name']
|
||||
cookie_mode = "REWRITE"
|
||||
else:
|
||||
pers_type = nsxlib_lb.PersistenceProfileTypes.SOURCE_IP
|
||||
|
||||
if pers_type:
|
||||
# There is a profile to create or update
|
||||
pp_kwargs = {
|
||||
'resource_type': pers_type,
|
||||
'display_name': "persistence_%s" % utils.get_name_and_uuid(
|
||||
pool['name'] or 'pool', pool['id'], maxlen=235),
|
||||
'tags': self._build_persistence_profile_tags(
|
||||
pool_tags, listener)
|
||||
}
|
||||
if cookie_name:
|
||||
pp_kwargs['cookie_name'] = cookie_name
|
||||
pp_kwargs['cookie_mode'] = cookie_mode
|
||||
|
||||
pp_client = self.core_plugin.nsxlib.load_balancer.persistence_profile
|
||||
persistence_profile_id = vs_data.get('persistence_profile_id')
|
||||
if persistence_profile_id:
|
||||
# NOTE: removal of the persistence profile must be executed
|
||||
# after the virtual server has been updated
|
||||
if pers_type:
|
||||
# Update existing profile
|
||||
LOG.debug("Updating persistence profile %(profile_id)s for "
|
||||
"listener %(listener_id)s with pool %(pool_id)s",
|
||||
{'profile_id': persistence_profile_id,
|
||||
'listener_id': listener['id'],
|
||||
'pool_id': pool['id']})
|
||||
pp_client.update(persistence_profile_id, **pp_kwargs)
|
||||
return persistence_profile_id, None
|
||||
else:
|
||||
# Prepare removal of persistence profile
|
||||
return (None, functools.partial(self._remove_persistence,
|
||||
vs_data))
|
||||
elif pers_type:
|
||||
# Create persistence profile
|
||||
pp_data = pp_client.create(**pp_kwargs)
|
||||
LOG.debug("Creaed persistence profile %(profile_id)s for "
|
||||
"listener %(listener_id)s with pool %(pool_id)s",
|
||||
{'profile_id': pp_data['id'],
|
||||
'listener_id': listener['id'],
|
||||
'pool_id': pool['id']})
|
||||
return pp_data['id'], None
|
||||
|
||||
def _remove_persistence(self, vs_data):
|
||||
pp_client = self.core_plugin.nsxlib.load_balancer.persistence_profile
|
||||
persistence_profile_id = vs_data.get('persistence_profile_id')
|
||||
if persistence_profile_id:
|
||||
pp_client.delete(persistence_profile_id)
|
||||
|
||||
def _process_vs_update(self, context, pool, listener,
|
||||
nsx_pool_id, nsx_vs_id, completor):
|
||||
vs_client = self.core_plugin.nsxlib.load_balancer.virtual_server
|
||||
try:
|
||||
# Process pool persistence profile and
|
||||
# create/update/delete profile for virtual server
|
||||
vs_data = vs_client.get(nsx_vs_id)
|
||||
if nsx_pool_id:
|
||||
(persistence_profile_id,
|
||||
post_process_func) = self._setup_session_persistence(
|
||||
pool, self._get_pool_tags(context, pool),
|
||||
listener, vs_data)
|
||||
else:
|
||||
post_process_func = functools.partial(
|
||||
self._remove_persistence, vs_data)
|
||||
persistence_profile_id = None
|
||||
except nsxlib_exc.ManagerError:
|
||||
with excutils.save_and_reraise_exception():
|
||||
completor(success=False)
|
||||
LOG.error("Failed to configure session persistence "
|
||||
"profile for pool %(pool_id)s",
|
||||
{'pool_id': pool['id']})
|
||||
try:
|
||||
# Update persistence profile and pool on virtual server
|
||||
vs_client.update(nsx_vs_id, pool_id=nsx_pool_id,
|
||||
persistence_profile_id=persistence_profile_id)
|
||||
LOG.debug("Updated NSX virtual server %(vs_id)s with "
|
||||
"pool %(pool_id)s and persistence profile %(prof)s",
|
||||
{'vs_id': nsx_vs_id, 'pool_id': nsx_pool_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 pool %s to virtual '
|
||||
'server %s', nsx_pool_id, nsx_vs_id)
|
||||
|
||||
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'],
|
||||
@ -57,11 +217,19 @@ class EdgePoolManagerFromDict(base_mgr.Nsxv3LoadbalancerBaseManager):
|
||||
def create(self, context, pool, completor):
|
||||
lb_id = pool['loadbalancer_id']
|
||||
pool_client = self.core_plugin.nsxlib.load_balancer.pool
|
||||
vs_client = self.core_plugin.nsxlib.load_balancer.virtual_server
|
||||
pool_name = utils.get_name_and_uuid(pool['name'] or 'pool', pool['id'])
|
||||
tags = self._get_pool_tags(context, pool)
|
||||
description = pool.get('description')
|
||||
lb_algorithm = lb_const.LB_POOL_ALGORITHM_MAP.get(pool['lb_algorithm'])
|
||||
# NOTE(salv-orlando): Guard against accidental compat breakages
|
||||
try:
|
||||
listener = pool['listener'] or pool['listeners'][0]
|
||||
except IndexError:
|
||||
# If listeners is an empty list we hit this exception
|
||||
listener = None
|
||||
# Perform additional validation for session persistence before
|
||||
# creating resources in the backend
|
||||
self._validate_session_persistence(pool, listener, completor)
|
||||
try:
|
||||
kwargs = self._get_pool_kwargs(pool_name, tags, lb_algorithm,
|
||||
description)
|
||||
@ -78,32 +246,22 @@ class EdgePoolManagerFromDict(base_mgr.Nsxv3LoadbalancerBaseManager):
|
||||
# --loadbalancer option. If listener is present, the virtual server
|
||||
# will be updated with the pool. Otherwise, just return. The binding
|
||||
# will be added later when the pool is associated with layer7 rule.
|
||||
# NOTE(salv-orlando): Guard against accidental compat breakages
|
||||
try:
|
||||
listener = pool['listener'] or pool['listeners'][0]
|
||||
except IndexError:
|
||||
# If listeners is an empty list we hit this exception
|
||||
listener = None
|
||||
# FIXME(salv-orlando): This two-step process can leave a zombie pool on
|
||||
# NSX if the VS update operation fails
|
||||
if listener:
|
||||
listener_id = listener['id']
|
||||
binding = nsx_db.get_nsx_lbaas_listener_binding(
|
||||
context.session, lb_id, listener_id)
|
||||
if binding:
|
||||
vs_id = binding['lb_vs_id']
|
||||
try:
|
||||
vs_client.update(vs_id, pool_id=lb_pool['id'])
|
||||
except nsxlib_exc.ManagerError:
|
||||
with excutils.save_and_reraise_exception():
|
||||
completor(success=False)
|
||||
LOG.error('Failed to attach pool %s to virtual '
|
||||
'server %s', lb_pool['id'], vs_id)
|
||||
self._process_vs_update(context, pool, listener,
|
||||
lb_pool['id'], vs_id, completor)
|
||||
nsx_db.update_nsx_lbaas_pool_binding(
|
||||
context.session, lb_id, pool['id'], vs_id)
|
||||
else:
|
||||
msg = (_("Couldn't find binding on the listener: %s") %
|
||||
listener_id)
|
||||
listener['id'])
|
||||
raise nsx_exc.NsxPluginException(err_msg=msg)
|
||||
|
||||
completor(success=True)
|
||||
|
||||
def update(self, context, old_pool, new_pool, completor):
|
||||
@ -127,11 +285,27 @@ class EdgePoolManagerFromDict(base_mgr.Nsxv3LoadbalancerBaseManager):
|
||||
msg = (_('Cannot find pool %(pool)s binding on NSX db '
|
||||
'mapping') % {'pool': old_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]
|
||||
except IndexError:
|
||||
# If listeners is an empty list we hit this exception
|
||||
listener = None
|
||||
# Perform additional validation for session persistence before
|
||||
# operating on resources in the backend
|
||||
self._validate_session_persistence(new_pool, listener, completor,
|
||||
old_pool=old_pool)
|
||||
|
||||
try:
|
||||
lb_pool_id = binding['lb_pool_id']
|
||||
kwargs = self._get_pool_kwargs(pool_name, tags, lb_algorithm,
|
||||
description)
|
||||
pool_client.update(lb_pool_id, **kwargs)
|
||||
if (listener and new_pool['session_persistence'] !=
|
||||
old_pool['session_persistence']):
|
||||
self._process_vs_update(context, new_pool, listener,
|
||||
lb_pool_id, binding['lb_vs_id'],
|
||||
completor)
|
||||
completor(success=True)
|
||||
except Exception as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
@ -143,24 +317,23 @@ class EdgePoolManagerFromDict(base_mgr.Nsxv3LoadbalancerBaseManager):
|
||||
def delete(self, context, pool, completor):
|
||||
lb_id = pool['loadbalancer_id']
|
||||
pool_client = self.core_plugin.nsxlib.load_balancer.pool
|
||||
vs_client = self.core_plugin.nsxlib.load_balancer.virtual_server
|
||||
|
||||
binding = nsx_db.get_nsx_lbaas_pool_binding(
|
||||
context.session, lb_id, pool['id'])
|
||||
if binding:
|
||||
vs_id = binding.get('lb_vs_id')
|
||||
lb_pool_id = binding.get('lb_pool_id')
|
||||
|
||||
if vs_id:
|
||||
# NOTE(salv-orlando): Guard against accidental compat breakages
|
||||
try:
|
||||
vs_client.update(vs_id, pool_id='')
|
||||
except nsxlib_exc.ResourceNotFound:
|
||||
pass
|
||||
except nsxlib_exc.ManagerError:
|
||||
completor(success=False)
|
||||
msg = _('Failed to remove lb pool %(pool)s from virtual '
|
||||
'server %(vs)s') % {'pool': lb_pool_id,
|
||||
'vs': vs_id}
|
||||
raise n_exc.BadRequest(resource='lbaas-pool', msg=msg)
|
||||
listener = pool['listener'] or pool['listeners'][0]
|
||||
except IndexError:
|
||||
# If listeners is an empty list we hit this exception
|
||||
listener = None
|
||||
if listener:
|
||||
self._process_vs_update(context, pool, listener,
|
||||
None, vs_id, completor)
|
||||
try:
|
||||
pool_client.delete(lb_pool_id)
|
||||
except nsxlib_exc.ResourceNotFound:
|
||||
|
@ -100,6 +100,7 @@ L7RULE_ID = 'l7rule-111'
|
||||
L7POLICY_BINDING = {'l7policy_id': L7POLICY_ID,
|
||||
'lb_vs_id': LB_VS_ID,
|
||||
'lb_rule_id': LB_RULE_ID}
|
||||
LB_PP_ID = "ppp-ppp"
|
||||
|
||||
FAKE_CERT = {'id': 'cert-xyz'}
|
||||
|
||||
@ -164,6 +165,15 @@ class BaseTestEdgeLbaasV2(base.BaseTestCase):
|
||||
listener=self.listener,
|
||||
listeners=[self.listener],
|
||||
loadbalancer=self.lb)
|
||||
self.sess_persistence = lb_models.SessionPersistence(
|
||||
POOL_ID, 'HTTP_COOKIE', 'meh_cookie')
|
||||
self.pool_persistency = lb_models.Pool(POOL_ID, LB_TENANT_ID,
|
||||
'pool1', '', None, 'HTTP',
|
||||
'ROUND_ROBIN', loadbalancer_id=LB_ID,
|
||||
listener=self.listener,
|
||||
listeners=[self.listener],
|
||||
loadbalancer=self.lb,
|
||||
session_persistence=self.sess_persistence)
|
||||
self.member = lb_models.Member(MEMBER_ID, LB_TENANT_ID, POOL_ID,
|
||||
MEMBER_ADDRESS, 80, 1, pool=self.pool,
|
||||
name='member1')
|
||||
@ -218,6 +228,8 @@ class BaseTestEdgeLbaasV2(base.BaseTestCase):
|
||||
'monitor').start()
|
||||
self.rule_client = mock.patch.object(load_balancer,
|
||||
'rule').start()
|
||||
self.pp_client = mock.patch.object(load_balancer,
|
||||
'persistence_profile').start()
|
||||
self.tm_client = mock.patch.object(nsxlib,
|
||||
'trust_management').start()
|
||||
|
||||
@ -477,7 +489,10 @@ class TestEdgeLbaasV2Pool(BaseTestEdgeLbaasV2):
|
||||
) as mock_add_pool_binding, \
|
||||
mock.patch.object(nsx_db, 'get_nsx_lbaas_listener_binding'
|
||||
) as mock_get_listener_binding, \
|
||||
mock.patch.object(self.vs_client, 'update', return_value=None), \
|
||||
mock.patch.object(self.pp_client, 'create'
|
||||
) as mock_create_pp, \
|
||||
mock.patch.object(self.vs_client, 'update', return_value=None
|
||||
) as mock_vs_update, \
|
||||
mock.patch.object(nsx_db, 'update_nsx_lbaas_pool_binding'
|
||||
) as mock_update_pool_binding:
|
||||
mock_create_pool.return_value = {'id': LB_POOL_ID}
|
||||
@ -487,6 +502,9 @@ class TestEdgeLbaasV2Pool(BaseTestEdgeLbaasV2):
|
||||
|
||||
mock_add_pool_binding.assert_called_with(
|
||||
self.context.session, LB_ID, POOL_ID, LB_POOL_ID)
|
||||
mock_create_pp.assert_not_called()
|
||||
mock_vs_update.assert_called_once_with(
|
||||
LB_VS_ID, pool_id=LB_POOL_ID, persistence_profile_id=None)
|
||||
mock_update_pool_binding.assert_called_with(
|
||||
self.context.session, LB_ID, POOL_ID, LB_VS_ID)
|
||||
mock_successful_completion = (
|
||||
@ -495,6 +513,93 @@ class TestEdgeLbaasV2Pool(BaseTestEdgeLbaasV2):
|
||||
self.pool,
|
||||
delete=False)
|
||||
|
||||
def _test_create_with_persistency(self, vs_data, verify_func):
|
||||
with mock.patch.object(self.pool_client, 'create'
|
||||
) as mock_create_pool, \
|
||||
mock.patch.object(nsx_db, 'add_nsx_lbaas_pool_binding'
|
||||
) as mock_add_pool_binding, \
|
||||
mock.patch.object(nsx_db, 'get_nsx_lbaas_listener_binding'
|
||||
) as mock_get_listener_binding, \
|
||||
mock.patch.object(self.pp_client, 'create'
|
||||
) as mock_create_pp, \
|
||||
mock.patch.object(self.pp_client, 'update', return_value=None,
|
||||
) as mock_update_pp, \
|
||||
mock.patch.object(self.vs_client, 'get'
|
||||
) as mock_vs_get, \
|
||||
mock.patch.object(self.vs_client, 'update', return_value=None
|
||||
) as mock_vs_update, \
|
||||
mock.patch.object(nsx_db, 'update_nsx_lbaas_pool_binding'
|
||||
) as mock_update_pool_binding:
|
||||
|
||||
mock_vs_get.return_value = vs_data
|
||||
mock_create_pool.return_value = {'id': LB_POOL_ID}
|
||||
mock_create_pp.return_value = {'id': LB_PP_ID}
|
||||
mock_get_listener_binding.return_value = LISTENER_BINDING
|
||||
|
||||
self.edge_driver.pool.create(self.context, self.pool_persistency)
|
||||
|
||||
mock_add_pool_binding.assert_called_with(
|
||||
self.context.session, LB_ID, POOL_ID, LB_POOL_ID)
|
||||
verify_func(mock_create_pp, mock_update_pp,
|
||||
mock_update_pool_binding, mock_vs_update)
|
||||
mock_successful_completion = (
|
||||
self.lbv2_driver.pool.successful_completion)
|
||||
mock_successful_completion.assert_called_with(
|
||||
self.context, self.pool_persistency, delete=False)
|
||||
|
||||
def test_create_with_persistency(self):
|
||||
|
||||
def verify_func(mock_create_pp, mock_update_pp,
|
||||
mock_update_pool_binding, mock_vs_update):
|
||||
mock_create_pp.assert_called_once_with(
|
||||
resource_type='LbCookiePersistenceProfile',
|
||||
cookie_mode='INSERT',
|
||||
cookie_name='meh_cookie',
|
||||
display_name=mock.ANY,
|
||||
tags=mock.ANY)
|
||||
mock_update_pp.assert_not_called()
|
||||
mock_update_pool_binding.assert_called_with(
|
||||
self.context.session, LB_ID, POOL_ID, LB_VS_ID)
|
||||
mock_vs_update.assert_called_once_with(
|
||||
LB_VS_ID, pool_id=LB_POOL_ID, persistence_profile_id=LB_PP_ID)
|
||||
|
||||
vs_data = {'id': LB_VS_ID}
|
||||
self._test_create_with_persistency(vs_data, verify_func)
|
||||
|
||||
def test_create_with_persistency_existing_profile(self):
|
||||
def verify_func(mock_create_pp, mock_update_pp,
|
||||
mock_update_pool_binding, mock_vs_update):
|
||||
mock_create_pp.assert_not_called()
|
||||
mock_update_pp.assert_called_once_with(
|
||||
LB_PP_ID,
|
||||
resource_type='LbCookiePersistenceProfile',
|
||||
cookie_mode='INSERT',
|
||||
cookie_name='meh_cookie',
|
||||
display_name=mock.ANY,
|
||||
tags=mock.ANY)
|
||||
mock_update_pool_binding.assert_called_with(
|
||||
self.context.session, LB_ID, POOL_ID, LB_VS_ID)
|
||||
mock_vs_update.assert_called_once_with(
|
||||
LB_VS_ID, pool_id=LB_POOL_ID, persistence_profile_id=LB_PP_ID)
|
||||
|
||||
vs_data = {'id': LB_VS_ID,
|
||||
'persistence_profile_id': LB_PP_ID}
|
||||
self._test_create_with_persistency(vs_data, verify_func)
|
||||
|
||||
def test_create_with_persistency_no_listener(self):
|
||||
def verify_func(mock_create_pp, mock_update_pp,
|
||||
mock_update_pool_binding, mock_vs_update):
|
||||
mock_create_pp.assert_not_called()
|
||||
mock_update_pp.assert_not_called()
|
||||
mock_update_pool_binding.assert_not_called()
|
||||
mock_vs_update.assert_not_called()
|
||||
|
||||
vs_data = {'id': LB_VS_ID,
|
||||
'persistence_profile_id': LB_PP_ID}
|
||||
self.pool_persistency.listener = None
|
||||
self.pool_persistency.listeners = []
|
||||
self._test_create_with_persistency(vs_data, verify_func)
|
||||
|
||||
def test_update(self):
|
||||
new_pool = lb_models.Pool(POOL_ID, LB_TENANT_ID, 'pool-name', '',
|
||||
None, 'HTTP', 'LEAST_CONNECTIONS',
|
||||
@ -511,6 +616,67 @@ class TestEdgeLbaasV2Pool(BaseTestEdgeLbaasV2):
|
||||
new_pool,
|
||||
delete=False)
|
||||
|
||||
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'
|
||||
) as mock_get_pool_binding, \
|
||||
mock.patch.object(self.pp_client, 'create'
|
||||
) as mock_create_pp, \
|
||||
mock.patch.object(self.pp_client, 'update', return_value=None,
|
||||
) as mock_update_pp, \
|
||||
mock.patch.object(self.pp_client, 'delete', return_value=None,
|
||||
) as mock_delete_pp, \
|
||||
mock.patch.object(self.vs_client, 'get'
|
||||
) as mock_vs_get, \
|
||||
mock.patch.object(self.vs_client, 'update', return_value=None
|
||||
) as mock_vs_update:
|
||||
|
||||
mock_vs_get.return_value = vs_data
|
||||
mock_get_pool_binding.return_value = POOL_BINDING
|
||||
mock_create_pp.return_value = {'id': LB_PP_ID}
|
||||
|
||||
self.edge_driver.pool.update(self.context, old_pool, new_pool)
|
||||
|
||||
verify_func(mock_create_pp, mock_update_pp,
|
||||
mock_delete_pp, mock_vs_update)
|
||||
mock_successful_completion = (
|
||||
self.lbv2_driver.pool.successful_completion)
|
||||
mock_successful_completion.assert_called_with(
|
||||
self.context, new_pool, delete=False)
|
||||
|
||||
def test_update_with_persistency(self):
|
||||
|
||||
def verify_func(mock_create_pp, mock_update_pp,
|
||||
mock_delete_pp, mock_vs_update):
|
||||
mock_create_pp.assert_called_once_with(
|
||||
resource_type='LbCookiePersistenceProfile',
|
||||
cookie_mode='INSERT',
|
||||
cookie_name='meh_cookie',
|
||||
display_name=mock.ANY,
|
||||
tags=mock.ANY)
|
||||
mock_update_pp.assert_not_called()
|
||||
mock_delete_pp.assert_not_called()
|
||||
mock_vs_update.assert_called_once_with(
|
||||
LB_VS_ID, pool_id=LB_POOL_ID, persistence_profile_id=LB_PP_ID)
|
||||
|
||||
vs_data = {'id': LB_VS_ID}
|
||||
self._test_update_with_persistency(vs_data, self.pool,
|
||||
self.pool_persistency, verify_func)
|
||||
|
||||
def test_update_remove_persistency(self):
|
||||
def verify_func(mock_create_pp, mock_update_pp,
|
||||
mock_delete_pp, mock_vs_update):
|
||||
mock_create_pp.assert_not_called()
|
||||
mock_update_pp.assert_not_called()
|
||||
mock_delete_pp.assert_called_with(LB_PP_ID)
|
||||
mock_vs_update.assert_called_once_with(
|
||||
LB_VS_ID, pool_id=LB_POOL_ID, persistence_profile_id=None)
|
||||
|
||||
vs_data = {'id': LB_VS_ID,
|
||||
'persistence_profile_id': LB_PP_ID}
|
||||
self._test_update_with_persistency(vs_data, self.pool_persistency,
|
||||
self.pool, verify_func)
|
||||
|
||||
def test_delete(self):
|
||||
with mock.patch.object(nsx_db, 'get_nsx_lbaas_pool_binding'
|
||||
) as mock_get_pool_binding, \
|
||||
@ -527,8 +693,8 @@ class TestEdgeLbaasV2Pool(BaseTestEdgeLbaasV2):
|
||||
|
||||
self.edge_driver.pool.delete(self.context, self.pool)
|
||||
|
||||
mock_update_virtual_server.assert_called_with(LB_VS_ID,
|
||||
pool_id='')
|
||||
mock_update_virtual_server.assert_called_with(
|
||||
LB_VS_ID, persistence_profile_id=None, pool_id=None)
|
||||
mock_delete_pool.assert_called_with(LB_POOL_ID)
|
||||
mock_delete_pool_binding.assert_called_with(
|
||||
self.context.session, LB_ID, POOL_ID)
|
||||
@ -539,6 +705,171 @@ class TestEdgeLbaasV2Pool(BaseTestEdgeLbaasV2):
|
||||
self.pool,
|
||||
delete=True)
|
||||
|
||||
def test_delete_with_persistency(self):
|
||||
with mock.patch.object(nsx_db, 'get_nsx_lbaas_pool_binding'
|
||||
) as mock_get_pool_binding, \
|
||||
mock.patch.object(self.vs_client, 'get'
|
||||
) as mock_vs_get, \
|
||||
mock.patch.object(self.vs_client, 'update', return_value=None
|
||||
) as mock_update_virtual_server, \
|
||||
mock.patch.object(self.pool_client, 'delete'
|
||||
) as mock_delete_pool, \
|
||||
mock.patch.object(self.pp_client, 'delete', return_value=None,
|
||||
) as mock_delete_pp, \
|
||||
mock.patch.object(nsx_db, 'delete_nsx_lbaas_pool_binding'
|
||||
) as mock_delete_pool_binding, \
|
||||
mock.patch.object(nsx_db, 'get_nsx_lbaas_loadbalancer_binding'
|
||||
) as mock_get_lb_binding:
|
||||
mock_get_pool_binding.return_value = POOL_BINDING
|
||||
mock_get_lb_binding.return_value = None
|
||||
mock_vs_get.return_value = {'id': LB_VS_ID,
|
||||
'persistence_profile_id': LB_PP_ID}
|
||||
|
||||
self.edge_driver.pool.delete(self.context, self.pool_persistency)
|
||||
|
||||
mock_delete_pp.assert_called_once_with(LB_PP_ID)
|
||||
mock_update_virtual_server.assert_called_once_with(
|
||||
LB_VS_ID, persistence_profile_id=None, pool_id=None)
|
||||
mock_delete_pool.assert_called_with(LB_POOL_ID)
|
||||
mock_delete_pool_binding.assert_called_with(
|
||||
self.context.session, LB_ID, POOL_ID)
|
||||
|
||||
mock_successful_completion = (
|
||||
self.lbv2_driver.pool.successful_completion)
|
||||
mock_successful_completion.assert_called_with(
|
||||
self.context, self.pool_persistency, delete=True)
|
||||
|
||||
def _verify_create(self, res_type, cookie_name, cookie_mode,
|
||||
mock_create_pp, mock_update_pp):
|
||||
if cookie_name:
|
||||
mock_create_pp.assert_called_once_with(
|
||||
resource_type=res_type,
|
||||
cookie_name=cookie_name,
|
||||
cookie_mode=cookie_mode,
|
||||
display_name=mock.ANY,
|
||||
tags=mock.ANY)
|
||||
else:
|
||||
mock_create_pp.assert_called_once_with(
|
||||
resource_type=res_type,
|
||||
display_name=mock.ANY,
|
||||
tags=mock.ANY)
|
||||
# Compare tags - kw args are the last item of a mock call tuple
|
||||
self.assertItemsEqual(mock_create_pp.mock_calls[0][-1]['tags'],
|
||||
[{'scope': 'os-lbaas-lb-id', 'tag': 'xxx-xxx'},
|
||||
{'scope': 'os-lbaas-lb-name', 'tag': 'lb1'},
|
||||
{'scope': 'os-lbaas-listener-id', 'tag': 'listener-x'}])
|
||||
mock_update_pp.assert_not_called()
|
||||
|
||||
def _verify_update(self, res_type, cookie_name, cookie_mode,
|
||||
mock_create_pp, mock_update_pp):
|
||||
if cookie_name:
|
||||
mock_update_pp.assert_called_once_with(
|
||||
LB_PP_ID,
|
||||
resource_type=res_type,
|
||||
cookie_name=cookie_name,
|
||||
cookie_mode=cookie_mode,
|
||||
display_name=mock.ANY,
|
||||
tags=mock.ANY)
|
||||
else:
|
||||
mock_update_pp.assert_called_once_with(
|
||||
LB_PP_ID,
|
||||
resource_type=res_type,
|
||||
display_name=mock.ANY,
|
||||
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'},
|
||||
{'scope': 'os-lbaas-lb-name', 'tag': 'lb1'},
|
||||
{'scope': 'os-lbaas-listener-id', 'tag': 'listener-x'}])
|
||||
mock_create_pp.assert_not_called()
|
||||
|
||||
def _verify_delete(self, res_type, cookie_name, cookie_mode,
|
||||
mock_create_pp, mock_update_pp):
|
||||
mock_create_pp.assert_not_called()
|
||||
mock_update_pp.assert_not_called()
|
||||
|
||||
def _test_setup_session_persistence(self, session_persistence,
|
||||
res_type, vs_data, verify_func,
|
||||
cookie_name=None, cookie_mode=None):
|
||||
with mock.patch.object(self.pp_client, 'create'
|
||||
) as mock_create_pp, \
|
||||
mock.patch.object(self.pp_client, 'update', return_value=None,
|
||||
) as mock_update_pp:
|
||||
|
||||
mock_create_pp.return_value = {'id': LB_PP_ID}
|
||||
self.pool.session_persistence = session_persistence
|
||||
pool_dict = self.edge_driver.pool.translator(self.pool)
|
||||
list_dict = self.edge_driver.listener.translator(self.listener)
|
||||
pool_impl = self.edge_driver.pool.implementor # make pep8 happy
|
||||
pp_id, post_func = pool_impl._setup_session_persistence(
|
||||
pool_dict, [], list_dict, vs_data)
|
||||
|
||||
if session_persistence:
|
||||
self.assertEqual(LB_PP_ID, pp_id)
|
||||
else:
|
||||
self.assertIsNone(pp_id)
|
||||
self.assertEqual(({'id': LB_VS_ID,
|
||||
'persistence_profile_id': LB_PP_ID},),
|
||||
post_func.args)
|
||||
verify_func(res_type, cookie_name, cookie_mode,
|
||||
mock_create_pp, mock_update_pp)
|
||||
|
||||
def test_setup_session_persistence_sourceip_new_profile(self):
|
||||
sess_persistence = lb_models.SessionPersistence(POOL_ID, 'SOURCE_IP')
|
||||
res_type = 'LbSourceIpPersistenceProfile'
|
||||
self._test_setup_session_persistence(
|
||||
sess_persistence, res_type, {'id': LB_VS_ID}, self._verify_create)
|
||||
|
||||
def test_setup_session_persistence_httpcookie_new_profile(self):
|
||||
sess_persistence = lb_models.SessionPersistence(
|
||||
POOL_ID, 'HTTP_COOKIE')
|
||||
res_type = 'LbCookiePersistenceProfile'
|
||||
self._test_setup_session_persistence(
|
||||
sess_persistence, res_type, {'id': LB_VS_ID},
|
||||
self._verify_create, 'default_cookie_name', 'INSERT')
|
||||
|
||||
def test_setup_session_persistence_appcookie_new_profile(self):
|
||||
sess_persistence = lb_models.SessionPersistence(
|
||||
POOL_ID, 'APP_COOKIE', 'whatever')
|
||||
res_type = 'LbCookiePersistenceProfile'
|
||||
self._test_setup_session_persistence(
|
||||
sess_persistence, res_type, {'id': LB_VS_ID},
|
||||
self._verify_create, 'whatever', 'REWRITE')
|
||||
|
||||
def test_setup_session_persistence_none_from_existing(self):
|
||||
sess_persistence = None
|
||||
self._test_setup_session_persistence(
|
||||
sess_persistence, None,
|
||||
{'id': LB_VS_ID, 'persistence_profile_id': LB_PP_ID},
|
||||
self._verify_delete)
|
||||
|
||||
def test_setup_session_persistence_sourceip_from_existing(self):
|
||||
sess_persistence = lb_models.SessionPersistence(POOL_ID, 'SOURCE_IP')
|
||||
res_type = 'LbSourceIpPersistenceProfile'
|
||||
self._test_setup_session_persistence(
|
||||
sess_persistence, res_type,
|
||||
{'id': LB_VS_ID, 'persistence_profile_id': LB_PP_ID},
|
||||
self._verify_update)
|
||||
|
||||
def test_setup_session_persistence_httpcookie_from_existing(self):
|
||||
sess_persistence = lb_models.SessionPersistence(POOL_ID, 'HTTP_COOKIE')
|
||||
res_type = 'LbCookiePersistenceProfile'
|
||||
self._test_setup_session_persistence(
|
||||
sess_persistence, res_type,
|
||||
{'id': LB_VS_ID, 'persistence_profile_id': LB_PP_ID},
|
||||
self._verify_update,
|
||||
'default_cookie_name', 'INSERT')
|
||||
|
||||
def test_setup_session_persistence_appcookie_from_existing(self):
|
||||
sess_persistence = lb_models.SessionPersistence(
|
||||
POOL_ID, 'APP_COOKIE', 'whatever')
|
||||
res_type = 'LbCookiePersistenceProfile'
|
||||
self._test_setup_session_persistence(
|
||||
sess_persistence, res_type,
|
||||
{'id': LB_VS_ID, 'persistence_profile_id': LB_PP_ID},
|
||||
self._verify_update,
|
||||
'whatever', 'REWRITE')
|
||||
|
||||
|
||||
class TestEdgeLbaasV2Member(BaseTestEdgeLbaasV2):
|
||||
def setUp(self):
|
||||
|
Loading…
Reference in New Issue
Block a user