Skip to content

Commit 37e0f22

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Add Listener sync logic"
2 parents 3686422 + 318160f commit 37e0f22

File tree

4 files changed

+254
-8
lines changed

4 files changed

+254
-8
lines changed

ovn_octavia_provider/driver.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,20 @@ def _get_loadbalancer_request_info(self, loadbalancer):
105105
loadbalancer.additional_vips
106106
return request_info
107107

108+
def _get_listener_request_info(self, listener):
109+
self._check_for_supported_protocols(listener.protocol)
110+
self._check_for_allowed_cidrs(listener.allowed_cidrs)
111+
admin_state_up = listener.admin_state_up
112+
if isinstance(admin_state_up, o_datamodels.UnsetType):
113+
admin_state_up = True
114+
request_info = {'id': listener.listener_id,
115+
'protocol': listener.protocol,
116+
'loadbalancer_id': listener.loadbalancer_id,
117+
'protocol_port': listener.protocol_port,
118+
'default_pool_id': listener.default_pool_id,
119+
'admin_state_up': admin_state_up}
120+
return request_info
121+
108122
def loadbalancer_create(self, loadbalancer):
109123
request = {'type': ovn_const.REQ_TYPE_LB_CREATE,
110124
'info': self._get_loadbalancer_request_info(
@@ -598,9 +612,16 @@ def _ensure_loadbalancer(self, loadbalancer):
598612
except idlutils.RowNotFound:
599613
LOG.debug(f"OVN loadbalancer {loadbalancer.loadbalancer_id} "
600614
"not found. Start create process.")
601-
# TODO(froyo): By now just syncing LB only
615+
# TODO(froyo): By now just syncing LB and listener only
602616
status = self._ovn_helper.lb_create(
603617
self._get_loadbalancer_request_info(loadbalancer))
618+
619+
if not isinstance(loadbalancer.listeners, o_datamodels.UnsetType):
620+
status[constants.LISTENERS] = []
621+
for listener in loadbalancer.listeners:
622+
status_listener = self._ovn_helper.listener_create(
623+
self._get_listener_request_info(listener))
624+
status[constants.LISTENERS].append(status_listener)
604625
self._ovn_helper._update_status_to_octavia(status)
605626
else:
606627
# Load Balancer found, check LB and listener/pool/member/hms
@@ -611,6 +632,12 @@ def _ensure_loadbalancer(self, loadbalancer):
611632
"found checking other entities related")
612633
self._ovn_helper.lb_sync(
613634
self._get_loadbalancer_request_info(loadbalancer), ovn_lb)
635+
# Listener
636+
if not isinstance(loadbalancer.listeners,
637+
o_datamodels.UnsetType):
638+
for listener in loadbalancer.listeners:
639+
self._ovn_helper.listener_sync(
640+
self._get_listener_request_info(listener), ovn_lb)
614641
status = self._ovn_helper._get_current_operating_statuses(
615642
ovn_lb)
616643
self._ovn_helper._update_status_to_octavia(status)
@@ -625,4 +652,11 @@ def do_sync(self, **lb_filters):
625652
provider_lb = (
626653
self._ovn_helper._octavia_driver_lib.get_loadbalancer(lb.id)
627654
)
655+
656+
listeners = provider_lb.listeners or []
657+
provider_lb.listeners = [
658+
o_datamodels.Listener.from_dict(listener)
659+
for listener in listeners
660+
] if listeners else o_datamodels.Unset
661+
628662
self._ensure_loadbalancer(provider_lb)

ovn_octavia_provider/helper.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,50 @@ def _sync_lb_to_lr_association(self, ovn_lb, ovn_lr):
618618
"router %s: %s", ovn_lb.uuid, ovn_lr.uuid,
619619
str(e))
620620

