From a04f44412bca2fce75ee32d16c71f6787b8a1682 Mon Sep 17 00:00:00 2001 From: sridhargaddam Date: Mon, 30 Mar 2015 05:11:01 +0000 Subject: [PATCH] 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 --- neutron/cmd/sanity/checks.py | 126 ++++++++++++++++++ neutron/cmd/sanity_check.py | 15 +++ .../tests/functional/sanity/test_sanity.py | 3 + 3 files changed, 144 insertions(+) diff --git a/neutron/cmd/sanity/checks.py b/neutron/cmd/sanity/checks.py index c31f5777dd0..3e90f45bcdf 100644 --- a/neutron/cmd/sanity/checks.py +++ b/neutron/cmd/sanity/checks.py @@ -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: diff --git a/neutron/cmd/sanity_check.py b/neutron/cmd/sanity_check.py index b49808cc96b..e3dcf9e2cb9 100644 --- a/neutron/cmd/sanity_check.py +++ b/neutron/cmd/sanity_check.py @@ -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(): diff --git a/neutron/tests/functional/sanity/test_sanity.py b/neutron/tests/functional/sanity/test_sanity.py index 55b0633f4fb..b65de687a5b 100644 --- a/neutron/tests/functional/sanity/test_sanity.py +++ b/neutron/tests/functional/sanity/test_sanity.py @@ -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()