diff --git a/neutron/common/exceptions.py b/neutron/common/exceptions.py index c7c57642d1f..0a892210708 100644 --- a/neutron/common/exceptions.py +++ b/neutron/common/exceptions.py @@ -308,5 +308,9 @@ class NetworkVxlanPortRangeError(NeutronException): message = _("Invalid network VXLAN port range: '%(vxlan_range)s'") +class VxlanNetworkUnsupported(NeutronException): + message = _("VXLAN Network unsupported.") + + class DuplicatedExtension(NeutronException): message = _("Found duplicate extension: %(alias)s") diff --git a/neutron/plugins/linuxbridge/agent/linuxbridge_neutron_agent.py b/neutron/plugins/linuxbridge/agent/linuxbridge_neutron_agent.py index 22e8c5d5f64..66b4427420d 100755 --- a/neutron/plugins/linuxbridge/agent/linuxbridge_neutron_agent.py +++ b/neutron/plugins/linuxbridge/agent/linuxbridge_neutron_agent.py @@ -22,9 +22,7 @@ # Neutron OpenVSwitch Plugin. # @author: Sumit Naiksatam, Cisco Systems, Inc. -import distutils.version as dist_version import os -import platform import sys import time @@ -38,6 +36,7 @@ from neutron.agent import rpc as agent_rpc from neutron.agent import securitygroups_rpc as sg_rpc from neutron.common import config as logging_config from neutron.common import constants +from neutron.common import exceptions from neutron.common import topics from neutron.common import utils as q_utils from neutron import context @@ -514,29 +513,74 @@ class LinuxBridgeManager: devices.add(device) return devices - def check_vxlan_support(self): - kernel_version = dist_version.LooseVersion(platform.release()) - if cfg.CONF.VXLAN.l2_population and ( - kernel_version > dist_version.LooseVersion( - lconst.MIN_VXLAN_KVER[lconst.VXLAN_UCAST])) and ( - ip_lib.iproute_arg_supported(['bridge', 'fdb'], - 'append', self.root_helper)): - self.vxlan_mode = lconst.VXLAN_UCAST - elif (kernel_version > dist_version.LooseVersion( - lconst.MIN_VXLAN_KVER[lconst.VXLAN_MCAST])) and ( - ip_lib.iproute_arg_supported(['ip', 'link', 'add', - 'type', 'vxlan'], 'proxy', - self.root_helper)): - if cfg.CONF.VXLAN.vxlan_group: - self.vxlan_mode = lconst.VXLAN_MCAST - else: - self.vxlan_mode = lconst.VXLAN_NONE - LOG.warning(_('VXLAN muticast group must be provided in ' - 'vxlan_group option to enable VXLAN')) + def vxlan_ucast_supported(self): + if not cfg.CONF.VXLAN.l2_population: + return False + if not ip_lib.iproute_arg_supported( + ['bridge', 'fdb'], 'append', self.root_helper): + LOG.warning(_('Option "%(option)s" must be supported by command ' + '"%(command)s" to enable %(mode)s mode') % + {'option': 'append', + 'command': 'bridge fdb', + 'mode': 'VXLAN UCAST'}) + return False + for segmentation_id in range(1, constants.MAX_VXLAN_VNI + 1): + if not self.device_exists( + self.get_vxlan_device_name(segmentation_id)): + break else: - self.vxlan_mode = lconst.VXLAN_NONE - LOG.warning(_('Unable to use VXLAN, it requires at least 3.8 ' - 'linux kernel and iproute2 3.8')) + LOG.error(_('No valid Segmentation ID to perform UCAST test.')) + return False + + test_iface = self.ensure_vxlan(segmentation_id) + try: + utils.execute( + cmd=['bridge', 'fdb', 'append', constants.FLOODING_ENTRY[0], + 'dev', test_iface, 'dst', '1.1.1.1'], + root_helper=self.root_helper) + return True + except RuntimeError: + return False + finally: + self.delete_vxlan(test_iface) + + def vxlan_mcast_supported(self): + if not cfg.CONF.VXLAN.vxlan_group: + LOG.warning(_('VXLAN muticast group must be provided in ' + 'vxlan_group option to enable VXLAN MCAST mode')) + return False + if not ip_lib.iproute_arg_supported( + ['ip', 'link', 'add', 'type', 'vxlan'], + 'proxy', self.root_helper): + LOG.warning(_('Option "%(option)s" must be supported by command ' + '"%(command)s" to enable %(mode)s mode') % + {'option': 'proxy', + 'command': 'ip link add type vxlan', + 'mode': 'VXLAN MCAST'}) + + return False + return True + + def vxlan_module_supported(self): + try: + utils.execute(cmd=['modinfo', 'vxlan']) + return True + except RuntimeError: + return False + + def check_vxlan_support(self): + self.vxlan_mode = lconst.VXLAN_NONE + if not self.vxlan_module_supported(): + LOG.error(_('Linux kernel vxlan module and iproute2 3.8 or above ' + 'are required to enable VXLAN.')) + raise exceptions.VxlanNetworkUnsupported() + + if self.vxlan_ucast_supported(): + self.vxlan_mode = lconst.VXLAN_UCAST + elif self.vxlan_mcast_supported(): + self.vxlan_mode = lconst.VXLAN_MCAST + else: + raise exceptions.VxlanNetworkUnsupported() LOG.debug(_('Using %s VXLAN mode'), self.vxlan_mode) def fdb_ip_entry_exists(self, mac, ip, interface): diff --git a/neutron/plugins/linuxbridge/common/constants.py b/neutron/plugins/linuxbridge/common/constants.py index 05180f592cc..6dee88f406c 100644 --- a/neutron/plugins/linuxbridge/common/constants.py +++ b/neutron/plugins/linuxbridge/common/constants.py @@ -28,9 +28,6 @@ VXLAN_NONE = 'not_supported' VXLAN_MCAST = 'multicast_flooding' VXLAN_UCAST = 'unicast_flooding' -# Corresponding minimal kernel versions requirements -MIN_VXLAN_KVER = {VXLAN_MCAST: '3.8', VXLAN_UCAST: '3.11'} - # TODO(rkukura): Eventually remove this function, which provides # temporary backward compatibility with pre-Havana RPC and DB vlan_id diff --git a/neutron/tests/unit/linuxbridge/test_lb_neutron_agent.py b/neutron/tests/unit/linuxbridge/test_lb_neutron_agent.py index 256707b5bb1..a15a1ee685f 100644 --- a/neutron/tests/unit/linuxbridge/test_lb_neutron_agent.py +++ b/neutron/tests/unit/linuxbridge/test_lb_neutron_agent.py @@ -24,6 +24,7 @@ import testtools from neutron.agent.linux import ip_lib from neutron.agent.linux import utils from neutron.common import constants +from neutron.common import exceptions from neutron.openstack.common.rpc import common as rpc_common from neutron.plugins.common import constants as p_const from neutron.plugins.linuxbridge.agent import linuxbridge_neutron_agent @@ -646,59 +647,107 @@ class TestLinuxBridgeManager(base.BaseTestCase): "removed": set(["dev3"]) }) - def _check_vxlan_support(self, kernel_version, vxlan_proxy_supported, - fdb_append_supported, l2_population, - expected_mode): - def iproute_supported_side_effect(*args): - if args[1] == 'proxy': - return vxlan_proxy_supported - elif args[1] == 'append': - return fdb_append_supported - + def _check_vxlan_support(self, expected, vxlan_module_supported, + vxlan_ucast_supported, vxlan_mcast_supported): with contextlib.nested( - mock.patch("platform.release", return_value=kernel_version), - mock.patch.object(ip_lib, 'iproute_arg_supported', - side_effect=iproute_supported_side_effect), - ) as (kver_fn, ip_arg_fn): - self.lbm.check_vxlan_support() - self.assertEqual(self.lbm.vxlan_mode, expected_mode) + mock.patch.object(self.lbm, 'vxlan_module_supported', + return_value=vxlan_module_supported), + mock.patch.object(self.lbm, 'vxlan_ucast_supported', + return_value=vxlan_ucast_supported), + mock.patch.object(self.lbm, 'vxlan_mcast_supported', + return_value=vxlan_mcast_supported)): + if expected == lconst.VXLAN_NONE: + self.assertRaises(exceptions.VxlanNetworkUnsupported, + self.lbm.check_vxlan_support) + self.assertEqual(expected, self.lbm.vxlan_mode) + else: + self.lbm.check_vxlan_support() + self.assertEqual(expected, self.lbm.vxlan_mode) - def test_vxlan_mode_ucast(self): - self._check_vxlan_support(kernel_version='3.12', - vxlan_proxy_supported=True, - fdb_append_supported=True, - l2_population=True, - expected_mode=lconst.VXLAN_MCAST) + def test_check_vxlan_support(self): + self._check_vxlan_support(expected=lconst.VXLAN_UCAST, + vxlan_module_supported=True, + vxlan_ucast_supported=True, + vxlan_mcast_supported=True) + self._check_vxlan_support(expected=lconst.VXLAN_MCAST, + vxlan_module_supported=True, + vxlan_ucast_supported=False, + vxlan_mcast_supported=True) - def test_vxlan_mode_mcast(self): - self._check_vxlan_support(kernel_version='3.12', - vxlan_proxy_supported=True, - fdb_append_supported=False, - l2_population=True, - expected_mode=lconst.VXLAN_MCAST) - self._check_vxlan_support(kernel_version='3.10', - vxlan_proxy_supported=True, - fdb_append_supported=True, - l2_population=True, - expected_mode=lconst.VXLAN_MCAST) + self._check_vxlan_support(expected=lconst.VXLAN_NONE, + vxlan_module_supported=False, + vxlan_ucast_supported=False, + vxlan_mcast_supported=False) + self._check_vxlan_support(expected=lconst.VXLAN_NONE, + vxlan_module_supported=True, + vxlan_ucast_supported=False, + vxlan_mcast_supported=False) - def test_vxlan_mode_unsupported(self): - self._check_vxlan_support(kernel_version='3.7', - vxlan_proxy_supported=True, - fdb_append_supported=True, - l2_population=False, - expected_mode=lconst.VXLAN_NONE) - self._check_vxlan_support(kernel_version='3.10', - vxlan_proxy_supported=False, - fdb_append_supported=False, - l2_population=False, - expected_mode=lconst.VXLAN_NONE) - cfg.CONF.set_override('vxlan_group', '', 'VXLAN') - self._check_vxlan_support(kernel_version='3.12', - vxlan_proxy_supported=True, - fdb_append_supported=True, - l2_population=True, - expected_mode=lconst.VXLAN_NONE) + def _check_vxlan_module_supported(self, expected, execute_side_effect): + with mock.patch.object( + utils, 'execute', + side_effect=execute_side_effect): + self.assertEqual(expected, self.lbm.vxlan_module_supported()) + + def test_vxlan_module_supported(self): + self._check_vxlan_module_supported( + expected=True, + execute_side_effect=None) + self._check_vxlan_module_supported( + expected=False, + execute_side_effect=RuntimeError()) + + def _check_vxlan_ucast_supported( + self, expected, l2_population, iproute_arg_supported, fdb_append): + cfg.CONF.set_override('l2_population', l2_population, 'VXLAN') + with contextlib.nested( + mock.patch.object( + self.lbm, 'device_exists', return_value=False), + mock.patch.object(self.lbm, 'delete_vxlan', return_value=None), + mock.patch.object(self.lbm, 'ensure_vxlan', return_value=None), + mock.patch.object( + utils, 'execute', + side_effect=None if fdb_append else RuntimeError()), + mock.patch.object( + ip_lib, 'iproute_arg_supported', + return_value=iproute_arg_supported)): + self.assertEqual(expected, self.lbm.vxlan_ucast_supported()) + + def test_vxlan_ucast_supported(self): + self._check_vxlan_ucast_supported( + expected=False, + l2_population=False, iproute_arg_supported=True, fdb_append=True) + self._check_vxlan_ucast_supported( + expected=False, + l2_population=True, iproute_arg_supported=False, fdb_append=True) + self._check_vxlan_ucast_supported( + expected=False, + l2_population=True, iproute_arg_supported=True, fdb_append=False) + self._check_vxlan_ucast_supported( + expected=True, + l2_population=True, iproute_arg_supported=True, fdb_append=True) + + def _check_vxlan_mcast_supported( + self, expected, vxlan_group, iproute_arg_supported): + cfg.CONF.set_override('vxlan_group', vxlan_group, 'VXLAN') + with mock.patch.object( + ip_lib, 'iproute_arg_supported', + return_value=iproute_arg_supported): + self.assertEqual(expected, self.lbm.vxlan_mcast_supported()) + + def test_vxlan_mcast_supported(self): + self._check_vxlan_mcast_supported( + expected=False, + vxlan_group='', + iproute_arg_supported=True) + self._check_vxlan_mcast_supported( + expected=False, + vxlan_group='224.0.0.1', + iproute_arg_supported=False) + self._check_vxlan_mcast_supported( + expected=True, + vxlan_group='224.0.0.1', + iproute_arg_supported=True) class TestLinuxBridgeRpcCallbacks(base.BaseTestCase):