Skip to content

Commit 80cffad

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Add Pool sync logic"
2 parents 37e0f22 + e7068ae commit 80cffad

File tree

4 files changed

+199
-7
lines changed

4 files changed

+199
-7
lines changed

ovn_octavia_provider/driver.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,26 @@ def _get_listener_request_info(self, listener):
119119
'admin_state_up': admin_state_up}
120120
return request_info
121121

122+
def _get_pool_request_info(self, pool):
123+
self._check_for_supported_protocols(pool.protocol)
124+
self._check_for_supported_algorithms(pool.lb_algorithm)
125+
admin_state_up = pool.admin_state_up
126+
if isinstance(admin_state_up, o_datamodels.UnsetType):
127+
admin_state_up = True
128+
request_info = {'id': pool.pool_id,
129+
'loadbalancer_id': pool.loadbalancer_id,
130+
'protocol': pool.protocol,
131+
'lb_algorithm': pool.lb_algorithm,
132+
'listener_id': pool.listener_id,
133+
'admin_state_up': admin_state_up}
134+
if not isinstance(
135+
pool.session_persistence, o_datamodels.UnsetType):
136+
self._check_for_supported_session_persistence(
137+
pool.session_persistence)
138+
request_info['session_persistence'] = pool.session_persistence
139+
140+
return request_info
141+
122142
def loadbalancer_create(self, loadbalancer):
123143
request = {'type': ovn_const.REQ_TYPE_LB_CREATE,
124144
'info': self._get_loadbalancer_request_info(
@@ -612,7 +632,7 @@ def _ensure_loadbalancer(self, loadbalancer):
612632
except idlutils.RowNotFound:
613633
LOG.debug(f"OVN loadbalancer {loadbalancer.loadbalancer_id} "
614634
"not found. Start create process.")
615-
# TODO(froyo): By now just syncing LB and listener only
635+
# TODO(froyo): By now just syncing LB, listener and pool only
616636
status = self._ovn_helper.lb_create(
617637
self._get_loadbalancer_request_info(loadbalancer))
618638

@@ -622,6 +642,12 @@ def _ensure_loadbalancer(self, loadbalancer):
622642
status_listener = self._ovn_helper.listener_create(
623643
self._get_listener_request_info(listener))
624644
status[constants.LISTENERS].append(status_listener)
645+
if not isinstance(loadbalancer.pools, o_datamodels.UnsetType):
646+
status[constants.POOLS] = []
647+
for pool in loadbalancer.pools:
648+
status_pool = self._ovn_helper.pool_create(
649+
self._get_pool_request_info(pool))
650+
status[constants.POOLS].append(status_pool)
625651
self._ovn_helper._update_status_to_octavia(status)
626652
else:
627653
# Load Balancer found, check LB and listener/pool/member/hms
@@ -638,6 +664,11 @@ def _ensure_loadbalancer(self, loadbalancer):
638664
for listener in loadbalancer.listeners:
639665
self._ovn_helper.listener_sync(
640666
self._get_listener_request_info(listener), ovn_lb)
667+
# Pool
668+
if not isinstance(loadbalancer.pools, o_datamodels.UnsetType):
669+
for pool in loadbalancer.pools:
670+
self._ovn_helper.pool_sync(
671+
self._get_pool_request_info(pool), ovn_lb)
641672
status = self._ovn_helper._get_current_operating_statuses(
642673
ovn_lb)
643674
self._ovn_helper._update_status_to_octavia(status)
@@ -659,4 +690,10 @@ def do_sync(self, **lb_filters):
659690
for listener in listeners
660691
] if listeners else o_datamodels.Unset
661692

693+
pools = provider_lb.pools or []
694+
provider_lb.pools = [
695+
o_datamodels.Pool.from_dict(pool)
696+
for pool in pools
697+
] if pools else o_datamodels.Unset
698+
662699
self._ensure_loadbalancer(provider_lb)

ovn_octavia_provider/helper.py

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,65 @@ def _update_protocol_if_needed(self, listener, ovn_lb, commands):
662662
)
663663
)
664664

