Add sanity_check for keepalived ipv6 support

Currently Neutron does not validate the version of Keepalived.
In order to support configuring IPv6 default route, the minimum
version [1] of keepalived that is required is 1.2.10
This patch validates the required support in keepalived.

Although keepalived changelog mentions that IPv6 virtual_routes and
static_routes are supported in version 1.2.8, it is seen that in
v1.2.8 IPv6 default route is not supported. Support for default route
is added in keepalived v1.2.10. Observations on v1.2.8 are captured
at the following link - http://paste.openstack.org/show/165403/

[1] - http://www.keepalived.org/changelog.html

Closes-Bug: #1415756
Change-Id: I768ae233d2c2e24df93a4736ef6d8f4005770fa5
This commit is contained in:
sridhargaddam 2015-03-30 05:11:01 +00:00
parent 345ffb99ef
commit a04f44412b
3 changed files with 144 additions and 0 deletions

View File

@ -14,15 +14,23 @@
# under the License.
import re
import shutil
import tempfile
import netaddr
from oslo_config import cfg
from oslo_log import log as logging
import six
from neutron.agent.common import ovs_lib
from neutron.agent.l3 import ha_router
from neutron.agent.l3 import namespaces
from neutron.agent.linux import external_process
from neutron.agent.linux import ip_lib
from neutron.agent.linux import ip_link_support
from neutron.agent.linux import keepalived
from neutron.agent.linux import utils as agent_utils
from neutron.common import constants as n_consts
from neutron.common import utils
from neutron.i18n import _LE
from neutron.openstack.common import uuidutils
@ -165,6 +173,124 @@ def dnsmasq_version_supported():
return True
class KeepalivedIPv6Test(object):
def __init__(self, ha_port, gw_port, gw_vip, default_gw):
self.ha_port = ha_port
self.gw_port = gw_port
self.gw_vip = gw_vip
self.default_gw = default_gw
self.manager = None
self.config = None
self.config_path = None
self.nsname = "keepalivedtest-" + uuidutils.generate_uuid()
self.pm = external_process.ProcessMonitor(cfg.CONF, 'router')
self.orig_interval = cfg.CONF.AGENT.check_child_processes_interval
def configure(self):
config = keepalived.KeepalivedConf()
instance1 = keepalived.KeepalivedInstance('MASTER', self.ha_port, 1,
['169.254.192.0/18'],
advert_int=5)
instance1.track_interfaces.append(self.ha_port)
# Configure keepalived with an IPv6 address (gw_vip) on gw_port.
vip_addr1 = keepalived.KeepalivedVipAddress(self.gw_vip, self.gw_port)
instance1.vips.append(vip_addr1)
# Configure keepalived with an IPv6 default route on gw_port.
gateway_route = keepalived.KeepalivedVirtualRoute(n_consts.IPv6_ANY,
self.default_gw,
self.gw_port)
instance1.virtual_routes.gateway_routes = [gateway_route]
config.add_instance(instance1)
self.config = config
def start_keepalived_process(self):
# Disable process monitoring for Keepalived process.
cfg.CONF.set_override('check_child_processes_interval', 0, 'AGENT')
# Create a temp directory to store keepalived configuration.
self.config_path = tempfile.mkdtemp()
# Instantiate keepalived manager with the IPv6 configuration.
self.manager = keepalived.KeepalivedManager('router1', self.config,
namespace=self.nsname, process_monitor=self.pm,
conf_path=self.config_path)
self.manager.spawn()
def verify_ipv6_address_assignment(self, gw_dev):
process = self.manager.get_process()
agent_utils.wait_until_true(lambda: process.active)
def _gw_vip_assigned():
iface_ip = gw_dev.addr.list(ip_version=6, scope='global')
if iface_ip:
return self.gw_vip == iface_ip[0]['cidr']
agent_utils.wait_until_true(_gw_vip_assigned)
def __enter__(self):
ip_lib.IPWrapper().netns.add(self.nsname)
return self
def __exit__(self, exc_type, exc_value, exc_tb):
self.pm.stop()
if self.manager:
self.manager.disable()
if self.config_path:
shutil.rmtree(self.config_path, ignore_errors=True)
ip_lib.IPWrapper().netns.delete(self.nsname)
cfg.CONF.set_override('check_child_processes_interval',
self.orig_interval, 'AGENT')
def keepalived_ipv6_supported():
"""Check if keepalived supports IPv6 functionality.
Validation is done as follows.
1. Create a namespace.
2. Create OVS bridge with two ports (ha_port and gw_port)
3. Move the ovs ports to the namespace.
4. Spawn keepalived process inside the namespace with IPv6 configuration.
5. Verify if IPv6 address is assigned to gw_port.
6. Verify if IPv6 default route is configured by keepalived.
"""
random_str = utils.get_random_string(6)
br_name = "ka-test-" + random_str
ha_port = ha_router.HA_DEV_PREFIX + random_str
gw_port = namespaces.INTERNAL_DEV_PREFIX + random_str
gw_vip = 'fdf8:f53b:82e4::10/64'
expected_default_gw = 'fe80:f816::1'
with ovs_lib.OVSBridge(br_name) as br:
with KeepalivedIPv6Test(ha_port, gw_port, gw_vip,
expected_default_gw) as ka:
br.add_port(ha_port, ('type', 'internal'))
br.add_port(gw_port, ('type', 'internal'))
ha_dev = ip_lib.IPDevice(ha_port)
gw_dev = ip_lib.IPDevice(gw_port)
ha_dev.link.set_netns(ka.nsname)
gw_dev.link.set_netns(ka.nsname)
ha_dev.link.set_up()
gw_dev.link.set_up()
ka.configure()
ka.start_keepalived_process()
ka.verify_ipv6_address_assignment(gw_dev)
default_gw = gw_dev.route.get_gateway(ip_version=6)
if default_gw:
default_gw = default_gw['gateway']
return expected_default_gw == default_gw
def ovsdb_native_supported():
# Running the test should ensure we are configured for OVSDB native
try:

