Ensure LB with HM rechecks member status after re-enable

This patch fixes an issue where disabling a LoadBalancer member
sets its status to ERROR instead of OFFLINE.

While in the ERROR state, re-enabling the member does not update
its status, and the member is not reintegrated into the "vips" field
of the LoadBalancer row in the OVN NB database. As a result, the
Health Monitor does not resume its checks on the member.

This patch ensures that re-enabled members are correctly reintegrated
and that health checks are properly resumed.

Closes-bug: #2112110
Change-Id: If2ca76df87c0f5a713604212ed6cb77792d0010c
This commit is contained in:
Fernando Royo
2025-05-29 13:44:23 +02:00
parent 84d6dfca43
commit 4ea86cee1a
3 changed files with 33 additions and 11 deletions

View File

@@ -2795,8 +2795,14 @@ class OvnProviderHelper():
# if HM exists trust on neutron:member_status # if HM exists trust on neutron:member_status
# as the last status valid for the member # as the last status valid for the member
if ovn_lb.health_check: if ovn_lb.health_check:
# search status of member_uuid # Put member ONLINE if was OFFLINE and trust on HM to
member_operating_status = last_status # come back to ERROR in case neccesary, it was already
# on ERROR keeps at that way.
member_operating_status = (
constants.ERROR
if last_status == constants.ERROR
else constants.ONLINE
)
else: else:
member_operating_status = constants.NO_MONITOR member_operating_status = constants.NO_MONITOR
else: else:
@@ -2815,12 +2821,18 @@ class OvnProviderHelper():
) or ( ) or (
last_status == constants.OFFLINE and last_status == constants.OFFLINE and
member_operating_status != constants.OFFLINE member_operating_status != constants.OFFLINE
) or (
member[constants.ADMIN_STATE_UP]
): ):
commands = [] commands = []
commands.extend(self._refresh_lb_vips(ovn_lb, commands.extend(self._refresh_lb_vips(ovn_lb,
ovn_lb.external_ids)) ovn_lb.external_ids))
self._execute_commands(commands) self._execute_commands(commands)
if ovn_lb.health_check:
delete = not member[constants.ADMIN_STATE_UP]
self._update_hm_member(
ovn_lb, pool_key, member.get(constants.ADDRESS),
delete=delete)
except Exception: except Exception:
LOG.exception(ovn_const.EXCEPTION_MSG, "update of member") LOG.exception(ovn_const.EXCEPTION_MSG, "update of member")
error_updating_member = True error_updating_member = True
@@ -3983,9 +3995,8 @@ class OvnProviderHelper():
"ovn_lbs": ovn_lbs, "ovn_lbs": ovn_lbs,
"ip": row.ip, "ip": row.ip,
"port": str(row.port), "port": str(row.port),
"status": row.status "status": row.status,
if not sm_delete_event "delete": sm_delete_event,
else ovn_const.HM_EVENT_MEMBER_PORT_OFFLINE,
} }
self.add_request({'type': ovn_const.REQ_TYPE_HM_UPDATE_EVENT, self.add_request({'type': ovn_const.REQ_TYPE_HM_UPDATE_EVENT,
'info': request_info}) 'info': request_info})
@@ -4130,9 +4141,12 @@ class OvnProviderHelper():
if not member_id: if not member_id:
LOG.warning('Member for event not found, info: %s', info) LOG.warning('Member for event not found, info: %s', info)
else: else:
member_status = constants.ONLINE if info['delete']:
if info['status'] == ovn_const.HM_EVENT_MEMBER_PORT_OFFLINE: member_status = constants.OFFLINE
elif info['status'] == ovn_const.HM_EVENT_MEMBER_PORT_OFFLINE:
member_status = constants.ERROR member_status = constants.ERROR
else:
member_status = constants.ONLINE
self._update_external_ids_member_status(ovn_lb, member_id, self._update_external_ids_member_status(ovn_lb, member_id,
member_status) member_status)

View File

@@ -991,7 +991,9 @@ class TestOvnOctaviaBase(base.TestOVNFunctionalBase,
'provisioning_status': 'ACTIVE', 'provisioning_status': 'ACTIVE',
'operating_status': o_constants.ONLINE}], 'operating_status': o_constants.ONLINE}],
'members': [{"id": member.member_id, 'members': [{"id": member.member_id,
'provisioning_status': 'ACTIVE'}], 'provisioning_status': 'ACTIVE',
'operating_status': o_constants.ONLINE
if admin_state_up else o_constants.OFFLINE}],
'loadbalancers': [{'id': pool.loadbalancer_id, 'loadbalancers': [{'id': pool.loadbalancer_id,
'provisioning_status': 'ACTIVE', 'provisioning_status': 'ACTIVE',
'operating_status': o_constants.ONLINE}], 'operating_status': o_constants.ONLINE}],