621+
def _build_listener_info(self, listener, external_ids):
622+
"""Build listener key and listener info."""
623+
listener_key = self._get_listener_key(
624+
listener.get(constants.ID),
625+
is_enabled=listener.get(constants.ADMIN_STATE_UP)
626+
)
627+
pool_key = ''
628+
if listener.get(constants.DEFAULT_POOL_ID):
629+
pool_key = self._get_pool_key(
630+
listener.get(constants.DEFAULT_POOL_ID))
631+
external_ids[listener_key] = self._make_listener_key_value(
632+
listener[constants.PROTOCOL_PORT], pool_key
633+
)
634+
listener_info = {listener_key: external_ids[listener_key]}
635+
return listener_key, listener_info
636+
637+
def _update_listener_key_if_needed(self, listener_key, listener_info,
638+
ovn_lb, commands):
639+
"""Update listener key on OVN LoadBalancer if needed."""
640+
prev_listener_key_content = ovn_lb.external_ids.get(listener_key, '')
641+
if (listener_key not in ovn_lb.external_ids or
642+
listener_info.get(listener_key) != prev_listener_key_content):
643+
commands.append(
644+
self.ovn_nbdb_api.db_set(
645+
'Load_Balancer',
646+
ovn_lb.uuid,
647+
('external_ids', listener_info)
648+
)
649+
)
650+
651+
def _update_protocol_if_needed(self, listener, ovn_lb, commands):
652+
"""Update protocol on OVN LoadBalancer if needed."""
653+
current_protocol = ''
654+
if ovn_lb.protocol:
655+
current_protocol = ovn_lb.protocol[0].lower()
656+
listener_protocol = str(listener.get(constants.PROTOCOL)).lower()
657+
if current_protocol != listener_protocol:
658+
commands.append(
659+
self.ovn_nbdb_api.db_set(
660+
'Load_Balancer', ovn_lb.uuid,
661+
('protocol', listener_protocol)
662+
)
663+
)
664+
621665
def _lb_status(self, loadbalancer, provisioning_status, operating_status):
622666
"""Return status for the LoadBalancer."""
623667
return {
@@ -1867,6 +1911,39 @@ def listener_create(self, listener):
18671911
constants.PROVISIONING_STATUS: constants.ACTIVE}]}
18681912
return status
18691913

1914+
def listener_sync(self, listener, ovn_lb):
1915+
"""Sync Listener object with an OVN LoadBalancer
1916+
1917+
The method performs the following steps:
1918+
1. Update listener key on OVN Loadbalancer external_ids if needed
1919+
2. Update OVN LoadBalancer protocol from Listener info if needed
1920+
3. Refresh OVN LoadBalancer vips
1921+
1922+
:param listener: The source listener object from Octavia DB
1923+
:param ovn_lb: The OVN LoadBalancer object that needs to be sync
1924+
"""
1925+
commands = []
1926+
external_ids = copy.deepcopy(ovn_lb.external_ids)
1927+
1928+
listener_key, listener_info = self._build_listener_info(
1929+
listener, external_ids)
1930+
self._update_listener_key_if_needed(
1931+
listener_key, listener_info, ovn_lb, commands)
1932+
self._update_protocol_if_needed(listener, ovn_lb, commands)
1933+
1934+
try:
1935+
commands.extend(self._refresh_lb_vips(
1936+
ovn_lb, external_ids, is_sync=True))
1937+
except Exception as e:
1938+
LOG.exception(f"Failed to refresh LB VIPs: {e}")
1939+
return
1940+
1941+
try:
1942+
self._execute_commands(commands)
1943+
except Exception as e:
1944+
LOG.exception(f"Failed to execute commands for listener sync: {e}")
1945+
return
1946+
18701947
def listener_delete(self, listener):
18711948
status = {
18721949
constants.LISTENERS: [

ovn_octavia_provider/tests/unit/test_driver.py

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,32 +1237,82 @@ 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, 'listener_create')
12401241
@mock.patch.object(ovn_helper.OvnProviderHelper, 'lb_create')
12411242
def test_ensure_loadbalancer_lb_not_found(
1242-
self, mock_lb_create, mock_update_status):
1243+
self, mock_lb_create, mock_listener_create, mock_update_status):
12431244
self.mock_find_ovn_lbs_with_retry.side_effect = [
12441245
idlutils.RowNotFound]
1245-
self.driver._ensure_loadbalancer(self.ref_lb0)
1246+
self.driver._ensure_loadbalancer(self.ref_lb_fully_populated)
12461247
mock_lb_create.assert_called_once_with(
1247-
self.driver._get_loadbalancer_request_info(self.ref_lb0),
1248+
self.driver._get_loadbalancer_request_info(
1249+
self.ref_lb_fully_populated),
12481250
)
1251+
mock_listener_create.assert_called_once_with(
1252+
self.driver._get_listener_request_info(
1253+
self.ref_lb_fully_populated.listeners[0]),
1254+
)
1255+
1256+
@mock.patch.object(ovn_helper.OvnProviderHelper,
1257+
'_update_status_to_octavia')
1258+
@mock.patch.object(ovn_helper.OvnProviderHelper, 'listener_create')
1259+
@mock.patch.object(ovn_helper.OvnProviderHelper, 'lb_create')
1260+
def test_ensure_loadbalancer_lb_no_listener_not_found(
1261+
self, mock_lb_create, mock_listener_create, mock_update_status):
1262+
self.mock_find_ovn_lbs_with_retry.side_effect = [
1263+
idlutils.RowNotFound]
1264+
self.ref_lb_fully_populated.listeners = data_models.Unset
1265+
self.driver._ensure_loadbalancer(self.ref_lb_fully_populated)
1266+
mock_lb_create.assert_called_once_with(
1267+
self.driver._get_loadbalancer_request_info(
1268+
self.ref_lb_fully_populated),
1269+
)
1270+
mock_listener_create.assert_not_called()
12491271

