Add support for SOURCE_IP session persistence

This patch adds support to configure ovn loadbalancer
affinity_timeout option based on the pool session persistence
timeout.

Change-Id: I07c8f3492e62576f66008e8ea1ef9846bed8c6fa
This commit is contained in:
Luis Tomas Bolivar 2023-06-07 11:29:02 +02:00
parent 20997b185f
commit 382ddb0329
5 changed files with 142 additions and 39 deletions

View File

@ -112,3 +112,7 @@ HM_EVENT_MEMBER_PORT_OFFLINE = ['offline']
# max timeout for request
MAX_TIMEOUT_REQUEST = 5
AFFINITY_TIMEOUT = "affinity_timeout"
# This driver only supports SOURCE_IP sesssion persistency option
OVN_NATIVE_SESSION_PERSISTENCE = [constants.SESSION_PERSISTENCE_SOURCE_IP]

View File

@ -68,6 +68,16 @@ class OvnProviderDriver(driver_base.ProviderDriver):
user_fault_string=msg,
operator_fault_string=msg)
def _check_for_supported_session_persistence(self, session):
if (session and
session.get("type") not in
ovn_const.OVN_NATIVE_SESSION_PERSISTENCE):
msg = _('OVN provider does not support %s session persistence. '
'Only SOURCE_IP type is supported.') % session.type
raise driver_exceptions.UnsupportedOptionError(
user_fault_string=msg,
operator_fault_string=msg)
def _check_for_allowed_cidrs(self, allowed_cidrs):
# TODO(haleyb): add support for this
if isinstance(allowed_cidrs, o_datamodels.UnsetType):
@ -140,6 +150,11 @@ class OvnProviderDriver(driver_base.ProviderDriver):
'admin_state_up': admin_state_up}
request = {'type': ovn_const.REQ_TYPE_POOL_CREATE,
'info': request_info}
if not isinstance(
pool.session_persistence, o_datamodels.UnsetType):
self._check_for_supported_session_persistence(
pool.session_persistence)
request['info']['session_persistence'] = pool.session_persistence
self._ovn_helper.add_request(request)
if pool.healthmonitor is not None and not isinstance(
pool.healthmonitor, o_datamodels.UnsetType):
@ -170,6 +185,12 @@ class OvnProviderDriver(driver_base.ProviderDriver):
if not isinstance(new_pool.admin_state_up, o_datamodels.UnsetType):
request_info['admin_state_up'] = new_pool.admin_state_up
if not isinstance(
new_pool.session_persistence, o_datamodels.UnsetType):
self._check_for_supported_session_persistence(
new_pool.session_persistence)
request_info['session_persistence'] = (
new_pool.session_persistence)
request = {'type': ovn_const.REQ_TYPE_POOL_UPDATE,
'info': request_info}
self._ovn_helper.add_request(request)

View File