View File

@ -21,6 +21,7 @@ from oslo_log import log as logging
from neutron.agent import dhcp_agent
from neutron.cmd.sanity import checks
from neutron.common import config
from neutron.db import l3_hamode_db
from neutron.i18n import _LE, _LW
@ -32,6 +33,7 @@ cfg.CONF.import_group('ml2', 'neutron.plugins.ml2.config')
cfg.CONF.import_group('ml2_sriov',
'neutron.plugins.ml2.drivers.mech_sriov.mech_driver')
dhcp_agent.register_options()
cfg.CONF.register_opts(l3_hamode_db.L3_HA_OPTS)
class BoolOptCallback(cfg.BoolOpt):
@ -102,6 +104,15 @@ def check_dnsmasq_version():
return result
def check_keepalived_ipv6_support():
result = checks.keepalived_ipv6_supported()
if not result:
LOG.error(_LE('The installed version of keepalived does not support '
'IPv6. Please update to at least version 1.2.10 for '
'IPv6 support.'))
return result
def check_nova_notify():
result = checks.nova_notify_supported()
if not result:
@ -178,6 +189,8 @@ OPTS = [
help=_('Check ovsdb native interface support')),
BoolOptCallback('ebtables_installed', check_ebtables,
help=_('Check ebtables installation')),
BoolOptCallback('keepalived_ipv6_support', check_keepalived_ipv6_support,
help=_('Check keepalived IPv6 support')),
]
@ -211,6 +224,8 @@ def enable_tests_from_config():
cfg.CONF.set_override('dnsmasq_version', True)
if cfg.CONF.OVS.ovsdb_interface == 'native':
cfg.CONF.set_override('ovsdb_native', True)
if cfg.CONF.l3_ha:
cfg.CONF.set_override('keepalived_ipv6_support', True)
def all_tests_passed():

View File

@ -67,3 +67,6 @@ class SanityTestCaseRoot(functional_base.BaseSudoTestCase):
def test_ovsdb_native_supported_runs(self):
checks.ovsdb_native_supported()
def test_keepalived_ipv6_support(self):
checks.keepalived_ipv6_supported()