12501272
@mock.patch.object(ovn_helper.OvnProviderHelper,
12511273
'_update_status_to_octavia')
12521274
@mock.patch.object(ovn_helper.OvnProviderHelper,
12531275
'_get_current_operating_statuses')
1276+
@mock.patch.object(ovn_helper.OvnProviderHelper, 'listener_sync')
12541277
@mock.patch.object(ovn_helper.OvnProviderHelper, 'lb_sync')
12551278
def test_ensure_loadbalancer_lb_found(
1256-
self, mock_lb_sync, mock_get_status, mock_update_status):
1279+
self, mock_lb_sync, mock_listener_sync, mock_get_status,
1280+
mock_update_status):
12571281
self.mock_find_ovn_lbs_with_retry.return_value = [
12581282
self.ovn_lb]
1259-
self.driver._ensure_loadbalancer(self.ref_lb0)
1283+
self.driver._ensure_loadbalancer(self.ref_lb_fully_populated)
12601284
mock_lb_sync.assert_called_with(
1261-
self.driver._get_loadbalancer_request_info(self.ref_lb0),
1285+
self.driver._get_loadbalancer_request_info(
1286+
self.ref_lb_fully_populated),
1287+
self.ovn_lb
1288+
)
1289+
mock_listener_sync.assert_called_with(
1290+
self.driver._get_listener_request_info(
1291+
self.ref_lb_fully_populated.listeners[0]),
12621292
self.ovn_lb
12631293
)
12641294
mock_get_status.assert_called_with(self.ovn_lb)
12651295

