# 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
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:
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
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):
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:
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')),
]
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():