Merge "FIX OVN LB Health Monitor checks for IPv6 members" into stable/2024.1
This commit is contained in:
commit
eb7fcaaa7e
@ -16,6 +16,7 @@ import inspect
|
||||
import threading
|
||||
|
||||
from futurist import periodics
|
||||
import netaddr
|
||||
from neutron_lib import constants as n_const
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
@ -118,3 +119,42 @@ class DBInconsistenciesPeriodics(object):
|
||||
raise periodics.NeverAgain()
|
||||
LOG.debug('Maintenance task: device_owner and device_id checked for '
|
||||
'OVN LB HM ports.')
|
||||
|
||||
# TODO(froyo): Remove this in the Caracal+4 cycle
|
||||
@periodics.periodic(spacing=600, run_immediately=True)
|
||||
def format_ip_port_mappings_ipv6(self):
|
||||
"""Give correct format to `ip_port_mappings` for IPv6 backend members.
|
||||
|
||||
The `ip_port_mappings` field for OVN LBs should be a dictionary with
|
||||
keys following the format:
|
||||
`${MEMBER_IP}=${LSP_NAME_MEMBER}:${HEALTH_SRC_IP}`. However, when
|
||||
`MEMBER_IP` and `HEALTH_SRC_IP` are IPv6 addresses, they should be
|
||||
enclosed in `[]`.
|
||||
"""
|
||||
LOG.debug('Maintenance task: Ensure correct formatting of '
|
||||
'ip_port_mappings for IPv6 backend members.')
|
||||
ovn_lbs = self.ovn_nbdb_api.db_find_rows(
|
||||
'Load_Balancer', ('ip_port_mappings', '!=', {})).execute()
|
||||
for lb in ovn_lbs:
|
||||
mappings = {}
|
||||
for k, v in lb.ip_port_mappings.items():
|
||||
try:
|
||||
# If first element is IPv4 (mixing IPv4 and IPv6 not
|
||||
# allowed) or get AddrFormatError (IPv6 already fixed) we
|
||||
# can jump to next item
|
||||
if netaddr.IPNetwork(k).version == n_const.IP_VERSION_4:
|
||||
break
|
||||
except netaddr.AddrFormatError:
|
||||
break
|
||||
port_uuid, src_ip = v.split(':', 1)
|
||||
mappings[f'[{k}]'] = f'{port_uuid}:[{src_ip}]'
|
||||
self.ovn_nbdb_api.db_clear('Load_Balancer', lb.uuid,
|
||||
'ip_port_mappings').execute(
|
||||
check_error=True)
|
||||
self.ovn_nbdb_api.db_set('Load_Balancer', lb.uuid,
|
||||
('ip_port_mappings', mappings)).execute(
|
||||
check_error=True)
|
||||
|
||||
LOG.debug('Maintenance task: no more ip_port_mappings to format, '
|
||||
'stopping the periodic task.')
|
||||
raise periodics.NeverAgain()
|
||||
|
@ -13,6 +13,8 @@
|
||||
import atexit
|
||||
import contextlib
|
||||
|
||||
import netaddr
|
||||
from neutron_lib import constants as n_const
|
||||
from neutron_lib import exceptions as n_exc
|
||||
from oslo_log import log
|
||||
from ovsdbapp.backend import ovs_idl
|
||||
@ -137,6 +139,53 @@ class GetLrsCommand(command.ReadOnlyCommand):
|
||||
self.api.tables['Logical_Router'].rows.values()]
|
||||
|
||||
|
||||
# NOTE(froyo): remove this class once ovsdbapp manages the IPv6 into [ ]
|
||||
# https://bugs.launchpad.net/ovsdbapp/+bug/2057471
|
||||
class DelBackendFromIPPortMapping(command.BaseCommand):
|
||||
table = 'Load_Balancer'
|
||||
|
||||
def __init__(self, api, lb, backend_ip):
|
||||
super().__init__(api)
|
||||
self.lb = lb
|
||||
if netaddr.IPNetwork(backend_ip).version == n_const.IP_VERSION_6:
|
||||
self.backend_ip = f'[{backend_ip}]'
|
||||
else:
|
||||
self.backend_ip = backend_ip
|
||||
|
||||
def run_idl(self, txn):
|
||||
try:
|
||||
ovn_lb = self.api.lookup(self.table, self.lb)
|
||||
ovn_lb.delkey('ip_port_mappings', self.backend_ip)
|
||||
except Exception:
|
||||
LOG.exception("Error deleting backend %s from ip_port_mappings "
|
||||
"for LB uuid %s", str(self.backend_ip), str(self.lb))
|
||||
|
||||
|
||||
# NOTE(froyo): remove this class once ovsdbapp manages the IPv6 into [ ]
|
||||
# https://bugs.launchpad.net/ovsdbapp/+bug/2057471
|
||||
class AddBackendToIPPortMapping(command.BaseCommand):
|
||||
table = 'Load_Balancer'
|
||||
|
||||
def __init__(self, api, lb, backend_ip, port_name, src_ip):
|
||||
super().__init__(api)
|
||||
self.lb = lb
|
||||
self.backend_ip = backend_ip
|
||||
self.port_name = port_name
|
||||
self.src_ip = src_ip
|
||||
if netaddr.IPNetwork(backend_ip).version == n_const.IP_VERSION_6:
|
||||
self.backend_ip = f'[{backend_ip}]'
|
||||
self.src_ip = f'[{src_ip}]'
|
||||
|
||||
def run_idl(self, txn):
|
||||
try:
|
||||
lb = self.api.lookup(self.table, self.lb)
|
||||
lb.setkey('ip_port_mappings', self.backend_ip,
|
||||
'%s:%s' % (self.port_name, self.src_ip))
|
||||
except Exception:
|
||||
LOG.exception("Error adding backend %s to ip_port_mappings "
|
||||
"for LB uuid %s", str(self.backend_ip), str(self.lb))
|
||||
|
||||
|
||||
class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
|
||||
def __init__(self, connection):
|
||||
super().__init__(connection)
|
||||
@ -172,6 +221,15 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
|
||||
def get_lrs(self):
|
||||
return GetLrsCommand(self)
|
||||
|
||||
# NOTE(froyo): remove this method once ovsdbapp manages the IPv6 into [ ]
|
||||
def lb_del_ip_port_mapping(self, lb_uuid, backend_ip):
|
||||
return DelBackendFromIPPortMapping(self, lb_uuid, backend_ip)
|
||||
|
||||
# NOTE(froyo): remove this method once ovsdbapp manages the IPv6 into [ ]
|
||||
def lb_add_ip_port_mapping(self, lb_uuid, backend_ip, port_name, src_ip):
|
||||
return AddBackendToIPPortMapping(self, lb_uuid, backend_ip, port_name,
|
||||
src_ip)
|
||||
|
||||
|
||||
class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend):
|
||||
def __init__(self, connection):
|
||||
|
@ -253,6 +253,28 @@ class FakeOVNRouter():
|
||||
return type('Logical_Router', (object, ), router_attrs)
|
||||
|
||||
|
||||
class FakeOVNLB():
|
||||
|
||||
@staticmethod
|
||||
def create_one_lb(attrs=None):
|
||||
fake_uuid = uuidutils.generate_uuid()
|
||||
lb_attrs = {
|
||||
'uuid': fake_uuid,
|
||||
'external_ids': {},
|
||||
'health_check': [],
|
||||
'ip_port_mappings': {},
|
||||
'name': '',
|
||||
'options': {},
|
||||
'protocol': 'tcp',
|
||||
'selection_fields': [],
|
||||
'vips': {}
|
||||
}
|
||||
|
||||
# Overwrite default attributes.
|
||||
lb_attrs.update(attrs)
|
||||
return type('Load_Balancer', (object, ), lb_attrs)
|
||||
|
||||
|
||||
class FakePort():
|
||||
"""Fake one or more ports."""
|
||||
|
||||
|
@ -326,26 +326,40 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase):
|
||||
mock.call(self.ovn_hm_lb.uuid, 'address2'),
|
||||
mock.ANY])
|
||||
|
||||
def test__update_ip_port_mappings(self):
|
||||
def test__update_ip_port_mappings_del_backend_member(self):
|
||||
src_ip = '10.22.33.4'
|
||||
fakes.FakeOvsdbRow.create_one_ovsdb_row(
|
||||
attrs={'ip': self.member_address,
|
||||
'logical_port': 'a-logical-port',
|
||||
'src_ip': src_ip,
|
||||
'port': self.member_port,
|
||||
'protocol': self.ovn_hm_lb.protocol,
|
||||
'status': ovn_const.HM_EVENT_MEMBER_PORT_ONLINE})
|
||||
self.helper._update_ip_port_mappings(
|
||||
self.ovn_lb, self.member_address, 'a-logical-port', src_ip)
|
||||
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)
|
||||
self.helper._update_ip_port_mappings(
|
||||
self.ovn_lb, self.member_address, 'a-logical-port', src_ip,
|
||||
delete=True)
|
||||
self.helper.ovn_nbdb_api.lb_del_ip_port_mapping.\
|
||||
assert_called_once_with(self.ovn_lb.uuid, self.member_address)
|
||||
|
||||
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.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)
|
||||
|
||||
def test__update_ip_port_mappings_del_backend_member_ipv6(self):
|
||||
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,
|
||||
delete=True)
|
||||
self.helper.ovn_nbdb_api.lb_del_ip_port_mapping.\
|
||||
assert_called_once_with(self.ovn_lb.uuid, member_address)
|
||||
|
||||
def test__update_ip_port_mappings_add_backend_member_ipv6(self):
|
||||
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.helper.ovn_nbdb_api.lb_add_ip_port_mapping.\
|
||||
assert_called_once_with(
|
||||
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(
|
||||
self.ovn_lb, self.member_id, constants.NO_MONITOR)
|
||||
|
@ -132,3 +132,61 @@ class TestDBInconsistenciesPeriodics(ovn_base.TestOvnOctaviaBase):
|
||||
]
|
||||
net_cli.assert_has_calls(expected_call)
|
||||
self.maint.ovn_nbdb_api.db_find_rows.assert_not_called()
|
||||
|
||||
def test_format_ip_port_mappings_ipv6_no_ip_port_mappings_to_change(self):
|
||||
self.maint.ovn_nbdb_api.db_find_rows.return_value.\
|
||||
execute.return_value = []
|
||||
|
||||
self.assertRaises(periodics.NeverAgain,
|
||||
self.maint.format_ip_port_mappings_ipv6)
|
||||
self.maint.ovn_nbdb_api.db_clear.assert_not_called()
|
||||
self.maint.ovn_nbdb_api.db_set.assert_not_called()
|
||||
|
||||
@mock.patch('ovn_octavia_provider.common.clients.get_neutron_client')
|
||||
def test_format_ip_port_mappings_ipv6(self, net_cli):
|
||||
ovn_lbs = [
|
||||
fakes.FakeOVNLB.create_one_lb(
|
||||
attrs={
|
||||
'uuid': 'foo1',
|
||||
'ip_port_mappings': {
|
||||
'fda2:918e:5869:0:f816:3eff:fe64:adf7':
|
||||
'f2b97caf-da62-4db9-91da-bc11f2ac3934:'
|
||||
'fda2:918e:5869:0:f816:3eff:fe81:61d0',
|
||||
'fda2:918e:5869:0:f816:3eff:fe64:adf8':
|
||||
'f2b97caf-da62-4db9-91da-bc11f2ac3935:'
|
||||
'fda2:918e:5869:0:f816:3eff:fe81:61d0'}}),
|
||||
fakes.FakeOVNLB.create_one_lb(
|
||||
attrs={
|
||||
'uuid': 'foo2',
|
||||
'ip_port_mappings': {
|
||||
'192.168.1.50':
|
||||
'f2b97caf-da62-4db9-91da-bc11f2ac3934:'
|
||||
'192.168.1.3'}}),
|
||||
fakes.FakeOVNLB.create_one_lb(
|
||||
attrs={
|
||||
'uuid': 'foo3',
|
||||
'ip_port_mappings': {
|
||||
'[fda2:918e:5869:0:f816:3eff:fe64:adf7]':
|
||||
'f2b97caf-da62-4db9-91da-bc11f2ac3934:'
|
||||
'[fda2:918e:5869:0:f816:3eff:fe81:61d0]'}}),
|
||||
]
|
||||
|
||||
self.maint.ovn_nbdb_api.db_find_rows.return_value.\
|
||||
execute.return_value = ovn_lbs
|
||||
self.assertRaises(periodics.NeverAgain,
|
||||
self.maint.format_ip_port_mappings_ipv6)
|
||||
mapping1 = {
|
||||
'[fda2:918e:5869:0:f816:3eff:fe64:adf7]':
|
||||
'f2b97caf-da62-4db9-91da-bc11f2ac3934:'
|
||||
'[fda2:918e:5869:0:f816:3eff:fe81:61d0]',
|
||||
'[fda2:918e:5869:0:f816:3eff:fe64:adf8]':
|
||||
'f2b97caf-da62-4db9-91da-bc11f2ac3935:'
|
||||
'[fda2:918e:5869:0:f816:3eff:fe81:61d0]'}
|
||||
expected_call_db_clear = [
|
||||
mock.call('Load_Balancer', 'foo1', 'ip_port_mappings')]
|
||||
self.maint.ovn_nbdb_api.db_clear.assert_has_calls(
|
||||
expected_call_db_clear)
|
||||
expected_call_db_set = [
|
||||
mock.call('Load_Balancer', 'foo1', ('ip_port_mappings', mapping1))]
|
||||
self.maint.ovn_nbdb_api.db_set.assert_has_calls(
|
||||
expected_call_db_set)
|
||||
|
Loading…
Reference in New Issue
Block a user