Cover the use case of a member non existing

When a HM is attached to a pool and a backend member in that pool
is a fake member (e.g. due to a typo on creation) the member remains
in ONLINE status. Basically this is due to the fact that there
isn't any LSP attached to that member and no Service_Monitor entries
will take care of it.

This patch checks inmediatelly after creation the member and update
the whole LB status to reflect this fake member that could help to
the user to identify quickly those fake members.

Closes-Bug: 2034522
Change-Id: I72b2d9c5f454f9b156414bf91ca7deb7f0e9d8b0
This commit is contained in:
Fernando Royo
2023-09-06 09:50:19 +02:00
parent e02c1b9b3e
commit fe6612f714
3 changed files with 165 additions and 218 deletions

View File

@@ -1969,41 +1969,14 @@ class OvnProviderHelper():
def member_create(self, member): def member_create(self, member):
new_member = None new_member = None
pool_listeners = []
try: try:
pool_key, ovn_lb = self._find_ovn_lb_by_pool_id( pool_key, ovn_lb = self._find_ovn_lb_by_pool_id(
member[constants.POOL_ID]) member[constants.POOL_ID])
pool_listeners = self._get_pool_listeners(ovn_lb, pool_key)
new_member = self._add_member(member, ovn_lb, pool_key) new_member = self._add_member(member, ovn_lb, pool_key)
pool = {constants.ID: member[constants.POOL_ID], operating_status = constants.NO_MONITOR
constants.PROVISIONING_STATUS: constants.ACTIVE,
constants.OPERATING_STATUS: constants.ONLINE}
status = {
constants.POOLS: [pool],
constants.LOADBALANCERS: [
{constants.ID: ovn_lb.name,
constants.PROVISIONING_STATUS: constants.ACTIVE}]}
except Exception: except Exception:
LOG.exception(ovn_const.EXCEPTION_MSG, "creation of member") LOG.exception(ovn_const.EXCEPTION_MSG, "creation of member")
status = { operating_status = constants.ERROR
constants.POOLS: [
{constants.ID: member[constants.POOL_ID],
constants.PROVISIONING_STATUS: constants.ERROR}],
constants.MEMBERS: [
{constants.ID: member[constants.ID],
constants.PROVISIONING_STATUS: constants.ACTIVE}],
constants.LOADBALANCERS: [
{constants.ID: ovn_lb.name,
constants.PROVISIONING_STATUS: constants.ACTIVE}]}
listener_status = []
for listener in pool_listeners:
listener_status.append(
{constants.ID: listener,
constants.PROVISIONING_STATUS: constants.ACTIVE})
status[constants.LISTENERS] = listener_status
operating_status = constants.NO_MONITOR
if not member[constants.ADMIN_STATE_UP]: if not member[constants.ADMIN_STATE_UP]:
operating_status = constants.OFFLINE operating_status = constants.OFFLINE
elif (new_member and operating_status == constants.NO_MONITOR and elif (new_member and operating_status == constants.NO_MONITOR and
@@ -2011,18 +1984,18 @@ class OvnProviderHelper():
operating_status = constants.ONLINE operating_status = constants.ONLINE
mb_ip, mb_port, mb_subnet, mb_id = self._extract_member_info( mb_ip, mb_port, mb_subnet, mb_id = self._extract_member_info(
new_member)[0] new_member)[0]
if not self._update_hm_member(ovn_lb, pool_key, mb_ip): mb_status = self._update_hm_member(ovn_lb, pool_key, mb_ip)
operating_status = constants.ERROR operating_status = (
member_status = {constants.ID: member[constants.ID], constants.ERROR
constants.PROVISIONING_STATUS: constants.ACTIVE, if mb_status != constants.ONLINE else mb_status
constants.OPERATING_STATUS: operating_status} )
status[constants.MEMBERS] = [member_status]
self._update_external_ids_member_status( self._update_external_ids_member_status(
ovn_lb, ovn_lb,
member[constants.ID], member[constants.ID],
operating_status) operating_status)
status = self._get_current_operating_statuses(ovn_lb)
return status return status
def _remove_member(self, member, ovn_lb, pool_key): def _remove_member(self, member, ovn_lb, pool_key):
@@ -2063,71 +2036,39 @@ class OvnProviderHelper():
operator_fault_string=msg) operator_fault_string=msg)
def member_delete(self, member): def member_delete(self, member):
pool_listeners = [] error_deleting_member = False
try: try:
pool_key, ovn_lb = self._find_ovn_lb_by_pool_id( pool_key, ovn_lb = self._find_ovn_lb_by_pool_id(
member[constants.POOL_ID]) member[constants.POOL_ID])
pool_listeners = self._get_pool_listeners(ovn_lb, pool_key)
pool_status = self._remove_member(member, ovn_lb, pool_key) pool_status = self._remove_member(member, ovn_lb, pool_key)
pool = {constants.ID: member[constants.POOL_ID],
constants.PROVISIONING_STATUS: constants.ACTIVE,
constants.OPERATING_STATUS: pool_status}
if ovn_lb.health_check and pool_status == constants.OFFLINE: if ovn_lb.health_check and pool_status == constants.OFFLINE:
# NOTE(froyo): if the pool status is OFFLINE there are no more # NOTE(froyo): if the pool status is OFFLINE there are no more
# members. So we should ensure the hm-port is deleted if no # members. So we should ensure the hm-port is deleted if no
# more LB are using it. We need to do this call after the # more LB are using it. We need to do this call after the
# cleaning of the ip_port_mappings for the ovn LB. # cleaning of the ip_port_mappings for the ovn LB.
self._clean_up_hm_port(member[constants.SUBNET_ID]) self._clean_up_hm_port(member[constants.SUBNET_ID])
status = {
constants.POOLS: [pool],
constants.MEMBERS: [
{constants.ID: member[constants.ID],
constants.PROVISIONING_STATUS: constants.DELETED}],
constants.LOADBALANCERS: [
{constants.ID: ovn_lb.name,
constants.PROVISIONING_STATUS: constants.ACTIVE}]}
except Exception: except Exception:
LOG.exception(ovn_const.EXCEPTION_MSG, "deletion of member") LOG.exception(ovn_const.EXCEPTION_MSG, "deletion of member")
status = { error_deleting_member = True
constants.POOLS: [
{constants.ID: member[constants.POOL_ID],
constants.PROVISIONING_STATUS: constants.ACTIVE}],
constants.MEMBERS: [
{constants.ID: member[constants.ID],
constants.PROVISIONING_STATUS: constants.ERROR}],
constants.LOADBALANCERS: [
{constants.ID: ovn_lb.name,
constants.PROVISIONING_STATUS: constants.ACTIVE}]}
self._update_external_ids_member_status( self._update_external_ids_member_status(
ovn_lb, member[constants.ID], None, delete=True) ovn_lb, member[constants.ID], None, delete=True)
status = self._get_current_operating_statuses(ovn_lb)
listener_status = [] status[constants.MEMBERS] = [
for listener in pool_listeners: {constants.ID: member[constants.ID],
listener_status.append( constants.PROVISIONING_STATUS: constants.DELETED}]
{constants.ID: listener, if error_deleting_member:
constants.PROVISIONING_STATUS: constants.ACTIVE}) status[constants.MEMBERS][0][constants.PROVISIONING_STATUS] = (
status[constants.LISTENERS] = listener_status constants.ERROR)
return status return status
def member_update(self, member): def member_update(self, member):
pool_listeners = []
try: try:
error_updating_member = False
pool_key, ovn_lb = self._find_ovn_lb_by_pool_id( pool_key, ovn_lb = self._find_ovn_lb_by_pool_id(
member[constants.POOL_ID]) member[constants.POOL_ID])
member_status = {constants.ID: member[constants.ID], member_operating_status = constants.NO_MONITOR
constants.PROVISIONING_STATUS: constants.ACTIVE}
status = {
constants.POOLS: [
{constants.ID: member[constants.POOL_ID],
constants.PROVISIONING_STATUS: constants.ACTIVE}],
constants.MEMBERS: [member_status],
constants.LOADBALANCERS: [
{constants.ID: ovn_lb.name,
constants.PROVISIONING_STATUS: constants.ACTIVE}]}
pool_listeners = self._get_pool_listeners(ovn_lb, pool_key)
last_status = self._find_member_status( last_status = self._find_member_status(
ovn_lb, member[constants.ID]) ovn_lb, member[constants.ID])
if constants.ADMIN_STATE_UP in member: if constants.ADMIN_STATE_UP in member:
@@ -2136,30 +2077,25 @@ class OvnProviderHelper():
# 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 # search status of member_uuid
member_status[constants.OPERATING_STATUS] = last_status member_operating_status = last_status
else: else:
member_status[constants.OPERATING_STATUS] = ( member_operating_status = constants.NO_MONITOR
constants.NO_MONITOR)
else: else:
member_status[constants.OPERATING_STATUS] = ( member_operating_status = constants.OFFLINE
constants.OFFLINE)
if constants.OPERATING_STATUS in member_status: self._update_external_ids_member_status(
self._update_external_ids_member_status( ovn_lb,
ovn_lb, member[constants.ID],
member[constants.ID], member_operating_status)
member_status[constants.OPERATING_STATUS])
# NOTE(froyo): If we are toggling from/to OFFLINE due to an # NOTE(froyo): If we are toggling from/to OFFLINE due to an
# admin_state_up change, in that case we should update vips # admin_state_up change, in that case we should update vips
if ( if (
last_status != constants.OFFLINE and last_status != constants.OFFLINE and
member_status[constants.OPERATING_STATUS] == member_operating_status == constants.OFFLINE
constants.OFFLINE
) or ( ) or (
last_status == constants.OFFLINE and last_status == constants.OFFLINE and
member_status[constants.OPERATING_STATUS] != member_operating_status != constants.OFFLINE
constants.OFFLINE
): ):
commands = [] commands = []
commands.extend(self._refresh_lb_vips(ovn_lb, commands.extend(self._refresh_lb_vips(ovn_lb,
@@ -2168,23 +2104,16 @@ class OvnProviderHelper():
except Exception: except Exception:
LOG.exception(ovn_const.EXCEPTION_MSG, "update of member") LOG.exception(ovn_const.EXCEPTION_MSG, "update of member")
status = { error_updating_member = True
constants.POOLS: [
{constants.ID: member[constants.POOL_ID],
constants.PROVISIONING_STATUS: constants.ACTIVE}],
constants.MEMBERS: [
{constants.ID: member[constants.ID],
constants.PROVISIONING_STATUS: constants.ERROR}],
constants.LOADBALANCERS: [
{constants.ID: ovn_lb.name,
constants.PROVISIONING_STATUS: constants.ACTIVE}]}
listener_status = [] status = self._get_current_operating_statuses(ovn_lb)
for listener in pool_listeners: status[constants.MEMBERS] = [
listener_status.append( {constants.ID: member[constants.ID],
{constants.ID: listener, constants.PROVISIONING_STATUS: constants.ACTIVE,
constants.PROVISIONING_STATUS: constants.ACTIVE}) constants.OPERATING_STATUS: member_operating_status}]
status[constants.LISTENERS] = listener_status if error_updating_member:
status[constants.MEMBERS][0][constants.PROVISIONING_STATUS] = (
constants.ERROR)
return status return status
def _get_existing_pool_members(self, pool_id): def _get_existing_pool_members(self, pool_id):
@@ -2615,18 +2544,17 @@ class OvnProviderHelper():
# Update just the backend_ip member # Update just the backend_ip member
for mb_ip, mb_port, mb_subnet, mb_id in self._extract_member_info( for mb_ip, mb_port, mb_subnet, mb_id in self._extract_member_info(
ovn_lb.external_ids[pool_key]): ovn_lb.external_ids[pool_key]):
member_lsp = self._get_member_lsp(mb_ip, mb_subnet)
if mb_ip == backend_ip: if mb_ip == backend_ip:
member_lsp = self._get_member_lsp(mb_ip, mb_subnet)
if not member_lsp: if not member_lsp:
# NOTE(froyo): In order to continue evaluating the rest of # No port found for the member backend IP, we can determine
# the members, we just warn about the member issue, # that the port doesn't exists or a typo on creation of the
# assuming that it will be in OFFLINE status as soon as the # member, anyway put the member inmediatelly as ERROR
# HM does the first evaluation.
LOG.error("Member %(member)s Logical_Switch_Port not " LOG.error("Member %(member)s Logical_Switch_Port not "
"found, when creating a Health Monitor for " "found, when creating a Health Monitor for "
"pool %(pool)s.", "pool %(pool)s.",
{'member': mb_ip, 'pool': pool_key}) {'member': mb_ip, 'pool': pool_key})
break return constants.ERROR
network_id = member_lsp.external_ids.get( network_id = member_lsp.external_ids.get(
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY).split('neutron-')[1] ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY).split('neutron-')[1]
@@ -2639,7 +2567,7 @@ class OvnProviderHelper():
"health monitoring. Cannot find a Health " "health monitoring. Cannot find a Health "
"Monitor for pool %(pool)s.", "Monitor for pool %(pool)s.",
{'network': network_id, 'pool': pool_key}) {'network': network_id, 'pool': pool_key})
return False return None
hm_source_ip = None hm_source_ip = None
for fixed_ip in hm_port['fixed_ips']: for fixed_ip in hm_port['fixed_ips']:
if fixed_ip['subnet_id'] == mb_subnet: if fixed_ip['subnet_id'] == mb_subnet:
@@ -2652,15 +2580,14 @@ class OvnProviderHelper():
{'subnet': mb_subnet, {'subnet': mb_subnet,
'member': mb_ip, 'member': mb_ip,
'pool': pool_key}) 'pool': pool_key})
return False return None
self._update_ip_port_mappings(ovn_lb, backend_ip, self._update_ip_port_mappings(ovn_lb, backend_ip,
member_lsp.name, hm_source_ip, member_lsp.name, hm_source_ip,
delete) delete)
return True return constants.ONLINE
# NOTE(froyo): If the backend is not located or just one member but not # NOTE(froyo): If the backend is not located
# found the lsp return constants.ERROR
return True
def _lookup_lbhcs_by_hm_id(self, hm_id): def _lookup_lbhcs_by_hm_id(self, hm_id):
lbhc_rows = self.ovn_nbdb_api.db_list_rows( lbhc_rows = self.ovn_nbdb_api.db_list_rows(
@@ -2748,11 +2675,16 @@ class OvnProviderHelper():
if hm_status[constants.PROVISIONING_STATUS] == constants.ACTIVE: if hm_status[constants.PROVISIONING_STATUS] == constants.ACTIVE:
for mb_ip, mb_port, mb_subnet, mb_id in self._extract_member_info( for mb_ip, mb_port, mb_subnet, mb_id in self._extract_member_info(
ovn_lb.external_ids[pool_key]): ovn_lb.external_ids[pool_key]):
if not self._update_hm_member(ovn_lb, pool_key, mb_ip): mb_status = self._update_hm_member(ovn_lb, pool_key, mb_ip)
if not mb_status:
hm_status[constants.PROVISIONING_STATUS] = constants.ERROR hm_status[constants.PROVISIONING_STATUS] = constants.ERROR
hm_status[constants.OPERATING_STATUS] = constants.ERROR hm_status[constants.OPERATING_STATUS] = constants.ERROR
self._clean_ip_port_mappings(ovn_lb, pool_key) self._clean_ip_port_mappings(ovn_lb, pool_key)
break break
self._update_external_ids_member_status(
ovn_lb, mb_id, mb_status)
else:
status = self._get_current_operating_statuses(ovn_lb)
status[constants.HEALTHMONITORS] = [hm_status] status[constants.HEALTHMONITORS] = [hm_status]
return status return status
@@ -2999,11 +2931,12 @@ class OvnProviderHelper():
for k, v in ovn_lb.external_ids.items(): for k, v in ovn_lb.external_ids.items():
if ovn_const.LB_EXT_IDS_LISTENER_PREFIX in k: if ovn_const.LB_EXT_IDS_LISTENER_PREFIX in k:
listeners[k.split('_')[1]] = [ listeners[k.split('_')[1]] = [
x.split('_')[1] for x in v.split(',')] x.split('_')[1] for x in v.split(',')
if ovn_const.LB_EXT_IDS_POOL_PREFIX in x]
continue continue
if ovn_const.LB_EXT_IDS_POOL_PREFIX in k: if ovn_const.LB_EXT_IDS_POOL_PREFIX in k:
pools[k.split('_')[1]] = [ pools[k.split('_')[1]] = [
x.split('_')[1] for x in v.split(',')] x.split('_')[1] for x in v.split(',') if x]
continue continue
for member_id, member_status in member_statuses.items(): for member_id, member_status in member_statuses.items():
@@ -3022,13 +2955,13 @@ class OvnProviderHelper():
members[i] = constants.ONLINE members[i] = constants.ONLINE
_pool = self._octavia_driver_lib.get_pool(pool_id) _pool = self._octavia_driver_lib.get_pool(pool_id)
if not _pool.admin_state_up: if not _pool.admin_state_up or not member_statuses:
pools[pool_id] = constants.OFFLINE pools[pool_id] = constants.OFFLINE
elif all(constants.ERROR == member_status elif pools[pool_id] and all(constants.ERROR == member_status
for member_status in pools[pool_id]): for member_status in pools[pool_id]):
pools[pool_id] = constants.ERROR pools[pool_id] = constants.ERROR
elif any(constants.ERROR == member_status elif pools[pool_id] and any(constants.ERROR == member_status
for member_status in pools[pool_id]): for member_status in pools[pool_id]):
pools[pool_id] = constants.DEGRADED pools[pool_id] = constants.DEGRADED
else: else:
pools[pool_id] = constants.ONLINE pools[pool_id] = constants.ONLINE

View File

@@ -342,11 +342,12 @@ class TestOvnOctaviaBase(base.TestOVNFunctionalBase,
# Withing this status update check if all values of # Withing this status update check if all values of
# expected keys match. # expected keys match.
for k, v in expected_status.items(): for k, v in expected_status.items():
val_check.append( ex = sorted(expected_status[k],
sorted(expected_status[k], key=lambda x: x['id'])
key=lambda x: x['id']) == ox = sorted(updated_status[k],
sorted(updated_status[k], key=lambda x: x['id'])
key=lambda x: x['id'])) val_check.append(all(item in ox for item in ex))
if False in val_check: if False in val_check:
# At least one value don't match. # At least one value don't match.
continue continue
@@ -793,7 +794,8 @@ class TestOvnOctaviaBase(base.TestOVNFunctionalBase,
"provisioning_status": o_constants.DELETED}], "provisioning_status": o_constants.DELETED}],
'loadbalancers': [{ 'loadbalancers': [{
"id": p.loadbalancer_id, "id": p.loadbalancer_id,
"provisioning_status": o_constants.ACTIVE}], "provisioning_status": o_constants.ACTIVE,
'operating_status': o_constants.ONLINE}],
'listeners': []}) 'listeners': []})
self._update_ls_refs( self._update_ls_refs(
lb_data, self._local_net_cache[m.subnet_id], add_ref=False) lb_data, self._local_net_cache[m.subnet_id], add_ref=False)
@@ -861,7 +863,8 @@ class TestOvnOctaviaBase(base.TestOVNFunctionalBase,
self._update_ls_refs(lb_data, network_id) self._update_ls_refs(lb_data, network_id)
pool_listeners = self._get_pool_listeners(lb_data, pool_id) pool_listeners = self._get_pool_listeners(lb_data, pool_id)
expected_listener_status = [ expected_listener_status = [
{'id': listener.listener_id, 'provisioning_status': 'ACTIVE'} {'id': listener.listener_id, 'provisioning_status': 'ACTIVE',
'operating_status': o_constants.ONLINE}
for listener in pool_listeners] for listener in pool_listeners]
expected_status = { expected_status = {
@@ -870,7 +873,8 @@ class TestOvnOctaviaBase(base.TestOVNFunctionalBase,
"provisioning_status": "ACTIVE", "provisioning_status": "ACTIVE",
"operating_status": o_constants.NO_MONITOR}], "operating_status": o_constants.NO_MONITOR}],
'loadbalancers': [{'id': pool.loadbalancer_id, 'loadbalancers': [{'id': pool.loadbalancer_id,
'provisioning_status': 'ACTIVE'}], 'provisioning_status': 'ACTIVE',
'operating_status': o_constants.ONLINE}],
'listeners': expected_listener_status 'listeners': expected_listener_status
} }
self._wait_for_status_and_validate(lb_data, [expected_status]) self._wait_for_status_and_validate(lb_data, [expected_status])
@@ -898,11 +902,13 @@ class TestOvnOctaviaBase(base.TestOVNFunctionalBase,
self.ovn_driver.member_update(old_member, member) self.ovn_driver.member_update(old_member, member)
expected_status = { expected_status = {
'pools': [{'id': pool.pool_id, 'pools': [{'id': pool.pool_id,
'provisioning_status': 'ACTIVE'}], 'provisioning_status': 'ACTIVE',
'operating_status': o_constants.ONLINE}],
'members': [{"id": member.member_id, 'members': [{"id": member.member_id,
'provisioning_status': 'ACTIVE'}], 'provisioning_status': 'ACTIVE'}],
'loadbalancers': [{'id': pool.loadbalancer_id, 'loadbalancers': [{'id': pool.loadbalancer_id,
'provisioning_status': 'ACTIVE'}], 'provisioning_status': 'ACTIVE',
'operating_status': o_constants.ONLINE}],
'listeners': [] 'listeners': []
} }
if getattr(member, 'admin_state_up', None): if getattr(member, 'admin_state_up', None):
@@ -926,7 +932,8 @@ class TestOvnOctaviaBase(base.TestOVNFunctionalBase,
'provisioning_status': 'ACTIVE', 'provisioning_status': 'ACTIVE',
'operating_status': 'ONLINE'}], 'operating_status': 'ONLINE'}],
'loadbalancers': [{'id': pool.loadbalancer_id, 'loadbalancers': [{'id': pool.loadbalancer_id,
'provisioning_status': 'ACTIVE'}], 'provisioning_status': 'ACTIVE',
'operating_status': o_constants.ONLINE}],
'listeners': []}) 'listeners': []})
for m in pool.members: for m in pool.members:
found = False found = False
@@ -974,7 +981,8 @@ class TestOvnOctaviaBase(base.TestOVNFunctionalBase,
'members': [{"id": member.member_id, 'members': [{"id": member.member_id,
"provisioning_status": "DELETED"}], "provisioning_status": "DELETED"}],
'loadbalancers': [{"id": pool.loadbalancer_id, 'loadbalancers': [{"id": pool.loadbalancer_id,
"provisioning_status": "ACTIVE"}], 'provisioning_status': 'ACTIVE',
'operating_status': o_constants.ONLINE}],
'listeners': []} 'listeners': []}
self._update_ls_refs(lb_data, network_id, add_ref=False) self._update_ls_refs(lb_data, network_id, add_ref=False)
@@ -992,6 +1000,10 @@ class TestOvnOctaviaBase(base.TestOVNFunctionalBase,
max_retries, hm_type) max_retries, hm_type)
pool.healthmonitor = m_hm pool.healthmonitor = m_hm
self.ovn_driver._ovn_helper._update_hm_member = mock.MagicMock()
self.ovn_driver._ovn_helper._update_hm_member.side_effect = [
o_constants.ONLINE, o_constants.ONLINE]
self.ovn_driver.health_monitor_create(m_hm) self.ovn_driver.health_monitor_create(m_hm)
pool_listeners = self._get_pool_listeners(lb_data, pool_id) pool_listeners = self._get_pool_listeners(lb_data, pool_id)
expected_listener_status = [ expected_listener_status = [
@@ -1011,7 +1023,8 @@ class TestOvnOctaviaBase(base.TestOVNFunctionalBase,
'pools': [pool_status], 'pools': [pool_status],
'members': expected_member_status, 'members': expected_member_status,
'loadbalancers': [{'id': pool.loadbalancer_id, 'loadbalancers': [{'id': pool.loadbalancer_id,
'provisioning_status': o_constants.ACTIVE}], 'provisioning_status': o_constants.ACTIVE,
'operating_status': o_constants.ONLINE}],
'listeners': expected_listener_status, 'listeners': expected_listener_status,
'healthmonitors': [expected_hm_status] 'healthmonitors': [expected_hm_status]
} }

View File

@@ -131,7 +131,7 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
ovn_const.LB_EXT_IDS_VIP_PORT_ID_KEY: 'foo_hm_port', ovn_const.LB_EXT_IDS_VIP_PORT_ID_KEY: 'foo_hm_port',
ovn_const.LB_EXT_IDS_HMS_KEY: '["%s"]' % (self.ovn_hm.uuid), ovn_const.LB_EXT_IDS_HMS_KEY: '["%s"]' % (self.ovn_hm.uuid),
'enabled': True, 'enabled': True,
'pool_%s' % self.pool_id: [], 'pool_%s' % self.pool_id: '',
'listener_%s' % self.listener_id: '80:pool_%s' % self.pool_id, 'listener_%s' % self.listener_id: '80:pool_%s' % self.pool_id,
ovn_const.OVN_MEMBER_STATUS_KEY: '{}'} ovn_const.OVN_MEMBER_STATUS_KEY: '{}'}
self.helper.ovn_nbdb_api.db_find.return_value.\ self.helper.ovn_nbdb_api.db_find.return_value.\
@@ -233,20 +233,16 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
def test__update_hm_member_no_members(self): def test__update_hm_member_no_members(self):
pool_key = 'pool_%s' % self.pool_id pool_key = 'pool_%s' % self.pool_id
self.ovn_lb.external_ids[pool_key] = '' self.ovn_lb.external_ids[pool_key] = ''
self.assertTrue( self.assertEqual(self.helper._update_hm_member(
self.helper._update_hm_member(self.ovn_lb, self.ovn_lb, pool_key, '10.0.0.4'), constants.ERROR)
pool_key,
'10.0.0.4'))
def test__update_hm_member_backend_ip_not_match(self): def test__update_hm_member_backend_ip_not_match(self):
pool_key = 'pool_%s' % self.pool_id pool_key = 'pool_%s' % self.pool_id
self.ovn_lb.external_ids[pool_key] = self.member_line self.ovn_lb.external_ids[pool_key] = self.member_line
with mock.patch.object(ovn_helper.OvnProviderHelper, with mock.patch.object(ovn_helper.OvnProviderHelper,
'_get_member_lsp'): '_get_member_lsp'):
self.assertTrue( self.assertEqual(self.helper._update_hm_member(
self.helper._update_hm_member(self.ovn_lb, self. ovn_lb, pool_key, '10.0.0.4'), constants.ERROR)
pool_key,
'10.0.0.4'))
@mock.patch.object(ovn_helper.OvnProviderHelper, '_ensure_hm_ovn_port') @mock.patch.object(ovn_helper.OvnProviderHelper, '_ensure_hm_ovn_port')
def test__update_hm_member_hm_port_multiple_ip(self, ensure_hm_port): def test__update_hm_member_hm_port_multiple_ip(self, ensure_hm_port):
@@ -260,10 +256,8 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
pool_key = 'pool_%s' % self.pool_id pool_key = 'pool_%s' % self.pool_id
with mock.patch.object(ovn_helper.OvnProviderHelper, with mock.patch.object(ovn_helper.OvnProviderHelper,
'_get_member_lsp'): '_get_member_lsp'):
self.assertTrue( self.assertEqual(self.helper._update_hm_member(
self.helper._update_hm_member(self.ovn_lb, self.ovn_lb, pool_key, self.member_address), constants.ONLINE)
pool_key,
self.member_address))
@mock.patch.object(ovn_helper.OvnProviderHelper, '_ensure_hm_ovn_port') @mock.patch.object(ovn_helper.OvnProviderHelper, '_ensure_hm_ovn_port')
def test__update_hm_member_hm_port_not_found(self, ensure_hm_port): def test__update_hm_member_hm_port_not_found(self, ensure_hm_port):
@@ -271,7 +265,7 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
pool_key = 'pool_%s' % self.pool_id pool_key = 'pool_%s' % self.pool_id
with mock.patch.object(ovn_helper.OvnProviderHelper, with mock.patch.object(ovn_helper.OvnProviderHelper,
'_get_member_lsp'): '_get_member_lsp'):
self.assertFalse( self.assertIsNone(
self.helper._update_hm_member(self.ovn_lb, self.helper._update_hm_member(self.ovn_lb,
pool_key, pool_key,
self.member_address)) self.member_address))
@@ -1699,28 +1693,11 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
self.helper.ovn_nbdb_api.lb_del.assert_called_once_with( self.helper.ovn_nbdb_api.lb_del.assert_called_once_with(
self.ovn_lb.uuid) self.ovn_lb.uuid)
@mock.patch('ovn_octavia_provider.common.clients.get_neutron_client')
def test_member_create(self, net_cli):
net_cli.return_value.show_subnet.side_effect = [idlutils.RowNotFound]
self.ovn_lb.external_ids = mock.MagicMock()
status = self.helper.member_create(self.member)
self.assertEqual(status['loadbalancers'][0]['provisioning_status'],
constants.ACTIVE)
self.assertEqual(status['pools'][0]['provisioning_status'],
constants.ACTIVE)
self.assertEqual(status['members'][0]['provisioning_status'],
constants.ACTIVE)
self.assertEqual(status['members'][0]['operating_status'],
constants.NO_MONITOR)
calls = [
mock.call.db_clear('Load_Balancer', self.ovn_lb.uuid, 'vips'),
mock.call.db_set('Load_Balancer', self.ovn_lb.uuid, ('vips', {}))]
self.helper.ovn_nbdb_api.assert_has_calls(calls)
@mock.patch('ovn_octavia_provider.common.clients.get_neutron_client') @mock.patch('ovn_octavia_provider.common.clients.get_neutron_client')
def test_member_create_disabled(self, net_cli): def test_member_create_disabled(self, net_cli):
net_cli.return_value.show_subnet.side_effect = [idlutils.RowNotFound] net_cli.return_value.show_subnet.side_effect = [idlutils.RowNotFound]
self.ovn_lb.external_ids = mock.MagicMock() self._update_external_ids_member_status(self.ovn_lb, self.member['id'],
'offline')
self.member['admin_state_up'] = False self.member['admin_state_up'] = False
status = self.helper.member_create(self.member) status = self.helper.member_create(self.member)
self.assertEqual(status['loadbalancers'][0]['provisioning_status'], self.assertEqual(status['loadbalancers'][0]['provisioning_status'],
@@ -1770,8 +1747,12 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
@mock.patch.object(ovn_helper.OvnProviderHelper, '_add_member') @mock.patch.object(ovn_helper.OvnProviderHelper, '_add_member')
def test_member_create_exception(self, mock_add_member): def test_member_create_exception(self, mock_add_member):
mock_add_member.side_effect = [RuntimeError] mock_add_member.side_effect = [RuntimeError]
self._update_external_ids_member_status(self.ovn_lb, self.member_id,
'error')
status = self.helper.member_create(self.member) status = self.helper.member_create(self.member)
self.assertEqual(status['pools'][0]['provisioning_status'], self.assertEqual(status['pools'][0]['provisioning_status'],
constants.ACTIVE)
self.assertEqual(status['pools'][0]['operating_status'],
constants.ERROR) constants.ERROR)
def test_member_create_lb_disabled(self): def test_member_create_lb_disabled(self):
@@ -1806,18 +1787,30 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
self.router) self.router)
@mock.patch('ovn_octavia_provider.common.clients.get_neutron_client') @mock.patch('ovn_octavia_provider.common.clients.get_neutron_client')
def test_member_create_listener(self, net_cli): def test_member_create(self, net_cli):
net_cli.return_value.get_subnet.side_effect = [idlutils.RowNotFound] net_cli.return_value.get_subnet.side_effect = [idlutils.RowNotFound]
self.ovn_lb.external_ids = mock.MagicMock()
self.helper._get_pool_listeners.return_value = ['listener1']
status = self.helper.member_create(self.member) status = self.helper.member_create(self.member)
self.assertEqual(status['loadbalancers'][0]['provisioning_status'],
constants.ACTIVE)
self.assertEqual(status['loadbalancers'][0]['operating_status'],
constants.ONLINE)
self.assertEqual(status['listeners'][0]['provisioning_status'], self.assertEqual(status['listeners'][0]['provisioning_status'],
constants.ACTIVE) constants.ACTIVE)
self.assertEqual(status['listeners'][0]['id'], self.assertEqual(status['listeners'][0]['operating_status'],
'listener1') constants.ONLINE)
self.assertEqual(status['pools'][0]['provisioning_status'],
constants.ACTIVE)
self.assertEqual(status['pools'][0]['operating_status'],
constants.ONLINE)
self.assertEqual(status['members'][0]['id'],
self.member_id)
self.assertEqual(status['members'][0]['provisioning_status'],
constants.ACTIVE)
self.assertEqual(status['members'][0]['operating_status'],
constants.NO_MONITOR)
def test_member_create_already_exists(self): def test_member_create_already_exists(self):
self.helper.member_create(self.member) status = self.helper.member_create(self.member)
member_status = { member_status = {
ovn_const.OVN_MEMBER_STATUS_KEY: '{"%s": "%s"}' ovn_const.OVN_MEMBER_STATUS_KEY: '{"%s": "%s"}'
% (self.member_id, constants.NO_MONITOR)} % (self.member_id, constants.NO_MONITOR)}
@@ -1825,6 +1818,14 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
'Load_Balancer', 'Load_Balancer',
self.ovn_lb.uuid, self.ovn_lb.uuid,
('external_ids', member_status)) ('external_ids', member_status))
self.assertEqual(status['loadbalancers'][0]['provisioning_status'],
constants.ACTIVE)
self.assertEqual(status['pools'][0]['provisioning_status'],
constants.ACTIVE)
self.assertEqual(status['members'][0]['provisioning_status'],
constants.ACTIVE)
self.assertEqual(status['members'][0]['operating_status'],
constants.NO_MONITOR)
def test_member_create_first_member_in_pool(self): def test_member_create_first_member_in_pool(self):
self.ovn_lb.external_ids.update({ self.ovn_lb.external_ids.update({
@@ -1873,7 +1874,6 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
expected_calls) expected_calls)
def test_member_update(self): def test_member_update(self):
self.ovn_lb.external_ids = mock.MagicMock()
status = self.helper.member_update(self.member) status = self.helper.member_update(self.member)
self.assertEqual(status['loadbalancers'][0]['provisioning_status'], self.assertEqual(status['loadbalancers'][0]['provisioning_status'],
constants.ACTIVE) constants.ACTIVE)
@@ -1884,6 +1884,8 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
self.assertEqual(status['members'][0]['operating_status'], self.assertEqual(status['members'][0]['operating_status'],
constants.NO_MONITOR) constants.NO_MONITOR)
self.member['admin_state_up'] = False self.member['admin_state_up'] = False
self._update_external_ids_member_status(self.ovn_lb, self.member_id,
'offline')
status = self.helper.member_update(self.member) status = self.helper.member_update(self.member)
self.assertEqual(status['loadbalancers'][0]['provisioning_status'], self.assertEqual(status['loadbalancers'][0]['provisioning_status'],
constants.ACTIVE) constants.ACTIVE)
@@ -1903,6 +1905,8 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
constants.ACTIVE) constants.ACTIVE)
self.member['old_admin_state_up'] = False self.member['old_admin_state_up'] = False
self.member['admin_state_up'] = True self.member['admin_state_up'] = True
self._update_external_ids_member_status(self.ovn_lb, self.member_id,
'online')
status = self.helper.member_update(self.member) status = self.helper.member_update(self.member)
self.assertEqual(status['loadbalancers'][0]['provisioning_status'], self.assertEqual(status['loadbalancers'][0]['provisioning_status'],
constants.ACTIVE) constants.ACTIVE)
@@ -1937,15 +1941,6 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
[mock.call('pool_%s' % self.pool_id), [mock.call('pool_%s' % self.pool_id),
mock.call('pool_%s%s' % (self.pool_id, ':D'))]) mock.call('pool_%s%s' % (self.pool_id, ':D'))])
def test_member_update_pool_listeners(self):
self.ovn_lb.external_ids = mock.MagicMock()
self.helper._get_pool_listeners.return_value = ['listener1']
status = self.helper.member_update(self.member)
self.assertEqual(status['listeners'][0]['provisioning_status'],
constants.ACTIVE)
self.assertEqual(status['listeners'][0]['id'],
'listener1')
@mock.patch.object(ovn_helper.OvnProviderHelper, '_find_member_status') @mock.patch.object(ovn_helper.OvnProviderHelper, '_find_member_status')
def test_member_update_exception(self, mock_find_member_status): def test_member_update_exception(self, mock_find_member_status):
mock_find_member_status.side_effect = [TypeError] mock_find_member_status.side_effect = [TypeError]
@@ -1964,7 +1959,11 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
self.assertEqual(status['members'][0]['provisioning_status'], self.assertEqual(status['members'][0]['provisioning_status'],
constants.DELETED) constants.DELETED)
def test_member_delete_one_left(self): @mock.patch.object(ovn_helper.OvnProviderHelper, '_remove_member')
@mock.patch.object(ovn_helper.OvnProviderHelper,
'_update_external_ids_member_status')
def test_member_delete_one_left(self, update_external_ids_members,
rmmember):
member2_id = uuidutils.generate_uuid() member2_id = uuidutils.generate_uuid()
member2_port = '1010' member2_port = '1010'
member2_address = '192.168.2.150' member2_address = '192.168.2.150'
@@ -1977,6 +1976,10 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
self.ovn_lb.external_ids.update({ self.ovn_lb.external_ids.update({
'pool_' + self.pool_id: member_line}) 'pool_' + self.pool_id: member_line})
status = self.helper.member_delete(self.member) status = self.helper.member_delete(self.member)
rmmember.assert_called_once_with(
self.member, self.ovn_lb, 'pool_' + self.pool_id)
update_external_ids_members.assert_called_once_with(
self.ovn_lb, self.member_id, None, delete=True)
self.assertEqual(status['members'][0]['provisioning_status'], self.assertEqual(status['members'][0]['provisioning_status'],
constants.DELETED) constants.DELETED)
self.assertEqual(status['pools'][0]['provisioning_status'], self.assertEqual(status['pools'][0]['provisioning_status'],
@@ -1996,13 +1999,18 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
self.member_address, self.member_address,
delete=True) delete=True)
def test_member_delete_none(self): def test_member_delete_not_found_in_pool(self):
self.ovn_lb.external_ids.update({'pool_' + self.pool_id: ''}) self.ovn_lb.external_ids.update({'pool_' + self.pool_id: ''})
self.ovn_lb.external_ids[ovn_const.OVN_MEMBER_STATUS_KEY] = '{}'
status = self.helper.member_delete(self.member) status = self.helper.member_delete(self.member)
self.assertEqual(status['members'][0]['provisioning_status'], self.assertEqual(status['loadbalancers'][0]['provisioning_status'],
constants.ERROR) constants.ACTIVE)
self.assertEqual(status['listeners'][0]['provisioning_status'],
constants.ACTIVE)
self.assertEqual(status['pools'][0]['provisioning_status'], self.assertEqual(status['pools'][0]['provisioning_status'],
constants.ACTIVE) constants.ACTIVE)
self.assertEqual(status['members'][0]['provisioning_status'],
constants.ERROR)
@mock.patch.object(ovn_helper.OvnProviderHelper, '_remove_member') @mock.patch.object(ovn_helper.OvnProviderHelper, '_remove_member')
def test_member_delete_exception(self, mock_remove_member): def test_member_delete_exception(self, mock_remove_member):
@@ -2019,20 +2027,6 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
[mock.call('pool_%s' % self.pool_id), [mock.call('pool_%s' % self.pool_id),
mock.call('pool_%s%s' % (self.pool_id, ':D'))]) mock.call('pool_%s%s' % (self.pool_id, ':D'))])
def test_member_delete_pool_listeners(self):
member_line = (
'member_%s_%s:%s_%s' %
(self.member_id, self.member_address, self.member_port,
self.member_subnet_id))
self.ovn_lb.external_ids.update({
'pool_' + self.pool_id: member_line})
self.helper._get_pool_listeners.return_value = ['listener1']
status = self.helper.member_delete(self.member)
self.assertEqual(status['listeners'][0]['provisioning_status'],
constants.ACTIVE)
self.assertEqual(status['listeners'][0]['id'],
'listener1')
@mock.patch('ovn_octavia_provider.common.clients.get_neutron_client') @mock.patch('ovn_octavia_provider.common.clients.get_neutron_client')
def test_logical_router_port_event_create(self, net_cli): def test_logical_router_port_event_create(self, net_cli):
self.router_port_event = ovn_event.LogicalRouterPortEvent( self.router_port_event = ovn_event.LogicalRouterPortEvent(
@@ -3705,10 +3699,12 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
pool_key = 'pool_%s' % self.pool_id pool_key = 'pool_%s' % self.pool_id
self.ovn_hm_lb.protocol = [protocol] self.ovn_hm_lb.protocol = [protocol]
folbpi.return_value = (pool_key, self.ovn_hm_lb) folbpi.return_value = (pool_key, self.ovn_hm_lb)
uhm.return_value = True uhm.return_value = constants.ONLINE
net_cli.return_value.get_subnet.return_value = {'subnet': fake_subnet} net_cli.return_value.get_subnet.return_value = {'subnet': fake_subnet}
if not fip: if not fip:
del self.ovn_hm_lb.external_ids[ovn_const.LB_EXT_IDS_VIP_FIP_KEY] del self.ovn_hm_lb.external_ids[ovn_const.LB_EXT_IDS_VIP_FIP_KEY]
self._update_external_ids_member_status(self.ovn_hm_lb, self.member_id,
'online')
status = self.helper.hm_create(self.health_monitor) status = self.helper.hm_create(self.health_monitor)
self.assertEqual(status['healthmonitors'][0]['provisioning_status'], self.assertEqual(status['healthmonitors'][0]['provisioning_status'],
constants.ACTIVE) constants.ACTIVE)
@@ -3815,6 +3811,8 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
pool_key = 'pool_%s' % self.pool_id pool_key = 'pool_%s' % self.pool_id
folbpi.return_value = (pool_key, self.ovn_hm_lb) folbpi.return_value = (pool_key, self.ovn_hm_lb)
self.health_monitor['admin_state_up'] = False self.health_monitor['admin_state_up'] = False
self._update_external_ids_member_status(self.ovn_hm_lb, self.member_id,
'online')
status = self.helper.hm_create(self.health_monitor) status = self.helper.hm_create(self.health_monitor)
self.assertEqual(status['healthmonitors'][0]['provisioning_status'], self.assertEqual(status['healthmonitors'][0]['provisioning_status'],
constants.ACTIVE) constants.ACTIVE)
@@ -4359,7 +4357,7 @@ 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,
member_status): mb_status):
info = { info = {
'ovn_lbs': ovn_lbs, 'ovn_lbs': ovn_lbs,
'ip': ip, 'ip': ip,
@@ -4367,9 +4365,10 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
'src_ip': '10.22.33.4', 'src_ip': '10.22.33.4',
'port': port, 'port': port,
'protocol': ovn_lbs[0].protocol, 'protocol': ovn_lbs[0].protocol,
'status': [member_status]} 'status': [mb_status]}
mb_status_ovn = 'error' if mb_status == 'offline' else mb_status
self._update_external_ids_member_status(self.ovn_hm_lb, member_id, self._update_external_ids_member_status(self.ovn_hm_lb, member_id,
member_status) mb_status_ovn)
status = self.helper.hm_update_event(info) status = self.helper.hm_update_event(info)
return status return status
@@ -4414,6 +4413,8 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
def _update_external_ids_member_status(self, lb, member_id, member_status): def _update_external_ids_member_status(self, lb, member_id, member_status):
status = constants.ONLINE status = constants.ONLINE
if member_status == 'offline': if member_status == 'offline':
status = constants.OFFLINE
elif member_status == 'error':
status = constants.ERROR status = constants.ERROR
try: try:
existing_member_status = lb.external_ids[ existing_member_status = lb.external_ids[
@@ -4657,9 +4658,9 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
'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'],
'offline') 'error')
self._update_external_ids_member_status(ovn_hm_lb_2, member_2['id'], self._update_external_ids_member_status(ovn_hm_lb_2, member_2['id'],
'offline') 'error')
status = self.helper.hm_update_event(info) status = self.helper.hm_update_event(info)
self.assertEqual(status['pools'][0]['provisioning_status'], self.assertEqual(status['pools'][0]['provisioning_status'],
@@ -4767,10 +4768,10 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
member_lb2 = self._add_member(ovn_hm_lb2, fake_subnet, 8081, member_lb2 = self._add_member(ovn_hm_lb2, fake_subnet, 8081,
pool_id=pool_id_2, ip=ip_member) pool_id=pool_id_2, ip=ip_member)
# member lb2 OFFLINE, so lb2 operating_status should be ERROR # member lb2 ERROR, so lb2 operating_status should be ERROR
# for Pool and Loadbalancer, but lb1 should keep ONLINE # for Pool and Loadbalancer, but lb1 should keep ONLINE
self._update_external_ids_member_status(ovn_hm_lb2, member_lb2['id'], self._update_external_ids_member_status(ovn_hm_lb2, member_lb2['id'],
'offline') 'error')
info = { info = {
'ovn_lbs': [self.ovn_hm_lb, ovn_hm_lb2], 'ovn_lbs': [self.ovn_hm_lb, ovn_hm_lb2],