[L3 HA] Add "no_track" option to VIPs in keepalived config
Patch [1] introduced new mechanism which only brings UP interfaces on master node of HA router. It works fine with keepalived 1.x but it is broken when keepalived 2.x was used (e.g. on Centos 8) as in this new version of keepalived by default all interfaces of VIPs and routes are tracked, and if one of them is DOWN, keepalived is going to FAULT state. Because of that router will never be transitioned to MASTER on any node. This patch fixes it by adding "no_track" option to all VIPs and routes in keepalived's config file. This "no_track" option isn't added to ha interface so this one is still tracked by keepalived. [1] https://review.opendev.org/#/c/707406/ Closes-bug: #1874211 Change-Id: Ic16cf83fe1d1576d91047adb2d4f9e07d57185b6
This commit is contained in:
parent
7f8b6933be
commit
dc9084a8ec
|
@ -85,24 +85,28 @@ class InvalidAuthenticationTypeException(exceptions.NeutronException):
|
||||||
class KeepalivedVipAddress(object):
|
class KeepalivedVipAddress(object):
|
||||||
"""A virtual address entry of a keepalived configuration."""
|
"""A virtual address entry of a keepalived configuration."""
|
||||||
|
|
||||||
def __init__(self, ip_address, interface_name, scope=None):
|
def __init__(self, ip_address, interface_name, scope=None, track=True):
|
||||||
self.ip_address = ip_address
|
self.ip_address = ip_address
|
||||||
self.interface_name = interface_name
|
self.interface_name = interface_name
|
||||||
self.scope = scope
|
self.scope = scope
|
||||||
|
self.track = track
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return (isinstance(other, KeepalivedVipAddress) and
|
return (isinstance(other, KeepalivedVipAddress) and
|
||||||
self.ip_address == other.ip_address)
|
self.ip_address == other.ip_address)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '[%s, %s, %s]' % (self.ip_address,
|
return '[%s, %s, %s, %s]' % (self.ip_address,
|
||||||
self.interface_name,
|
self.interface_name,
|
||||||
self.scope)
|
self.scope,
|
||||||
|
self.track)
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
result = '%s dev %s' % (self.ip_address, self.interface_name)
|
result = '%s dev %s' % (self.ip_address, self.interface_name)
|
||||||
if self.scope:
|
if self.scope:
|
||||||
result += ' scope %s' % self.scope
|
result += ' scope %s' % self.scope
|
||||||
|
if not self.track:
|
||||||
|
result += ' no_track'
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -124,6 +128,7 @@ class KeepalivedVirtualRoute(object):
|
||||||
output += ' dev %s' % self.interface_name
|
output += ' dev %s' % self.interface_name
|
||||||
if self.scope:
|
if self.scope:
|
||||||
output += ' scope %s' % self.scope
|
output += ' scope %s' % self.scope
|
||||||
|
output += ' no_track'
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
@ -200,7 +205,8 @@ class KeepalivedInstance(object):
|
||||||
self.authentication = (auth_type, password)
|
self.authentication = (auth_type, password)
|
||||||
|
|
||||||
def add_vip(self, ip_cidr, interface_name, scope):
|
def add_vip(self, ip_cidr, interface_name, scope):
|
||||||
vip = KeepalivedVipAddress(ip_cidr, interface_name, scope)
|
track = interface_name in self.track_interfaces
|
||||||
|
vip = KeepalivedVipAddress(ip_cidr, interface_name, scope, track=track)
|
||||||
if vip not in self.vips:
|
if vip not in self.vips:
|
||||||
self.vips.append(vip)
|
self.vips.append(vip)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import functools
|
import functools
|
||||||
import textwrap
|
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
import netaddr
|
import netaddr
|
||||||
|
@ -47,6 +46,39 @@ _uuid = uuidutils.generate_uuid
|
||||||
|
|
||||||
OVS_INTERFACE_DRIVER = 'neutron.agent.linux.interface.OVSInterfaceDriver'
|
OVS_INTERFACE_DRIVER = 'neutron.agent.linux.interface.OVSInterfaceDriver'
|
||||||
|
|
||||||
|
KEEPALIVED_CONFIG = """\
|
||||||
|
global_defs {
|
||||||
|
notification_email_from %(email_from)s
|
||||||
|
router_id %(router_id)s
|
||||||
|
}
|
||||||
|
vrrp_instance VR_1 {
|
||||||
|
state BACKUP
|
||||||
|
interface %(ha_device_name)s
|
||||||
|
virtual_router_id 1
|
||||||
|
priority 50
|
||||||
|
garp_master_delay 60
|
||||||
|
nopreempt
|
||||||
|
advert_int 2
|
||||||
|
track_interface {
|
||||||
|
%(ha_device_name)s
|
||||||
|
}
|
||||||
|
virtual_ipaddress {
|
||||||
|
169.254.0.1/24 dev %(ha_device_name)s
|
||||||
|
}
|
||||||
|
virtual_ipaddress_excluded {
|
||||||
|
%(floating_ip_cidr)s dev %(ex_device_name)s no_track
|
||||||
|
%(external_device_cidr)s dev %(ex_device_name)s no_track
|
||||||
|
%(internal_device_cidr)s dev %(internal_device_name)s no_track
|
||||||
|
%(ex_port_ipv6)s dev %(ex_device_name)s scope link no_track
|
||||||
|
%(int_port_ipv6)s dev %(internal_device_name)s scope link no_track
|
||||||
|
}
|
||||||
|
virtual_routes {
|
||||||
|
0.0.0.0/0 via %(default_gateway_ip)s dev %(ex_device_name)s no_track
|
||||||
|
8.8.8.0/24 via 19.4.4.4 no_track
|
||||||
|
%(extra_subnet_cidr)s dev %(ex_device_name)s scope link no_track
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
|
||||||
|
|
||||||
def get_ovs_bridge(br_name):
|
def get_ovs_bridge(br_name):
|
||||||
return ovs_lib.OVSBridge(br_name)
|
return ovs_lib.OVSBridge(br_name)
|
||||||
|
@ -442,38 +474,7 @@ class L3AgentTestFramework(base.BaseSudoTestCase):
|
||||||
router.get_floating_ips()[0]['floating_ip_address'])
|
router.get_floating_ips()[0]['floating_ip_address'])
|
||||||
default_gateway_ip = external_port['subnets'][0].get('gateway_ip')
|
default_gateway_ip = external_port['subnets'][0].get('gateway_ip')
|
||||||
extra_subnet_cidr = external_port['extra_subnets'][0].get('cidr')
|
extra_subnet_cidr = external_port['extra_subnets'][0].get('cidr')
|
||||||
return textwrap.dedent("""\
|
return KEEPALIVED_CONFIG % {
|
||||||
global_defs {
|
|
||||||
notification_email_from %(email_from)s
|
|
||||||
router_id %(router_id)s
|
|
||||||
}
|
|
||||||
vrrp_instance VR_1 {
|
|
||||||
state BACKUP
|
|
||||||
interface %(ha_device_name)s
|
|
||||||
virtual_router_id 1
|
|
||||||
priority 50
|
|
||||||
garp_master_delay 60
|
|
||||||
nopreempt
|
|
||||||
advert_int 2
|
|
||||||
track_interface {
|
|
||||||
%(ha_device_name)s
|
|
||||||
}
|
|
||||||
virtual_ipaddress {
|
|
||||||
169.254.0.1/24 dev %(ha_device_name)s
|
|
||||||
}
|
|
||||||
virtual_ipaddress_excluded {
|
|
||||||
%(floating_ip_cidr)s dev %(ex_device_name)s
|
|
||||||
%(external_device_cidr)s dev %(ex_device_name)s
|
|
||||||
%(internal_device_cidr)s dev %(internal_device_name)s
|
|
||||||
%(ex_port_ipv6)s dev %(ex_device_name)s scope link
|
|
||||||
%(int_port_ipv6)s dev %(internal_device_name)s scope link
|
|
||||||
}
|
|
||||||
virtual_routes {
|
|
||||||
0.0.0.0/0 via %(default_gateway_ip)s dev %(ex_device_name)s
|
|
||||||
8.8.8.0/24 via 19.4.4.4
|
|
||||||
%(extra_subnet_cidr)s dev %(ex_device_name)s scope link
|
|
||||||
}
|
|
||||||
}""") % {
|
|
||||||
'email_from': keepalived.KEEPALIVED_EMAIL_FROM,
|
'email_from': keepalived.KEEPALIVED_EMAIL_FROM,
|
||||||
'router_id': keepalived.KEEPALIVED_ROUTER_ID,
|
'router_id': keepalived.KEEPALIVED_ROUTER_ID,
|
||||||
'ha_device_name': ha_device_name,
|
'ha_device_name': ha_device_name,
|
||||||
|
|
|
@ -83,16 +83,16 @@ class KeepalivedConfBaseMixin(object):
|
||||||
instance1.track_interfaces.append("eth0")
|
instance1.track_interfaces.append("eth0")
|
||||||
|
|
||||||
vip_address1 = keepalived.KeepalivedVipAddress('192.168.1.0/24',
|
vip_address1 = keepalived.KeepalivedVipAddress('192.168.1.0/24',
|
||||||
'eth1')
|
'eth1', track=False)
|
||||||
|
|
||||||
vip_address2 = keepalived.KeepalivedVipAddress('192.168.2.0/24',
|
vip_address2 = keepalived.KeepalivedVipAddress('192.168.2.0/24',
|
||||||
'eth2')
|
'eth2', track=False)
|
||||||
|
|
||||||
vip_address3 = keepalived.KeepalivedVipAddress('192.168.3.0/24',
|
vip_address3 = keepalived.KeepalivedVipAddress('192.168.3.0/24',
|
||||||
'eth2')
|
'eth2', track=False)
|
||||||
|
|
||||||
vip_address_ex = keepalived.KeepalivedVipAddress('192.168.55.0/24',
|
vip_address_ex = keepalived.KeepalivedVipAddress('192.168.55.0/24',
|
||||||
'eth10')
|
'eth10', track=False)
|
||||||
|
|
||||||
instance1.vips.append(vip_address1)
|
instance1.vips.append(vip_address1)
|
||||||
instance1.vips.append(vip_address2)
|
instance1.vips.append(vip_address2)
|
||||||
|
@ -110,7 +110,7 @@ class KeepalivedConfBaseMixin(object):
|
||||||
instance2.track_interfaces.append("eth4")
|
instance2.track_interfaces.append("eth4")
|
||||||
|
|
||||||
vip_address1 = keepalived.KeepalivedVipAddress('192.168.3.0/24',
|
vip_address1 = keepalived.KeepalivedVipAddress('192.168.3.0/24',
|
||||||
'eth6')
|
'eth6', track=False)
|
||||||
|
|
||||||
instance2.vips.append(vip_address1)
|
instance2.vips.append(vip_address1)
|
||||||
instance2.vips.append(vip_address2)
|
instance2.vips.append(vip_address2)
|
||||||
|
@ -144,13 +144,13 @@ class KeepalivedConfTestCase(base.BaseTestCase,
|
||||||
169.254.0.1/24 dev eth0
|
169.254.0.1/24 dev eth0
|
||||||
}
|
}
|
||||||
virtual_ipaddress_excluded {
|
virtual_ipaddress_excluded {
|
||||||
192.168.1.0/24 dev eth1
|
192.168.1.0/24 dev eth1 no_track
|
||||||
192.168.2.0/24 dev eth2
|
192.168.2.0/24 dev eth2 no_track
|
||||||
192.168.3.0/24 dev eth2
|
192.168.3.0/24 dev eth2 no_track
|
||||||
192.168.55.0/24 dev eth10
|
192.168.55.0/24 dev eth10 no_track
|
||||||
}
|
}
|
||||||
virtual_routes {
|
virtual_routes {
|
||||||
0.0.0.0/0 via 192.168.1.1 dev eth1
|
0.0.0.0/0 via 192.168.1.1 dev eth1 no_track
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vrrp_instance VR_2 {
|
vrrp_instance VR_2 {
|
||||||
|
@ -167,9 +167,9 @@ class KeepalivedConfTestCase(base.BaseTestCase,
|
||||||
169.254.0.2/24 dev eth4
|
169.254.0.2/24 dev eth4
|
||||||
}
|
}
|
||||||
virtual_ipaddress_excluded {
|
virtual_ipaddress_excluded {
|
||||||
192.168.2.0/24 dev eth2
|
192.168.2.0/24 dev eth2 no_track
|
||||||
192.168.3.0/24 dev eth6
|
192.168.3.0/24 dev eth6 no_track
|
||||||
192.168.55.0/24 dev eth10
|
192.168.55.0/24 dev eth10 no_track
|
||||||
}
|
}
|
||||||
}""")
|
}""")
|
||||||
|
|
||||||
|
@ -239,11 +239,11 @@ class KeepalivedInstanceRoutesTestCase(base.BaseTestCase):
|
||||||
|
|
||||||
def test_build_config(self):
|
def test_build_config(self):
|
||||||
expected = """ virtual_routes {
|
expected = """ virtual_routes {
|
||||||
0.0.0.0/0 via 1.0.0.254 dev eth0
|
0.0.0.0/0 via 1.0.0.254 dev eth0 no_track
|
||||||
::/0 via fe80::3e97:eff:fe26:3bfa/64 dev eth1
|
::/0 via fe80::3e97:eff:fe26:3bfa/64 dev eth1 no_track
|
||||||
10.0.0.0/8 via 1.0.0.1
|
10.0.0.0/8 via 1.0.0.1 no_track
|
||||||
20.0.0.0/8 via 2.0.0.2
|
20.0.0.0/8 via 2.0.0.2 no_track
|
||||||
30.0.0.0/8 dev eth0 scope link
|
30.0.0.0/8 dev eth0 scope link no_track
|
||||||
}"""
|
}"""
|
||||||
routes = self._get_instance_routes()
|
routes = self._get_instance_routes()
|
||||||
self.assertEqual(expected, '\n'.join(routes.build_config()))
|
self.assertEqual(expected, '\n'.join(routes.build_config()))
|
||||||
|
@ -281,10 +281,10 @@ class KeepalivedInstanceTestCase(base.BaseTestCase,
|
||||||
169.254.0.1/24 dev eth0
|
169.254.0.1/24 dev eth0
|
||||||
}
|
}
|
||||||
virtual_ipaddress_excluded {
|
virtual_ipaddress_excluded {
|
||||||
192.168.1.0/24 dev eth1
|
192.168.1.0/24 dev eth1 no_track
|
||||||
}
|
}
|
||||||
virtual_routes {
|
virtual_routes {
|
||||||
0.0.0.0/0 via 192.168.1.1 dev eth1
|
0.0.0.0/0 via 192.168.1.1 dev eth1 no_track
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vrrp_instance VR_2 {
|
vrrp_instance VR_2 {
|
||||||
|
@ -301,9 +301,9 @@ class KeepalivedInstanceTestCase(base.BaseTestCase,
|
||||||
169.254.0.2/24 dev eth4
|
169.254.0.2/24 dev eth4
|
||||||
}
|
}
|
||||||
virtual_ipaddress_excluded {
|
virtual_ipaddress_excluded {
|
||||||
192.168.2.0/24 dev eth2
|
192.168.2.0/24 dev eth2 no_track
|
||||||
192.168.3.0/24 dev eth6
|
192.168.3.0/24 dev eth6 no_track
|
||||||
192.168.55.0/24 dev eth10
|
192.168.55.0/24 dev eth10 no_track
|
||||||
}
|
}
|
||||||
}""")
|
}""")
|
||||||
|
|
||||||
|
@ -371,12 +371,13 @@ class KeepalivedVirtualRouteTestCase(base.BaseTestCase):
|
||||||
def test_virtual_route_with_dev(self):
|
def test_virtual_route_with_dev(self):
|
||||||
route = keepalived.KeepalivedVirtualRoute(n_consts.IPv4_ANY, '1.2.3.4',
|
route = keepalived.KeepalivedVirtualRoute(n_consts.IPv4_ANY, '1.2.3.4',
|
||||||
'eth0')
|
'eth0')
|
||||||
self.assertEqual('0.0.0.0/0 via 1.2.3.4 dev eth0',
|
self.assertEqual('0.0.0.0/0 via 1.2.3.4 dev eth0 no_track',
|
||||||
route.build_config())
|
route.build_config())
|
||||||
|
|
||||||
def test_virtual_route_without_dev(self):
|
def test_virtual_route_without_dev(self):
|
||||||
route = keepalived.KeepalivedVirtualRoute('50.0.0.0/8', '1.2.3.4')
|
route = keepalived.KeepalivedVirtualRoute('50.0.0.0/8', '1.2.3.4')
|
||||||
self.assertEqual('50.0.0.0/8 via 1.2.3.4', route.build_config())
|
self.assertEqual('50.0.0.0/8 via 1.2.3.4 no_track',
|
||||||
|
route.build_config())
|
||||||
|
|
||||||
|
|
||||||
class KeepalivedTrackScriptTestCase(base.BaseTestCase):
|
class KeepalivedTrackScriptTestCase(base.BaseTestCase):
|
||||||
|
|
Loading…
Reference in New Issue