]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Add sanity_check for keepalived ipv6 support
authorsridhargaddam <sridhar.gaddam@enovance.com>
Mon, 30 Mar 2015 05:11:01 +0000 (05:11 +0000)
committersridhargaddam <sridhar.gaddam@enovance.com>
Fri, 19 Jun 2015 09:11:01 +0000 (09:11 +0000)
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
neutron/cmd/sanity_check.py
neutron/tests/functional/sanity/test_sanity.py

index c31f5777dd0c8346fdb3f6ffea597fa3715d0524..3e90f45bcdfd5aa82b29c3900a6c1bb57da26c36 100644 (file)
 #    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:
index b49808cc96b6789d5fdbcbb58127dc1d74c8346a..e3dcf9e2cb942bb8012c3a845ff6b5962986b6f2 100644 (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():
index 55b0633f4fbf49aa160f3623371cb312feb2eddf..b65de687a5bb175ee4e1f68f3a6ae404d6a182ef 100644 (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()