@ -1605,10 +1605,25 @@ class OvnProviderHelper():
if listener_key in ovn_lb.external_ids:
external_ids[listener_key] = str(
external_ids[listener_key]) + str(pool_key)
persistence_timeout = None
if pool.get(constants.SESSION_PERSISTENCE):
persistence_timeout = pool[constants.SESSION_PERSISTENCE].get(
constants.PERSISTENCE_TIMEOUT, '360')
try:
self.ovn_nbdb_api.db_set(
commands = []
commands.append(self.ovn_nbdb_api.db_set(
'Load_Balancer', ovn_lb.uuid,
('external_ids', external_ids)).execute(check_error=True)
('external_ids', external_ids)))
if persistence_timeout:
options = copy.deepcopy(ovn_lb.options)
options[ovn_const.AFFINITY_TIMEOUT] = str(persistence_timeout)
commands.append(self.ovn_nbdb_api.db_set(
'Load_Balancer', ovn_lb.uuid,
('options', options)))
self._execute_commands(commands)
# Pool status will be set to Online after a member is added to it
# or when it is created with listener.
@ -1693,6 +1708,11 @@ class OvnProviderHelper():
'Load_Balancer', ovn_lb.uuid,
'external_ids', (pool_key_when_disabled)))
if ovn_const.AFFINITY_TIMEOUT in ovn_lb.options:
commands.append(
self.ovn_nbdb_api.db_remove('Load_Balancer', ovn_lb.uuid,
'options',
(ovn_const.AFFINITY_TIMEOUT)))
commands.extend(
self._clean_lb_if_empty(
ovn_lb, pool[constants.LOADBALANCER_ID], external_ids)[0])
@ -1725,7 +1745,8 @@ class OvnProviderHelper():
status = {
constants.POOLS: [pool_status],
constants.LOADBALANCERS: [lbalancer_status]}
if constants.ADMIN_STATE_UP not in pool:
if (constants.ADMIN_STATE_UP not in pool and
constants.SESSION_PERSISTENCE not in pool):
return status
try:
ovn_lb = self._find_ovn_lbs(
@ -1746,37 +1767,54 @@ class OvnProviderHelper():
p_key_to_add = {}
pool_listeners = []
commands = []
try:
pool_listeners = self._get_pool_listeners(ovn_lb, pool_key)
if pool[constants.ADMIN_STATE_UP]:
if p_key_when_disabled in external_ids:
p_key_to_add[pool_key] = external_ids[p_key_when_disabled]
external_ids[pool_key] = external_ids[p_key_when_disabled]
del external_ids[p_key_when_disabled]
p_key_to_remove = p_key_when_disabled
else:
if pool_key in external_ids:
p_key_to_add[p_key_when_disabled] = external_ids[pool_key]
external_ids[p_key_when_disabled] = external_ids[pool_key]
del external_ids[pool_key]
p_key_to_remove = pool_key
admin_state_up = pool.get(constants.ADMIN_STATE_UP)
if admin_state_up is not None:
if admin_state_up:
if p_key_when_disabled in external_ids:
p_key_to_add[pool_key] = external_ids[
p_key_when_disabled]
external_ids[pool_key] = external_ids[
p_key_when_disabled]
del external_ids[p_key_when_disabled]
p_key_to_remove = p_key_when_disabled
else:
if pool_key in external_ids:
p_key_to_add[p_key_when_disabled] = external_ids[
pool_key]
external_ids[p_key_when_disabled] = external_ids[
pool_key]
del external_ids[pool_key]
p_key_to_remove = pool_key
if p_key_to_remove:
commands = []
commands.append(
self.ovn_nbdb_api.db_remove(
'Load_Balancer', ovn_lb.uuid, 'external_ids',
(p_key_to_remove)))
if p_key_to_remove:
commands.append(
self.ovn_nbdb_api.db_remove(
'Load_Balancer', ovn_lb.uuid, 'external_ids',
(p_key_to_remove)))
commands.append(
self.ovn_nbdb_api.db_set(
'Load_Balancer', ovn_lb.uuid,
('external_ids', p_key_to_add)))
commands.append(
self.ovn_nbdb_api.db_set(
'Load_Balancer', ovn_lb.uuid,
('external_ids', p_key_to_add)))
commands.extend(
self._refresh_lb_vips(ovn_lb, external_ids))
if pool.get(constants.SESSION_PERSISTENCE):
new_timeout = pool[constants.SESSION_PERSISTENCE].get(
constants.PERSISTENCE_TIMEOUT, '360')
options = copy.deepcopy(ovn_lb.options)
options[ovn_const.AFFINITY_TIMEOUT] = str(new_timeout)
commands.append(self.ovn_nbdb_api.db_set(
'Load_Balancer', ovn_lb.uuid,
('options', options)))
self._execute_commands(commands)
commands.extend(
self._refresh_lb_vips(ovn_lb, external_ids))
self._execute_commands(commands)
if pool[constants.ADMIN_STATE_UP]:
operating_status = constants.ONLINE
else:

View File

@ -89,7 +89,7 @@ class TestOvnProviderDriver(ovn_base.TestOvnOctaviaBase):
members=[self.ref_member],
pool_id=self.pool_id,
protocol='TCP',
session_persistence={'type': 'fix'})
session_persistence={'type': 'SOURCE_IP'})
self.ref_pool = data_models.Pool(
admin_state_up=True,
description='pool',
@ -100,7 +100,7 @@ class TestOvnProviderDriver(ovn_base.TestOvnOctaviaBase):
members=[self.ref_member],
pool_id=self.pool_id,
protocol='TCP',
session_persistence={'type': 'fix'})
session_persistence={'type': 'SOURCE_IP'})
self.ref_http_pool = data_models.Pool(
admin_state_up=True,
description='pool',
@ -689,7 +689,8 @@ class TestOvnProviderDriver(ovn_base.TestOvnOctaviaBase):
'listener_id': self.ref_pool.listener_id,
'protocol': self.ref_pool.protocol,
'lb_algorithm': constants.LB_ALGORITHM_SOURCE_IP_PORT,
'admin_state_up': self.ref_pool.admin_state_up}
'admin_state_up': self.ref_pool.admin_state_up,
'session_persistence': {'type': 'SOURCE_IP'}}
info_member = {
'id': self.ref_member.member_id,
'address': self.ref_member.address,
@ -758,7 +759,8 @@ class TestOvnProviderDriver(ovn_base.TestOvnOctaviaBase):
'listener_id': self.ref_pool.listener_id,
'protocol': self.ref_pool.protocol,
'lb_algorithm': constants.LB_ALGORITHM_SOURCE_IP_PORT,
'admin_state_up': self.ref_pool.admin_state_up}
'admin_state_up': self.ref_pool.admin_state_up,
'session_persistence': {'type': 'SOURCE_IP'}}
info_member = {
'id': self.ref_member.member_id,
'address': self.ref_member.address,
@ -866,7 +868,8 @@ class TestOvnProviderDriver(ovn_base.TestOvnOctaviaBase):
'listener_id': self.ref_pool.listener_id,
'protocol': self.ref_pool.protocol,
'lb_algorithm': constants.LB_ALGORITHM_SOURCE_IP_PORT,
'admin_state_up': self.ref_pool.admin_state_up}
'admin_state_up': self.ref_pool.admin_state_up,
'session_persistence': {'type': 'SOURCE_IP'}}
expected_dict = {'type': ovn_const.REQ_TYPE_POOL_CREATE,
'info': info}
self.driver.pool_create(self.ref_pool)
@ -879,7 +882,8 @@ class TestOvnProviderDriver(ovn_base.TestOvnOctaviaBase):
'listener_id': self.ref_pool.listener_id,
'protocol': self.ref_pool.protocol,
'lb_algorithm': constants.LB_ALGORITHM_SOURCE_IP_PORT,
'admin_state_up': self.ref_pool.admin_state_up}
'admin_state_up': self.ref_pool.admin_state_up,
'session_persistence': {'type': 'SOURCE_IP'}}
info_hm = {'id': self.ref_health_monitor.healthmonitor_id,
'pool_id': self.ref_health_monitor.pool_id,
'type': self.ref_health_monitor.type,
@ -905,7 +909,21 @@ class TestOvnProviderDriver(ovn_base.TestOvnOctaviaBase):
'protocol': self.ref_pool.protocol,
'lb_algorithm': constants.LB_ALGORITHM_SOURCE_IP_PORT,
'listener_id': self.ref_pool.listener_id,
'admin_state_up': True}
'admin_state_up': True,
'session_persistence': {'type': 'SOURCE_IP'}}
expected_dict = {'type': ovn_const.REQ_TYPE_POOL_CREATE,
'info': info}
self.driver.pool_create(self.ref_pool)
self.mock_add_request.assert_called_once_with(expected_dict)
def test_pool_create_unset_session_persistence(self):
self.ref_pool.session_persistence = data_models.UnsetType()
info = {'id': self.ref_pool.pool_id,
'loadbalancer_id': self.ref_pool.loadbalancer_id,
'protocol': self.ref_pool.protocol,
'lb_algorithm': constants.LB_ALGORITHM_SOURCE_IP_PORT,
'listener_id': self.ref_pool.listener_id,
'admin_state_up': self.ref_pool.admin_state_up}
expected_dict = {'type': ovn_const.REQ_TYPE_POOL_CREATE,
'info': info}
self.driver.pool_create(self.ref_pool)
@ -956,7 +974,8 @@ class TestOvnProviderDriver(ovn_base.TestOvnOctaviaBase):
info = {'id': self.ref_update_pool.pool_id,
'loadbalancer_id': self.ref_update_pool.loadbalancer_id,
'protocol': self.ref_pool.protocol,
'admin_state_up': self.ref_update_pool.admin_state_up}
'admin_state_up': self.ref_update_pool.admin_state_up,
'session_persistence': {'type': 'SOURCE_IP'}}
expected_dict = {'type': ovn_const.REQ_TYPE_POOL_UPDATE,
'info': info}
self.driver.pool_update(self.ref_pool, self.ref_update_pool)
@ -967,7 +986,8 @@ class TestOvnProviderDriver(ovn_base.TestOvnOctaviaBase):
info = {'id': self.ref_update_pool.pool_id,
'loadbalancer_id': self.ref_update_pool.loadbalancer_id,
'protocol': self.ref_pool.protocol,
'admin_state_up': self.ref_update_pool.admin_state_up}
'admin_state_up': self.ref_update_pool.admin_state_up,
'session_persistence': {'type': 'SOURCE_IP'}}
expected_dict = {'type': ovn_const.REQ_TYPE_POOL_UPDATE,
'info': info}
self.driver.pool_update(self.ref_pool, self.ref_update_pool)
@ -978,7 +998,8 @@ class TestOvnProviderDriver(ovn_base.TestOvnOctaviaBase):
info = {'id': self.ref_update_pool.pool_id,
'loadbalancer_id': self.ref_update_pool.loadbalancer_id,
'protocol': self.ref_pool.protocol,
'admin_state_up': self.ref_update_pool.admin_state_up}
'admin_state_up': self.ref_update_pool.admin_state_up,
'session_persistence': {'type': 'SOURCE_IP'}}
expected_dict = {'type': ovn_const.REQ_TYPE_POOL_UPDATE,
'info': info}
self.driver.pool_update(self.ref_pool, self.ref_update_pool)
@ -988,7 +1009,19 @@ class TestOvnProviderDriver(ovn_base.TestOvnOctaviaBase):
self.ref_update_pool.admin_state_up = data_models.UnsetType()
info = {'id': self.ref_update_pool.pool_id,
'loadbalancer_id': self.ref_update_pool.loadbalancer_id,
'protocol': self.ref_pool.protocol}
'protocol': self.ref_pool.protocol,
'session_persistence': {'type': 'SOURCE_IP'}}
expected_dict = {'type': ovn_const.REQ_TYPE_POOL_UPDATE,
'info': info}
self.driver.pool_update(self.ref_pool, self.ref_update_pool)
self.mock_add_request.assert_called_once_with(expected_dict)
def test_pool_update_unset_new_session_timeout(self):
self.ref_update_pool.session_persistence = data_models.UnsetType()
info = {'id': self.ref_update_pool.pool_id,
'loadbalancer_id': self.ref_update_pool.loadbalancer_id,
'protocol': self.ref_pool.protocol,
'admin_state_up': self.ref_update_pool.admin_state_up}
expected_dict = {'type': ovn_const.REQ_TYPE_POOL_UPDATE,
'info': info}
self.driver.pool_update(self.ref_pool, self.ref_update_pool)

View File

@ -0,0 +1,7 @@
---
features:
- |
Now the OVN Octavia provider uses the affinity_timeout option of OVN
Load Balancers to support pools sessions persistence. It only supports
the SOURCE_IP option type. If not timeout is set, by default 360 seconds
is set if the session persistence is enabled.