1296+
@mock.patch.object(ovn_helper.OvnProviderHelper,
1297+
'_update_status_to_octavia')
1298+
@mock.patch.object(ovn_helper.OvnProviderHelper,
1299+
'_get_current_operating_statuses')
1300+
@mock.patch.object(ovn_helper.OvnProviderHelper, 'listener_sync')
1301+
@mock.patch.object(ovn_helper.OvnProviderHelper, 'lb_sync')
1302+
def test_ensure_loadbalancer_lb_no_listener_found(
1303+
self, mock_lb_sync, mock_listener_sync, mock_get_status,
1304+
mock_update_status):
1305+
self.mock_find_ovn_lbs_with_retry.return_value = [
1306+
self.ovn_lb]
1307+
self.ref_lb_fully_populated.listeners = data_models.Unset
1308+
self.driver._ensure_loadbalancer(self.ref_lb_fully_populated)
1309+
mock_lb_sync.assert_called_with(
1310+
self.driver._get_loadbalancer_request_info(
1311+
self.ref_lb_fully_populated),
1312+
self.ovn_lb
1313+
)
1314+
mock_listener_sync.assert_not_called()
1315+
12661316
@mock.patch.object(ovn_helper.OvnProviderHelper, 'get_octavia_lbs')
12671317
@mock.patch.object(clients, 'get_octavia_client')
12681318
def test_do_sync_no_loadbalancers(self, mock_get_octavia_client,
@@ -1276,16 +1326,40 @@ def test_do_sync_no_loadbalancers(self, mock_get_octavia_client,
12761326
self.driver.do_sync(**lb_filters)
12771327
mock_ensure_lb.assert_not_called()
12781328

1329+
@mock.patch.object(data_models.Listener, 'from_dict')
12791330
@mock.patch.object(o_driver_lib.DriverLibrary, 'get_loadbalancer')
12801331
@mock.patch.object(ovn_helper.OvnProviderHelper, 'get_octavia_lbs')
12811332
@mock.patch.object(clients, 'get_octavia_client')
12821333
def test_do_sync_with_loadbalancers(self,
12831334
mock_get_octavia_client,
12841335
mock_get_octavia_lbs,
1285-
mock_get_loadbalancer):
1336+
mock_get_loadbalancer,
1337+
mock_listener_from_dict):
12861338
lb = mock.MagicMock(id=self.ref_lb_fully_sync_populated.name)
12871339
mock_get_octavia_lbs.return_value = [lb]
12881340
mock_get_loadbalancer.return_value = self.ref_lb_fully_sync_populated
1341+
mock_listener_from_dict.return_value = self.ref_listener
1342+
lb_filters = {}
1343+
with mock.patch.object(self.driver, '_ensure_loadbalancer') \
1344+
as mock_ensure_lb:
1345+
self.driver.do_sync(**lb_filters)
1346+
mock_ensure_lb.assert_any_call(
1347+
self.ref_lb_fully_sync_populated)
1348+
1349+
@mock.patch.object(data_models.Listener, 'from_dict')
1350+
@mock.patch.object(o_driver_lib.DriverLibrary, 'get_loadbalancer')
1351+
@mock.patch.object(ovn_helper.OvnProviderHelper, 'get_octavia_lbs')
1352+
@mock.patch.object(clients, 'get_octavia_client')
1353+
def test_do_sync_with_loadbalancers_no_listener(
1354+
self,
1355+
mock_get_octavia_client,
1356+
mock_get_octavia_lbs,
1357+
mock_get_loadbalancer,
1358+
mock_listener_from_dict):
1359+
lb = mock.MagicMock(id=self.ref_lb_fully_sync_populated.name)
1360+
mock_get_octavia_lbs.return_value = [lb, lb]
1361+
mock_get_loadbalancer.return_value = self.ref_lb_fully_sync_populated
1362+
self.ref_lb_fully_sync_populated.listeners = data_models.Unset
12891363
lb_filters = {}
12901364
with mock.patch.object(self.driver, '_ensure_loadbalancer') \
12911365
as mock_ensure_lb:

ovn_octavia_provider/tests/unit/test_helper.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1886,6 +1886,67 @@ def test_listener_delete_ovn_lb_empty_ovn_lb_not_found(self, lb_empty):
18861886
mock.call('Load_Balancer', self.ovn_lb.uuid,
18871887
('vips', {}))])
18881888