665+
def _prepare_external_ids(self, pool, ovn_lb):
666+
"""Prepare the updated external_ids for the LoadBalancer."""
667+
external_ids = copy.deepcopy(ovn_lb.external_ids)
668+
pool_key = self._get_pool_key(
669+
pool[constants.ID],
670+
is_enabled=pool[constants.ADMIN_STATE_UP])
671+
external_ids[pool_key] = ''
672+
673+
if pool[constants.LISTENER_ID]:
674+
self._update_listener_association(
675+
pool, ovn_lb, external_ids, pool_key)
676+
677+
return external_ids
678+
679+
def _update_listener_association(self, pool, ovn_lb, external_ids,
680+
pool_key):
681+
"""Update the listener association in external_ids."""
682+
listener_key = self._get_listener_key(pool[constants.LISTENER_ID])
683+
if listener_key in ovn_lb.external_ids:
684+
pool_key_enable = self._get_pool_key(pool[constants.ID],
685+
is_enabled=True)
686+
pool_key_disable = self._get_pool_key(pool[constants.ID],
687+
is_enabled=False)
688+
689+
if pool[constants.ID] in external_ids[listener_key]:
690+
# Remove existing pool keys before adding the updated key
691+
external_ids[listener_key] = (
692+
external_ids[listener_key]
693+
.replace(pool_key_disable, '')
694+
.replace(pool_key_enable, '')
695+
)
696+
697+
external_ids[listener_key] += str(pool_key)
698+
699+
def _extract_persistence_timeout(self, pool):
700+
"""Extract persistence timeout value from the pool, if available."""
701+
if pool.get(constants.SESSION_PERSISTENCE):
702+
return pool[constants.SESSION_PERSISTENCE].get(
703+
constants.PERSISTENCE_TIMEOUT, '360')
704+
return None
705+
706+
def _add_external_ids_command(self, commands, ovn_lb, external_ids):
707+
"""Add a command to update the external_ids of the LoadBalancer."""
708+
commands.append(
709+
self.ovn_nbdb_api.db_set('Load_Balancer', ovn_lb.uuid,
710+
('external_ids', external_ids))
711+
)
712+
713+
def _add_persistence_timeout_command(self, commands, ovn_lb,
714+
persistence_timeout):
715+
"""Add command to update persistence timeout in LoadBalancer."""
716+
options = copy.deepcopy(ovn_lb.options)
717+
options[ovn_const.AFFINITY_TIMEOUT] = str(persistence_timeout)
718+
if ovn_lb.options != options:
719+
commands.append(
720+
self.ovn_nbdb_api.db_set('Load_Balancer', ovn_lb.uuid,
721+
('options', options))
722+
)
723+
665724
def _lb_status(self, loadbalancer, provisioning_status, operating_status):
666725
"""Return status for the LoadBalancer."""
667726
return {
@@ -2114,7 +2173,11 @@ def pool_create(self, pool):
21142173
external_ids[pool_key] = ''
21152174
if pool[constants.LISTENER_ID]:
21162175
listener_key = self._get_listener_key(pool[constants.LISTENER_ID])
2117-
if listener_key in ovn_lb.external_ids:
2176+
# NOTE(froyo): checking is not already when ovn-db-sync-tool is
2177+
# triggered, because listener_create could be added already if
2178+
# pool is considered as default one
2179+
if listener_key in ovn_lb.external_ids and \
2180+
str(pool_key) not in external_ids[listener_key]:
21182181
external_ids[listener_key] = str(
21192182
external_ids[listener_key]) + str(pool_key)
21202183
persistence_timeout = None
@@ -2173,6 +2236,29 @@ def pool_create(self, pool):
21732236

21742237
return status
21752238

2239+
def pool_sync(self, pool, ovn_lb):
2240+
"""Sync Pool object with an OVN LoadBalancer
2241+
2242+
The method performs the following steps:
2243+
1. Update pool key on OVN Loadbalancer external_ids if needed
2244+
2. Update OVN LoadBalancer options from Pool info
2245+
2246+
:param pool: The source pool object from Octavia DB
2247+
:param ovn_lb: The OVN LoadBalancer object that needs to be sync
2248+
"""
2249+
external_ids = self._prepare_external_ids(pool, ovn_lb)
2250+
persistence_timeout = self._extract_persistence_timeout(pool)
2251+
2252+
try:
2253+
commands = []
2254+
self._add_external_ids_command(commands, ovn_lb, external_ids)
2255+
if persistence_timeout:
2256+
self._add_persistence_timeout_command(commands, ovn_lb,
2257+
persistence_timeout)
2258+
self._execute_commands(commands)
2259+
except Exception as e:
2260+
LOG.exception(f"Failed to execute commands for pool sync: {e}")
2261+
21762262
def pool_delete(self, pool):
21772263
status = {
21782264
constants.POOLS: [

ovn_octavia_provider/tests/unit/test_driver.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,10 +1237,12 @@ def test_health_monitor_delete(self):
12371237

12381238
@mock.patch.object(ovn_helper.OvnProviderHelper,
12391239
'_update_status_to_octavia')
1240+
@mock.patch.object(ovn_helper.OvnProviderHelper, 'pool_create')
12401241
@mock.patch.object(ovn_helper.OvnProviderHelper, 'listener_create')
12411242
@mock.patch.object(ovn_helper.OvnProviderHelper, 'lb_create')
12421243
def test_ensure_loadbalancer_lb_not_found(
1243-
self, mock_lb_create, mock_listener_create, mock_update_status):
1244+
self, mock_lb_create, mock_listener_create, mock_pool_create,
1245+
mock_update_status):
12441246
self.mock_find_ovn_lbs_with_retry.side_effect = [
12451247
idlutils.RowNotFound]
12461248
self.driver._ensure_loadbalancer(self.ref_lb_fully_populated)
@@ -1252,16 +1254,21 @@ def test_ensure_loadbalancer_lb_not_found(
12521254
self.driver._get_listener_request_info(
12531255
self.ref_lb_fully_populated.listeners[0]),
12541256
)
1257+
mock_pool_create.assert_called_once_with(
1258+
self.driver._get_pool_request_info(
1259+
self.ref_lb_fully_populated.pools[0]),
1260+
)
12551261

12561262
@mock.patch.object(ovn_helper.OvnProviderHelper,
12571263
'_update_status_to_octavia')
12581264
@mock.patch.object(ovn_helper.OvnProviderHelper, 'listener_create')
12591265
@mock.patch.object(ovn_helper.OvnProviderHelper, 'lb_create')
1260-
def test_ensure_loadbalancer_lb_no_listener_not_found(
1266+
def test_ensure_loadbalancer_lb_no_listener_no_pool_not_found(
12611267
self, mock_lb_create, mock_listener_create, mock_update_status):
12621268
self.mock_find_ovn_lbs_with_retry.side_effect = [
12631269
idlutils.RowNotFound]
12641270
self.ref_lb_fully_populated.listeners = data_models.Unset
1271+
self.ref_lb_fully_populated.pools = data_models.Unset
12651272
self.driver._ensure_loadbalancer(self.ref_lb_fully_populated)
12661273
mock_lb_create.assert_called_once_with(
12671274
self.driver._get_loadbalancer_request_info(
@@ -1273,11 +1280,12 @@ def test_ensure_loadbalancer_lb_no_listener_not_found(
12731280
'_update_status_to_octavia')
12741281
@mock.patch.object(ovn_helper.OvnProviderHelper,
12751282
'_get_current_operating_statuses')
1283+
@mock.patch.object(ovn_helper.OvnProviderHelper, 'pool_sync')
12761284
@mock.patch.object(ovn_helper.OvnProviderHelper, 'listener_sync')
12771285
@mock.patch.object(ovn_helper.OvnProviderHelper, 'lb_sync')
12781286
def test_ensure_loadbalancer_lb_found(
1279-
self, mock_lb_sync, mock_listener_sync, mock_get_status,
1280-
mock_update_status):
1287+
self, mock_lb_sync, mock_listener_sync, mock_pool_sync,
1288+
mock_get_status, mock_update_status):
12811289
self.mock_find_ovn_lbs_with_retry.return_value = [
12821290
self.ovn_lb]
12831291
self.driver._ensure_loadbalancer(self.ref_lb_fully_populated)
@@ -1327,17 +1335,20 @@ def test_do_sync_no_loadbalancers(self, mock_get_octavia_client,
13271335
mock_ensure_lb.assert_not_called()
13281336

13291337
@mock.patch.object(data_models.Listener, 'from_dict')
1338+
@mock.patch.object(data_models.Pool, 'from_dict')
13301339
@mock.patch.object(o_driver_lib.DriverLibrary, 'get_loadbalancer')
13311340
@mock.patch.object(ovn_helper.OvnProviderHelper, 'get_octavia_lbs')
13321341
@mock.patch.object(clients, 'get_octavia_client')
13331342
def test_do_sync_with_loadbalancers(self,
13341343
mock_get_octavia_client,
13351344
mock_get_octavia_lbs,
13361345
mock_get_loadbalancer,
1346+
mock_pool_from_dict,
13371347
mock_listener_from_dict):
13381348
lb = mock.MagicMock(id=self.ref_lb_fully_sync_populated.name)
13391349
mock_get_octavia_lbs.return_value = [lb]
13401350
mock_get_loadbalancer.return_value = self.ref_lb_fully_sync_populated
1351+
mock_pool_from_dict.return_value = self.ref_pool_with_hm
13411352
mock_listener_from_dict.return_value = self.ref_listener
13421353
lb_filters = {}
13431354
with mock.patch.object(self.driver, '_ensure_loadbalancer') \
@@ -1350,7 +1361,7 @@ def test_do_sync_with_loadbalancers(self,
13501361
@mock.patch.object(o_driver_lib.DriverLibrary, 'get_loadbalancer')
13511362
@mock.patch.object(ovn_helper.OvnProviderHelper, 'get_octavia_lbs')
13521363
@mock.patch.object(clients, 'get_octavia_client')
1353-
def test_do_sync_with_loadbalancers_no_listener(
1364+
def test_do_sync_with_loadbalancers_no_listener_no_pool(
13541365
self,
13551366
mock_get_octavia_client,
13561367
mock_get_octavia_lbs,
@@ -1360,6 +1371,7 @@ def test_do_sync_with_loadbalancers_no_listener(
13601371
mock_get_octavia_lbs.return_value = [lb, lb]
13611372
mock_get_loadbalancer.return_value = self.ref_lb_fully_sync_populated
13621373
self.ref_lb_fully_sync_populated.listeners = data_models.Unset
1374+
self.ref_lb_fully_sync_populated.pools = data_models.Unset
13631375
lb_filters = {}
13641376
with mock.patch.object(self.driver, '_ensure_loadbalancer') \
13651377
as mock_ensure_lb:

ovn_octavia_provider/tests/unit/test_helper.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2209,6 +2209,63 @@ def test_pool_delete_ovn_lb_empty_lb_not_empty(self, lb_empty):
22092209
self.helper.ovn_nbdb_api.lb_del.assert_called_once_with(
22102210
self.ovn_lb.uuid)
22112211

2212+
def test_pool_sync_exception(self):
2213+
self.helper.ovn_nbdb_api.db_set.side_effect = [
2214+
RuntimeError("ERROR_MSG"), RuntimeError("ERROR_MSG")]
2215+
with mock.patch.object(ovn_helper, 'LOG') as m_l:
2216+
self.assertIsNone(self.helper.pool_sync(self.pool, self.ovn_lb))
2217+
m_l.exception.assert_called_once_with(
2218+
'Failed to execute commands for pool sync: ERROR_MSG')
2219+
2220+
def test_pool_sync(self):
2221+
self.helper.pool_sync(self.pool, self.ovn_lb)
2222+
listener_key_value = (f"80:pool_{self.pool_id}:"
2223+
f"{ovn_const.DISABLED_RESOURCE_SUFFIX}")
2224+
expected_calls = [mock.call(
2225+
'Load_Balancer', self.ovn_lb.uuid,
2226+
('external_ids', {
2227+
ovn_const.LB_EXT_IDS_VIP_KEY: mock.ANY,
2228+
ovn_const.LB_EXT_IDS_VIP_FIP_KEY: '123.123.123.123',
2229+
ovn_const.LB_EXT_IDS_VIP_PORT_ID_KEY: mock.ANY,
2230+
'enabled': True,
2231+
f"pool_{self.pool_id}": mock.ANY,
2232+
f"listener_{self.listener_id}": listener_key_value,
2233+
ovn_const.OVN_MEMBER_STATUS_KEY: '{"%s": "%s"}'
2234+
% (self.member_id, constants.NO_MONITOR),
2235+
f"pool_{self.pool_id}:{ovn_const.DISABLED_RESOURCE_SUFFIX}": ''
2236+
}))
2237+
]
2238+
self.helper.ovn_nbdb_api.db_set.assert_has_calls(
2239+
expected_calls)
2240+
2241+
def test_pool_sync_press_key(self):
2242+
self.pool[constants.SESSION_PERSISTENCE] = {
2243+
constants.PERSISTENCE_TIMEOUT: '360'
2244+
}
2245+
self.ovn_lb.options = {'a': 1}
2246+
self.helper.pool_sync(self.pool, self.ovn_lb)
2247+
listener_key_value = (f"80:pool_{self.pool_id}:"
2248+
f"{ovn_const.DISABLED_RESOURCE_SUFFIX}")
2249+
expected_calls = [mock.call(
2250+
'Load_Balancer', self.ovn_lb.uuid,
2251+
('external_ids', {
2252+
ovn_const.LB_EXT_IDS_VIP_KEY: mock.ANY,
2253+
ovn_const.LB_EXT_IDS_VIP_FIP_KEY: '123.123.123.123',
2254+
ovn_const.LB_EXT_IDS_VIP_PORT_ID_KEY: mock.ANY,
2255+
'enabled': True,
2256+
f"pool_{self.pool_id}": mock.ANY,
2257+
f"listener_{self.listener_id}": listener_key_value,
2258+
ovn_const.OVN_MEMBER_STATUS_KEY: '{"%s": "%s"}'
2259+
% (self.member_id, constants.NO_MONITOR),
2260+
f"pool_{self.pool_id}:{ovn_const.DISABLED_RESOURCE_SUFFIX}": ''
2261+
})),
2262+
mock.call(
2263+
'Load_Balancer', self.ovn_lb.uuid,
2264+
('options', {'a': 1, 'affinity_timeout': '360'}))
2265+
]
2266+
self.helper.ovn_nbdb_api.db_set.assert_has_calls(
2267+
expected_calls)
2268+
22122269
@mock.patch('ovn_octavia_provider.common.clients.get_neutron_client')
22132270
def test_member_create_disabled(self, net_cli):
22142271
net_cli.return_value.show_subnet.side_effect = [idlutils.RowNotFound]

0 commit comments

Comments
 (0)