Apply ServiceMonitorEvent to affected LBs

Every Health_Monitor over a LB creates Load_Balancer_Health_Check
NB entry, and it creates 1..n Service_Monitor OVN SB entries according
to the member to be monitored. When more than one LB exists on the
same network segment for the same member, Service_Monitor SB
entries are not duplicated, in order to not performing multiple
checks on the same member.

When a ServiceMonitorUpdateEvent is received with status information
from a member, the LBs that match the event information (protocol,
ip, port and logical_port) are searched. At this way we ensure that
network segments are separated where the same ip and port could match
but the logical_port would be different.

The current logic will update just the status of the first LB selected
as "related" to the member event received from the Service_Monitor,
but we could use this information to keep updated all those LBs, that
are in the same network segment and have a Health_Monitor associated
to.

This patch will fix this behaviour in order to provide status to
Octavia of all LBs affected by a ServiceMonitorUpdateEvent.

Partial-Bug: #1965772
Closes-Bug: #1965772
Change-Id: I7c75003516015863320e53f0175dec8fdf4e2cf7
(cherry picked from commit 7db9e23fd9)
This commit is contained in:
Fernando Royo 2022-06-01 13:46:09 +02:00
parent 6b2b7501a8
commit b006d5273d
2 changed files with 114 additions and 71 deletions

View File