1889+
@mock.patch.object(ovn_helper.OvnProviderHelper, '_refresh_lb_vips')
1890+
def test_listener_sync_listener_same_in_externals_ids(self, refresh_vips):
1891+
self.listener['admin_state_up'] = True
1892+
listener_key = 'listener_%s' % self.listener_id
1893+
self.ovn_lb.external_ids[listener_key] = f"80:pool_{self.pool_id}"
1894+
self.helper.listener_sync(self.listener, self.ovn_lb)
1895+
refresh_vips.assert_called_once_with(
1896+
self.ovn_lb, self.ovn_lb.external_ids, is_sync=True)
1897+
self.helper.ovn_nbdb_api.db_set.assert_not_called()
1898+
1899+
@mock.patch.object(ovn_helper.OvnProviderHelper, '_refresh_lb_vips')
1900+
def test_listener_sync_listener_diff_in_externals_ids(self, refresh_vips):
1901+
self.listener['admin_state_up'] = True
1902+
listener_key = 'listener_%s' % self.listener_id
1903+
external_ids = copy.deepcopy(self.ovn_lb.external_ids)
1904+
self.ovn_lb.external_ids[listener_key] = ''
1905+
self.helper.listener_sync(self.listener, self.ovn_lb)
1906+
refresh_vips.assert_called_once_with(
1907+
self.ovn_lb, external_ids, is_sync=True)
1908+
expected_calls = [
1909+
mock.call('Load_Balancer', self.ovn_lb.uuid, ('external_ids', {
1910+
f"listener_{self.listener_id}": f"80:pool_{self.pool_id}"}))
1911+
]
1912+
self.helper.ovn_nbdb_api.db_set.assert_has_calls(
1913+
expected_calls)
1914+
1915+
@mock.patch.object(ovn_helper.OvnProviderHelper, '_execute_commands')
1916+
def test_listener_sync_exception(self, execute_commands):
1917+
execute_commands.side_effect = [RuntimeError('a fail')]
1918+
self.ovn_lb.external_ids.pop('listener_%s' % self.listener_id)
1919+
self.listener['admin_state_up'] = True
1920+
with mock.patch.object(ovn_helper, 'LOG') as m_l:
1921+
self.assertIsNone(self.helper.listener_sync(
1922+
self.listener, self.ovn_lb))
1923+
m_l.exception.assert_called_once_with(
1924+
'Failed to execute commands for listener sync: a fail')
1925+
1926+
@mock.patch.object(ovn_helper.OvnProviderHelper, '_refresh_lb_vips')
1927+
def test_listener_sync_refresh_vips_exception(self, refresh_lb_vips):
1928+
refresh_lb_vips.side_effect = [RuntimeError('a fail')]
1929+
self.ovn_lb.external_ids.pop('listener_%s' % self.listener_id)
1930+
self.listener['admin_state_up'] = True
1931+
with mock.patch.object(ovn_helper, 'LOG') as m_l:
1932+
self.assertIsNone(self.helper.listener_sync(
1933+
self.listener, self.ovn_lb))
1934+
m_l.exception.assert_called_once_with(
1935+
'Failed to refresh LB VIPs: a fail')
1936+
1937+
@mock.patch.object(ovn_helper.OvnProviderHelper, '_refresh_lb_vips')
1938+
def test_listener_sync_listener_not_in_externals_ids(
1939+
self, refresh_vips):
1940+
self.ovn_lb.external_ids.pop('listener_%s' % self.listener_id)
1941+
self.listener['admin_state_up'] = True
1942+
self.helper.listener_sync(self.listener, self.ovn_lb)
1943+
expected_calls = [
1944+
mock.call('Load_Balancer', self.ovn_lb.uuid, ('external_ids', {
1945+
f"listener_{self.listener_id}": f"80:pool_{self.pool_id}"}))
1946+
]
1947+
self.helper.ovn_nbdb_api.db_set.assert_has_calls(
1948+
expected_calls)
1949+
18891950
def test_pool_create(self):
18901951
status = self.helper.pool_create(self.pool)
18911952
self.assertEqual(status['loadbalancers'][0]['provisioning_status'],

0 commit comments

Comments
 (0)