Merge "Patching Octavia LB pool removal issue and adding updated unit tests"

This commit is contained in:
Zuul
2025-07-07 11:03:35 +00:00
committed by Gerrit Code Review
2 changed files with 146 additions and 14 deletions

View File

@@ -3520,9 +3520,14 @@ class OvnProviderHelper():
self._execute_commands(commands)
return True
def _update_ip_port_mappings(self, ovn_lb, backend_ip, port_name, src_ip,
delete=False):
def _update_ip_port_mappings(
self,
ovn_lb,
backend_ip,
port_name,
src_ip,
pool_key,
delete=False):
# ip_port_mappings:${MEMBER_IP}=${LSP_NAME_MEMBER}:${HEALTH_SRC}
# where:
# MEMBER_IP: IP of member_lsp
@@ -3530,8 +3535,37 @@ class OvnProviderHelper():
# HEALTH_SRC: source IP of hm_port
if delete:
self.ovn_nbdb_api.lb_del_ip_port_mapping(ovn_lb.uuid,
backend_ip).execute()
# Before removing a member from ip_port_mappings,
# make sure no other
# pool uses the same member.
other_members = []
for k, v in ovn_lb.external_ids.items():
if ovn_const.LB_EXT_IDS_POOL_PREFIX in k and k != pool_key:
other_members.extend(self._extract_member_info(
ovn_lb.external_ids[k]))
member_statuses = ovn_lb.external_ids.get(
ovn_const.OVN_MEMBER_STATUS_KEY)
try:
member_statuses = jsonutils.loads(member_statuses)
except TypeError:
LOG.debug("No member status in external_ids: %s",
str(member_statuses))
member_statuses = {}
execute_delete = True
for member_id in [item[3] for item in other_members
if item[0] == backend_ip]:
if member_statuses.get(member_id, '') != constants.NO_MONITOR:
execute_delete = False
LOG.debug(
f"Backend {backend_ip} still in use by member"
f" {member_id}, "
f"so it won't be removed"
)
break
if execute_delete:
LOG.debug(f"Removing ip_port_mapping for {backend_ip}")
self.ovn_nbdb_api.lb_del_ip_port_mapping(
ovn_lb.uuid, backend_ip).execute()
else:
self.ovn_nbdb_api.lb_add_ip_port_mapping(ovn_lb.uuid,
backend_ip,
@@ -3620,8 +3654,11 @@ class OvnProviderHelper():
'member': mb_ip,
'pool': pool_key})
return None
self._update_ip_port_mappings(ovn_lb, backend_ip,
member_lsp.name, hm_source_ip,
self._update_ip_port_mappings(ovn_lb,
backend_ip,
member_lsp.name,
hm_source_ip,
pool_key,
delete)
return constants.ONLINE

View File

@@ -336,7 +336,11 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
def test__update_ip_port_mappings_del_backend_member(self):
src_ip = '10.22.33.4'
self.helper._update_ip_port_mappings(
self.ovn_lb, self.member_address, 'a-logical-port', src_ip,
self.ovn_lb,
self.member_address,
'a-logical-port',
src_ip,
'test_pool_key',
delete=True)
self.helper.ovn_nbdb_api.lb_del_ip_port_mapping.\
assert_called_once_with(self.ovn_lb.uuid, self.member_address)
@@ -344,7 +348,11 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
def test__update_ip_port_mappings_add_backend_member(self):
src_ip = '10.22.33.4'
self.helper._update_ip_port_mappings(
self.ovn_lb, self.member_address, 'a-logical-port', src_ip)
self.ovn_lb,
self.member_address,
'a-logical-port',
src_ip,
'test_pool_key')
self.helper.ovn_nbdb_api.lb_add_ip_port_mapping.\
assert_called_once_with(self.ovn_lb.uuid, self.member_address,
'a-logical-port', src_ip)
@@ -353,7 +361,11 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
member_address = 'fda2:918e:5869:0:f816:3eff:feab:cdef'
src_ip = 'fda2:918e:5869:0:f816:3eff:fecd:398a'
self.helper._update_ip_port_mappings(
self.ovn_lb, member_address, 'a-logical-port', src_ip,
self.ovn_lb,
member_address,
'a-logical-port',
src_ip,
'test_pool_key',
delete=True)
self.helper.ovn_nbdb_api.lb_del_ip_port_mapping.\
assert_called_once_with(self.ovn_lb.uuid, member_address)
@@ -362,10 +374,17 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
member_address = 'fda2:918e:5869:0:f816:3eff:feab:cdef'
src_ip = 'fda2:918e:5869:0:f816:3eff:fecd:398a'
self.helper._update_ip_port_mappings(
self.ovn_lb, member_address, 'a-logical-port', src_ip)
self.ovn_lb,
member_address,
'a-logical-port',
src_ip,
'test_pool_key')
self.helper.ovn_nbdb_api.lb_add_ip_port_mapping.\
assert_called_once_with(
self.ovn_lb.uuid, member_address, 'a-logical-port', src_ip)
self.ovn_lb.uuid,
member_address,
'a-logical-port',
src_ip)
def test__update_external_ids_member_status(self):
self.helper._update_external_ids_member_status(
@@ -374,7 +393,10 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
ovn_const.OVN_MEMBER_STATUS_KEY: '{"%s": "%s"}'
% (self.member_id, constants.NO_MONITOR)}
self.helper.ovn_nbdb_api.db_set.assert_called_once_with(
'Load_Balancer', self.ovn_lb.uuid, ('external_ids', member_status))
'Load_Balancer',
self.ovn_lb.uuid,
('external_ids',
member_status))
def test__update_external_ids_member_status_delete(self):
self.helper._update_external_ids_member_status(
@@ -390,7 +412,10 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
ovn_const.OVN_MEMBER_STATUS_KEY: '{"%s": "%s"}'
% (self.member_id, constants.NO_MONITOR)}
self.helper.ovn_nbdb_api.db_set.assert_called_once_with(
'Load_Balancer', self.ovn_lb.uuid, ('external_ids', member_status))
'Load_Balancer',
self.ovn_lb.uuid,
('external_ids',
member_status))
def test__find_member_status(self):
status = self.helper._find_member_status(self.ovn_lb, self.member_id)
@@ -6746,3 +6771,73 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
self.ovn_lb.uuid,
('vips', {'vip1:port1': 'ip1:port1,ip2:port1'})
)
def test_update_ip_port_mappings_add(self):
# Setup mock OVN load balancer
ovn_lb = mock.Mock()
ovn_lb.uuid = 'test-lb-uuid'
ovn_lb.external_ids = {}
# Call the method with delete=False
self.helper._update_ip_port_mappings(
ovn_lb, '10.0.0.1', 'port1', '192.168.0.1', 'pool1', delete=False
)
# Assert that lb_add_ip_port_mapping was called
self.helper.ovn_nbdb_api.lb_add_ip_port_mapping\
.assert_called_once_with(
'test-lb-uuid',
'10.0.0.1',
'port1',
'192.168.0.1',
)
def test_update_ip_port_mappings_delete_minimal(self):
ovn_lb = mock.Mock()
ovn_lb.uuid = 'test-lb-uuid'
ovn_lb.external_ids = {}
# Patch _extract_member_info to return no other members
self.helper._extract_member_info = mock.Mock(return_value=[])
# Also patch ovn_nbdb_api call
self.helper.ovn_nbdb_api.lb_del_ip_port_mapping = mock.Mock()
self.helper.ovn_nbdb_api.lb_add_ip_port_mapping = mock.Mock()
self.helper._update_ip_port_mappings(
ovn_lb,
backend_ip='10.0.0.1',
port_name='dummy-port',
src_ip='192.168.0.1',
pool_key='pool-test',
delete=True
)
self.helper.ovn_nbdb_api.\
lb_del_ip_port_mapping.\
assert_called_once_with(
'test-lb-uuid',
'10.0.0.1'
)
def test_update_ip_port_mappings_delete_with_other_members_present(self):
ovn_lb = mock.Mock()
ovn_lb.uuid = 'test-lb-uuid'
ovn_lb.external_ids = {
"pool_A": "member_memberA_10.0.0.1:80_subnetA",
"pool_B": "member_memberB_10.0.0.1:80_subnetA",
"neutron:member_statuses": '{"memberB": "ONLINE"}'
}
self.helper.ovn_nbdb_api.lb_del_ip_port_mapping = mock.Mock()
self.helper.ovn_nbdb_api.lb_add_ip_port_mapping = mock.Mock()
# Call the method under test
self.helper._update_ip_port_mappings(
ovn_lb,
backend_ip='10.0.0.1',
port_name='dummy-port',
src_ip='192.168.0.1',
pool_key='pool_A',
delete=True
)
# Should not call delete because memberB is ONLINE and shares the IP
self.helper.ovn_nbdb_api.lb_del_ip_port_mapping.assert_not_called()
self.helper.ovn_nbdb_api.lb_add_ip_port_mapping.assert_not_called()