@ -2498,14 +2498,17 @@ class OvnProviderHelper():
'update will have incomplete data', ovn_lb.name)
return status
def _get_lb_on_hm_event(self, row):
def _get_lbs_on_hm_event(self, row):
"""Get the Load Balancer information on a health_monitor event
This function is called when the status of a member has
been updated.
been updated. As no duplicate entries are created on a same
member for different LBs we will search all LBs affected by
the member reported in the health check event
Input: Service Monitor row which is coming from
ServiceMonitorUpdateEvent.
Output: A row from load_balancer table table matching the member
Output: Rows from load_balancer table table matching the member
for which the event was generated.
Exception: RowNotFound exception can be generated.
"""
@ -2525,11 +2528,11 @@ class OvnProviderHelper():
lbs = self.ovn_nbdb_api.db_find_rows(
'Load_Balancer', ('ip_port_mappings', '=', mappings),
('protocol', '=', row.protocol[0])).execute()
return lbs[0] if lbs else None
return lbs if lbs else None
def hm_update_event_handler(self, row):
try:
ovn_lb = self._get_lb_on_hm_event(row)
ovn_lb = self._get_lbs_on_hm_event(row)
except idlutils.RowNotFound:
LOG.debug("Load balancer information not found")
return
@ -2538,11 +2541,6 @@ class OvnProviderHelper():
LOG.debug("Load balancer not found")
return
if row.protocol != ovn_lb.protocol:
LOG.debug('Row protocol (%s) does not match LB protocol (%s)',
row.protocol, ovn_lb.protocol)
return
request_info = {'ovn_lb': ovn_lb,
'ip': row.ip,
'port': str(row.port),
@ -2661,37 +2659,50 @@ class OvnProviderHelper():
return status
def hm_update_event(self, info):
ovn_lb = info['ovn_lb']
ovn_lbs = info['ovn_lb']
statuses = []
# Lookup member
member_id = None
for k, v in ovn_lb.external_ids.items():
if ovn_const.LB_EXT_IDS_POOL_PREFIX not in k:
continue
for member_ip, member_port, subnet in self._extract_member_info(v):
if info['ip'] != member_ip:
for ovn_lb in ovn_lbs:
for k, v in ovn_lb.external_ids.items():
if ovn_const.LB_EXT_IDS_POOL_PREFIX not in k:
continue
if info['port'] != member_port:
continue
# match
for (
member_ip,
member_port,
subnet,
) in self._extract_member_info(v):
if info['ip'] != member_ip:
continue
if info['port'] != member_port:
continue
# match
member_id = [mb.split('_')[1] for mb in v.split(',')
if member_ip in mb and member_port in mb][0]
break
member_id = [mb.split('_')[1] for mb in v.split(',')
if member_ip in mb and member_port in mb][0]
break
# found it in inner loop
if member_id:
break
# found it in inner loop
if member_id:
break
if not member_id:
LOG.warning('Member for event not found, info: %s', info)
return
if not member_id:
LOG.warning('Member for event not found, info: %s', info)
return
member_status = constants.ONLINE
if info['status'] == ['offline']:
member_status = constants.ERROR
member_status = constants.ONLINE
if info['status'] == ['offline']:
member_status = constants.ERROR
self._update_member_status(ovn_lb, member_id, member_status)
statuses.append(self._get_current_operating_statuses(ovn_lb))
status = {}
for status_lb in statuses:
for k in status_lb.keys():
if k not in status:
status[k] = []
status[k].extend(status_lb[k])
self._update_member_status(ovn_lb, member_id, member_status)
status = self._get_current_operating_statuses(ovn_lb)
return status

View File

@ -3664,7 +3664,7 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
self.hm_update_event.run('update', row, mock.ANY)
expected = {
'info':
{'ovn_lb': self.ovn_hm_lb,
{'ovn_lb': [self.ovn_hm_lb],
'ip': self.member_address,
'port': self.member_port,
'status': ['offline']},
@ -3706,21 +3706,6 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
self.hm_update_event.run('update', row, mock.ANY)
self.mock_add_request.assert_not_called()
def test_hm_update_event_lb_protocol_not_found(self):
self.helper.ovn_nbdb_api.db_find_rows.\
side_effect = [self.ovn_hm_lb, idlutils.RowNotFound]
self.hm_update_event = ovn_event.ServiceMonitorUpdateEvent(
self.helper)
row = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={'ip': self.member_address,
'logical_port': 'a-logical-port',
'src_ip': '10.22.33.4',
'port': self.member_port,
'protocol': 'unknown',
'status': ['offline']})
self.hm_update_event.run('update', row, mock.ANY)
self.mock_add_request.assert_not_called()
def _test_hm_update_no_member(self, bad_ip, bad_port):
fake_subnet = fakes.FakeSubnet.create_one_subnet()
fake_port = fakes.FakePort.create_one_port(
@ -3746,7 +3731,7 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
if bad_port:
port = 'bad-port'
info = {
'ovn_lb': self.ovn_hm_lb,
'ovn_lb': [self.ovn_hm_lb],
'ip': ip,
'logical_port': 'a-logical-port',
'src_ip': '10.22.33.4',
@ -3765,7 +3750,7 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
def _test_hm_update_status(self, member_id, ip, port, member_status):
info = {
'ovn_lb': self.ovn_hm_lb,
'ovn_lb': [self.ovn_hm_lb],
'ip': ip,
'logical_port': 'a-logical-port',
'src_ip': '10.22.33.4',
@ -3792,10 +3777,12 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
ovn_const.OVN_MEMBER_STATUS_KEY] = jsonutils.dumps(
member_statuses)
def _add_member(self, subnet, port):
fake_port = fakes.FakePort.create_one_port(
attrs={'allowed_address_pairs': ''})
ip = fake_port['fixed_ips'][0]['ip_address']
def _add_member(self, lb, subnet, port, ip=None):
if not ip:
fake_port = fakes.FakePort.create_one_port(
attrs={'allowed_address_pairs': ''})
ip = fake_port['fixed_ips'][0]['ip_address']
member = {'id': uuidutils.generate_uuid(),
'address': ip,
'protocol_port': port,
@ -3809,8 +3796,8 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
member['protocol_port'], member['subnet_id']))
pool_key = 'pool_%s' % self.pool_id
existing_members = self.ovn_hm_lb.external_ids[pool_key]
existing_member_status = self.ovn_hm_lb.external_ids[
existing_members = lb.external_ids[pool_key]
existing_member_status = lb.external_ids[
ovn_const.OVN_MEMBER_STATUS_KEY]
try:
@ -3820,22 +3807,22 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
if existing_members:
existing_members = ','.join([existing_members, member_line])
self.ovn_hm_lb.external_ids[pool_key] = existing_members
lb.external_ids[pool_key] = existing_members
member_statuses[member['id']] = constants.ONLINE
self.ovn_hm_lb.external_ids[
lb.external_ids[
ovn_const.OVN_MEMBER_STATUS_KEY] = jsonutils.dumps(
member_statuses)
else:
self.ovn_hm_lb.external_ids[pool_key] = member_line
lb.external_ids[pool_key] = member_line
member_status = '{"%s": "%s"}' % (member['id'],
constants.ONLINE)
self.ovn_hm_lb.external_ids[
lb.external_ids[
ovn_const.OVN_MEMBER_STATUS_KEY] = member_status
return member
def test_hm_update_status_offline(self):
fake_subnet = fakes.FakeSubnet.create_one_subnet()
member = self._add_member(fake_subnet, 8080)
member = self._add_member(self.ovn_hm_lb, fake_subnet, 8080)
status = self._test_hm_update_status(
member['id'], member['address'], '8080', 'offline')
@ -3852,9 +3839,54 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
self.assertEqual(status['loadbalancers'][0]['operating_status'],
constants.ERROR)
def test_hm_update_status_offline_two_lbs_affected(self):
fake_subnet = fakes.FakeSubnet.create_one_subnet()
ovn_hm_lb_2 = copy.deepcopy(self.ovn_hm_lb)
ovn_hm_lb_2.uuid = uuidutils.generate_uuid()
member = self._add_member(self.ovn_hm_lb, fake_subnet, 8080)
member_2 = self._add_member(
ovn_hm_lb_2, fake_subnet, 8080, ip=member['address'])
info = {
'ovn_lb': [self.ovn_hm_lb, ovn_hm_lb_2],
'ip': member['address'],
'logical_port': 'a-logical-port',
'src_ip': '10.22.33.4',
'port': '8080',
'protocol': self.ovn_hm_lb.protocol,
'status': ['offline']}
self._update_member_status(self.ovn_hm_lb, member['id'], 'offline')
self._update_member_status(ovn_hm_lb_2, member_2['id'], 'offline')
status = self.helper.hm_update_event(info)
self.assertEqual(status['pools'][0]['provisioning_status'],
constants.ACTIVE)
self.assertEqual(status['pools'][0]['operating_status'],
constants.ERROR)
self.assertEqual(status['members'][0]['provisioning_status'],
constants.ACTIVE)
self.assertEqual(status['members'][0]['operating_status'],
constants.ERROR)
self.assertEqual(status['loadbalancers'][0]['provisioning_status'],
constants.ACTIVE)
self.assertEqual(status['loadbalancers'][0]['operating_status'],
constants.ERROR)
self.assertEqual(status['pools'][1]['provisioning_status'],
constants.ACTIVE)
self.assertEqual(status['pools'][1]['operating_status'],
constants.ERROR)
self.assertEqual(status['members'][1]['provisioning_status'],
constants.ACTIVE)
self.assertEqual(status['members'][1]['operating_status'],
constants.ERROR)
self.assertEqual(status['loadbalancers'][1]['provisioning_status'],
constants.ACTIVE)
self.assertEqual(status['loadbalancers'][1]['operating_status'],
constants.ERROR)
def test_hm_update_status_offline_lb_pool_offline(self):
fake_subnet = fakes.FakeSubnet.create_one_subnet()
member = self._add_member(fake_subnet, 8080)
member = self._add_member(self.ovn_hm_lb, fake_subnet, 8080)
status = self._test_hm_update_status(
member['id'], member['address'], '8080', 'offline')
@ -3873,7 +3905,7 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
def test_hm_update_status_online(self):
fake_subnet = fakes.FakeSubnet.create_one_subnet()
member = self._add_member(fake_subnet, 8080)
member = self._add_member(self.ovn_hm_lb, fake_subnet, 8080)
status = self._test_hm_update_status(
member['id'], member['address'], '8080', 'online')
@ -3892,7 +3924,7 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
def test_hm_update_status_online_lb_pool_offline(self):
fake_subnet = fakes.FakeSubnet.create_one_subnet()
member = self._add_member(fake_subnet, 8080)
member = self._add_member(self.ovn_hm_lb, fake_subnet, 8080)
status = self._test_hm_update_status(
member['id'], member['address'], '8080', 'online')
@ -3911,9 +3943,9 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
def test_hm_update_status_offline_two_members(self):
fake_subnet = fakes.FakeSubnet.create_one_subnet()
member_1 = self._add_member(fake_subnet, 8080)
member_1 = self._add_member(self.ovn_hm_lb, fake_subnet, 8080)
ip_1 = member_1['address']
member_2 = self._add_member(fake_subnet, 8081)
member_2 = self._add_member(self.ovn_hm_lb, fake_subnet, 8081)
ip_2 = member_2['address']
# This is the Octavia API version
fake_member = fakes.FakeMember(
@ -3954,8 +3986,8 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
def test_hm_update_status_online_two_members(self):
fake_subnet = fakes.FakeSubnet.create_one_subnet()
member_1 = self._add_member(fake_subnet, 8080)
member_2 = self._add_member(fake_subnet, 8081)
member_1 = self._add_member(self.ovn_hm_lb, fake_subnet, 8080)
member_2 = self._add_member(self.ovn_hm_lb, fake_subnet, 8081)
ip_2 = member_2['address']
# This is the Octavia API version
fake_member = fakes.FakeMember(