View File

@@ -6051,6 +6051,7 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
'logical_port': 'a-logical-port', 'logical_port': 'a-logical-port',
'src_ip': src_ip, 'src_ip': src_ip,
'port': self.member_port, 'port': self.member_port,
'delete': False,
'protocol': self.ovn_hm_lb.protocol, 'protocol': self.ovn_hm_lb.protocol,
'status': ovn_const.HM_EVENT_MEMBER_PORT_OFFLINE}) 'status': ovn_const.HM_EVENT_MEMBER_PORT_OFFLINE})
self.hm_update_event.run('update', row, mock.ANY) self.hm_update_event.run('update', row, mock.ANY)
@@ -6059,6 +6060,7 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
{'ovn_lbs': [self.ovn_hm_lb], {'ovn_lbs': [self.ovn_hm_lb],
'ip': self.member_address, 'ip': self.member_address,
'port': self.member_port, 'port': self.member_port,
'delete': False,
'status': ovn_const.HM_EVENT_MEMBER_PORT_OFFLINE}, 'status': ovn_const.HM_EVENT_MEMBER_PORT_OFFLINE},
'type': 'hm_update_event'} 'type': 'hm_update_event'}
self.mock_add_request.assert_called_once_with(expected) self.mock_add_request.assert_called_once_with(expected)
@@ -6087,7 +6089,8 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
{'ovn_lbs': [self.ovn_hm_lb], {'ovn_lbs': [self.ovn_hm_lb],
'ip': self.member_address, 'ip': self.member_address,
'port': self.member_port, 'port': self.member_port,
'status': ovn_const.HM_EVENT_MEMBER_PORT_OFFLINE}, 'delete': True,
'status': ovn_const.HM_EVENT_MEMBER_PORT_ONLINE},
'type': 'hm_update_event'} 'type': 'hm_update_event'}
self.mock_add_request.assert_called_once_with(expected) self.mock_add_request.assert_called_once_with(expected)
self.helper.ovn_nbdb_api.db_find_rows.assert_called_once_with( self.helper.ovn_nbdb_api.db_find_rows.assert_called_once_with(
@@ -6169,13 +6172,14 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
self._test_hm_update_no_member(False, True) self._test_hm_update_no_member(False, True)
def _test_hm_update_status(self, ovn_lbs, member_id, ip, port, def _test_hm_update_status(self, ovn_lbs, member_id, ip, port,
mb_status): mb_status, delete=False):
info = { info = {
'ovn_lbs': ovn_lbs, 'ovn_lbs': ovn_lbs,
'ip': ip, 'ip': ip,
'logical_port': 'a-logical-port', 'logical_port': 'a-logical-port',
'src_ip': '10.22.33.4', 'src_ip': '10.22.33.4',
'port': port, 'port': port,
'delete': delete,
'protocol': ovn_lbs[0].protocol, 'protocol': ovn_lbs[0].protocol,
'status': [mb_status]} 'status': [mb_status]}
mb_status_ovn = 'error' if mb_status == 'offline' else mb_status mb_status_ovn = 'error' if mb_status == 'offline' else mb_status
@@ -6472,6 +6476,7 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
'logical_port': 'a-logical-port', 'logical_port': 'a-logical-port',
'src_ip': '10.22.33.4', 'src_ip': '10.22.33.4',
'port': '8080', 'port': '8080',
'delete': False,
'protocol': self.ovn_hm_lb.protocol, 'protocol': self.ovn_hm_lb.protocol,
'status': ovn_const.HM_EVENT_MEMBER_PORT_OFFLINE} 'status': ovn_const.HM_EVENT_MEMBER_PORT_OFFLINE}
self._update_external_ids_member_status(self.ovn_hm_lb, member['id'], self._update_external_ids_member_status(self.ovn_hm_lb, member['id'],
@@ -6596,6 +6601,7 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
'logical_port': 'a-logical-port', 'logical_port': 'a-logical-port',
'src_ip': '10.22.33.4', 'src_ip': '10.22.33.4',
'port': '8081', 'port': '8081',
'delete': False,
'protocol': ovn_hm_lb2.protocol, 'protocol': ovn_hm_lb2.protocol,
'status': ovn_const.HM_EVENT_MEMBER_PORT_OFFLINE} 'status': ovn_const.HM_EVENT_MEMBER_PORT_OFFLINE}