Remove run-time version checking for openvswitch features

The current method of checking openvswitch and kernel versions for
specific feature support is brittle, distro-specific and unsupportable.

This patch removes the runtime version checks and implements a test
script which allows testing specific neutron features or can test
that all features required by a specific configuration are available.

For example, to test VXLAN support in openvswitch, either:

  neutron-sanity-check --ovs_vxlan

or pass in the deployed configuration files with AGENT/tunnel_types
containing 'vxlan', like:

  neutron-sanity-check --config-file /etc/neutron/plugins/ml2.conf.ini

Deployment tools can then test that the required features exist without
relying on version numbers and without incurring a penalty every time
the agent is started.

Implements: blueprint remove-openvswitch-version-check
DocImpact
Change-Id: I5e822d97b9bece9242ba5ac8901aa0ca4f090cfe
This commit is contained in:
Terry Wilson 2014-04-16 21:48:04 -04:00
parent b33ecb3d16
commit ab4dc5261e
14 changed files with 191 additions and 260 deletions

View File

@ -13,10 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import distutils.version as dist_version
import os
import re
from oslo.config import cfg
from neutron.agent.linux import ip_lib
@ -176,7 +172,14 @@ class OVSBridge(BaseOVS):
self.run_ofctl("del-flows", [])
def get_port_ofport(self, port_name):
return self.db_get_val("Interface", port_name, "ofport")
ofport = self.db_get_val("Interface", port_name, "ofport")
# This can return a non-integer string, like '[]' so ensure a
# common failure case
try:
int(ofport)
return ofport
except ValueError:
return constants.INVALID_OFPORT
def get_datapath_id(self):
return self.db_get_val('Bridge',
@ -248,7 +251,13 @@ class OVSBridge(BaseOVS):
"options:in_key=flow",
"options:out_key=flow"])
self.run_vsctl(vsctl_command)
return self.get_port_ofport(port_name)
ofport = self.get_port_ofport(port_name)
if (tunnel_type == p_const.TYPE_VXLAN and
ofport == constants.INVALID_OFPORT):
LOG.error(_('Unable to create VXLAN tunnel port. Please ensure '
'that an openvswitch version that supports VXLAN is '
'installed.'))
return ofport
def add_patch_port(self, local_name, remote_name):
self.run_vsctl(["add-port", self.br_name, local_name,
@ -451,6 +460,13 @@ class OVSBridge(BaseOVS):
msg = _('Unable to determine mac address for %s') % self.br_name
raise Exception(msg)
def __enter__(self):
self.create()
return self
def __exit__(self, exc_type, exc_value, exc_tb):
self.destroy()
def get_bridge_for_iface(root_helper, iface):
args = ["ovs-vsctl", "--timeout=%d" % cfg.CONF.ovs_vsctl_timeout,
@ -472,35 +488,6 @@ def get_bridges(root_helper):
LOG.exception(_("Unable to retrieve bridges. Exception: %s"), e)
def get_installed_ovs_usr_version(root_helper):
args = ["ovs-vsctl", "--version"]
try:
cmd = utils.execute(args, root_helper=root_helper)
ver = re.findall("\d+\.\d+", cmd)[0]
return ver
except Exception:
LOG.exception(_("Unable to retrieve OVS userspace version."))
def get_installed_ovs_klm_version():
args = ["modinfo", "openvswitch"]
try:
cmd = utils.execute(args)
for line in cmd.split('\n'):
if 'version: ' in line and not 'srcversion' in line:
ver = re.findall("\d+\.\d+", line)
return ver[0]
except Exception:
LOG.exception(_("Unable to retrieve OVS kernel module version."))
def get_installed_kernel_version():
try:
return os.uname()[2].split('-', 1)[0]
except IndexError:
LOG.exception(_("Unable to retrieve installed Linux kernel version."))
def get_bridge_external_bridge_id(root_helper, bridge):
args = ["ovs-vsctl", "--timeout=2", "br-get-external-id",
bridge, "bridge-id"]
@ -511,57 +498,6 @@ def get_bridge_external_bridge_id(root_helper, bridge):
return None
def _compare_installed_and_required_version(
installed_kernel_version, installed_version, required_version,
check_type, version_type):
if installed_kernel_version:
if dist_version.StrictVersion(
installed_kernel_version) >= dist_version.StrictVersion(
constants.MINIMUM_LINUX_KERNEL_OVS_VXLAN):
return
if installed_version:
if dist_version.StrictVersion(
installed_version) < dist_version.StrictVersion(
required_version):
msg = (_('Failed %(ctype)s version check for Open '
'vSwitch with %(vtype)s support. To use '
'%(vtype)s tunnels with OVS, please ensure '
'the OVS version is %(required)s or newer!') %
{'ctype': check_type, 'vtype': version_type,
'required': required_version})
raise SystemError(msg)
else:
msg = (_('Unable to determine %(ctype)s version for Open '
'vSwitch with %(vtype)s support. To use '
'%(vtype)s tunnels with OVS, please ensure '
'that the version is %(required)s or newer!') %
{'ctype': check_type, 'vtype': version_type,
'required': required_version})
raise SystemError(msg)
def check_ovs_vxlan_version(root_helper):
min_required_version = constants.MINIMUM_OVS_VXLAN_VERSION
installed_klm_version = get_installed_ovs_klm_version()
installed_kernel_version = get_installed_kernel_version()
installed_usr_version = get_installed_ovs_usr_version(root_helper)
LOG.debug(_("Checking OVS version for VXLAN support "
"installed klm version is %(klm)s, installed Linux version is "
"%(kernel)s, installed user version is %(usr)s ") %
{'klm': installed_klm_version,
'kernel': installed_kernel_version,
'usr': installed_usr_version})
# First check the userspace version
_compare_installed_and_required_version(None, installed_usr_version,
min_required_version,
'userspace', 'VXLAN')
# Now check the kernel version
_compare_installed_and_required_version(installed_kernel_version,
installed_klm_version,
min_required_version,
'kernel', 'VXLAN')
def _build_flow_expr_str(flow_dict, cmd):
flow_expr_arr = []
actions = None

View File

View File

@ -0,0 +1,28 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2014 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron.agent.linux import ovs_lib
from neutron.common import utils
from neutron.plugins.common import constants as const
from neutron.plugins.openvswitch.common import constants as ovs_const
def vxlan_supported(root_helper, from_ip='192.0.2.1', to_ip='192.0.2.2'):
name = "vxlantest-" + utils.get_random_string(6)
with ovs_lib.OVSBridge(name, root_helper) as br:
port = br.add_tunnel_port(from_ip, to_ip, const.TYPE_VXLAN)
return port != ovs_const.INVALID_OFPORT

View File

@ -0,0 +1,79 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2014 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
from neutron.cmd.sanity import checks
from neutron.common import config
from neutron.openstack.common import log as logging
from oslo.config import cfg
LOG = logging.getLogger(__name__)
cfg.CONF.import_group('AGENT', 'neutron.plugins.openvswitch.common.config')
class BoolOptCallback(cfg.BoolOpt):
def __init__(self, name, callback, **kwargs):
self.callback = callback
super(BoolOptCallback, self).__init__(name, **kwargs)
def check_ovs_vxlan():
result = checks.vxlan_supported(root_helper=cfg.CONF.AGENT.root_helper)
if not result:
LOG.error(_('Check for Open vSwitch VXLAN support failed. '
'Please ensure that the version of openvswitch '
'being used has VXLAN support.'))
return result
# Define CLI opts to test specific features, with a calback for the test
OPTS = [
BoolOptCallback('ovs_vxlan', check_ovs_vxlan, default=False,
help=_('Check for vxlan support')),
]
def enable_tests_from_config():
"""If a test can depend on configuration, use this function to set the
appropriate CLI option to enable that test. It will then be possible to
run all necessary tests, just by passing in the appropriate configs.
"""
if 'vxlan' in cfg.CONF.AGENT.tunnel_types:
cfg.CONF.set_override('ovs_vxlan', True)
def all_tests_passed():
res = True
for opt in OPTS:
if cfg.CONF.get(opt.name):
res &= opt.callback()
return res
def main():
cfg.CONF.register_cli_opts(OPTS)
cfg.CONF.set_override('use_stderr', True)
config.setup_logging(cfg.CONF)
config.parse(sys.argv[1:], default_config_files=[])
if cfg.CONF.config_file:
enable_tests_from_config()
return 0 if all_tests_passed() else 1

View File

@ -133,9 +133,10 @@ db_options.set_defaults(sql_connection=_SQL_CONNECTION_DEFAULT,
max_overflow=20, pool_timeout=10)
def parse(args):
def parse(args, **kwargs):
cfg.CONF(args=args, project='neutron',
version='%%prog %s' % version.version_info.release_string())
version='%%prog %s' % version.version_info.release_string(),
**kwargs)
# Validate that the base_mac is of the correct format
msg = attributes._validate_regex(cfg.CONF.base_mac,

View File

@ -267,7 +267,6 @@ class OFANeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
self.local_ip = local_ip
self.tunnel_count = 0
self.vxlan_udp_port = cfg.CONF.AGENT.vxlan_udp_port
self._check_ovs_version()
if self.enable_tunneling:
self.setup_tunnel_br(tun_br)
# Collect additional bridges to monitor
@ -280,14 +279,6 @@ class OFANeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
# Initialize iteration counter
self.iter_num = 0
def _check_ovs_version(self):
if p_const.TYPE_VXLAN in self.tunnel_types:
try:
ovs_lib.check_ovs_vxlan_version(self.root_helper)
except SystemError:
LOG.exception(_("Agent terminated"))
raise SystemExit(1)
def _report_state(self):
# How many devices are likely used by a VM
self.agent_state.get('configurations')['devices'] = (

View File

@ -223,7 +223,6 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
self.local_ip = local_ip
self.tunnel_count = 0
self.vxlan_udp_port = cfg.CONF.AGENT.vxlan_udp_port
self._check_ovs_version()
self.tun_br = None
if self.enable_tunneling:
self.setup_tunnel_br(tun_br)
@ -237,14 +236,6 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
# Initialize iteration counter
self.iter_num = 0
def _check_ovs_version(self):
if p_const.TYPE_VXLAN in self.tunnel_types:
try:
ovs_lib.check_ovs_vxlan_version(self.root_helper)
except SystemError:
LOG.exception(_("Agent terminated"))
raise SystemExit(1)
def _check_arp_responder_support(self):
'''Check if OVS supports to modify ARP headers.

View File

@ -30,12 +30,6 @@ VXLAN_UDP_PORT = 4789
VETH_INTEGRATION_PREFIX = 'int-'
VETH_PHYSICAL_PREFIX = 'phy-'
# The minimum version of OVS which supports VXLAN tunneling
MINIMUM_OVS_VXLAN_VERSION = "1.10"
# The first version of the Linux kernel with converged VXLAN code for OVS
MINIMUM_LINUX_KERNEL_OVS_VXLAN = "3.13.0"
# The different types of tunnels
TUNNEL_NETWORK_TYPES = [p_const.TYPE_GRE, p_const.TYPE_VXLAN]
@ -55,3 +49,6 @@ TUN_TABLE = {p_const.TYPE_GRE: GRE_TUN_TO_LV,
# The default respawn interval for the ovsdb monitor
DEFAULT_OVSDBMON_RESPAWN = 30
# Special return value for an invalid OVS ofport
INVALID_OFPORT = '-1'

View File

@ -1,67 +0,0 @@
# Copyright 2014 Cisco Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron.agent.linux import ovs_lib
from neutron.plugins.common import constants as p_const
from neutron.tests.functional.agent.linux import base as base_agent
PORT_PREFIX = 'testp-'
INVALID_OFPORT_ID = '-1'
class TestOVSAgentVXLAN(base_agent.BaseOVSLinuxTestCase):
def setUp(self):
super(TestOVSAgentVXLAN, self).setUp()
self._check_test_requirements()
def _check_test_requirements(self):
self.check_sudo_enabled()
self.check_command(['which', 'ovs-vsctl'],
'Exit code: 1', 'ovs-vsctl is not installed')
self.check_command(['sudo', '-n', 'ovs-vsctl', 'show'],
'Exit code: 1',
'password-less sudo not granted for ovs-vsctl')
def test_ovs_lib_vxlan_version_check(self):
"""Verify VXLAN versions match
This function compares the return values of functionally checking if
VXLAN is supported with the ovs_lib programmatic check. It will fail
if the two do not align.
"""
expected = self.is_vxlan_supported()
actual = self.is_ovs_lib_vxlan_supported()
self.assertEqual(actual, expected)
def is_ovs_lib_vxlan_supported(self):
try:
ovs_lib.check_ovs_vxlan_version(self.root_helper)
except SystemError:
return False
else:
return True
def is_vxlan_supported(self):
bridge = self.create_ovs_bridge()
vxlan_port = self.create_resource(
PORT_PREFIX,
bridge.add_tunnel_port,
"10.10.10.10",
"10.10.10.20",
p_const.TYPE_VXLAN)
return vxlan_port != INVALID_OFPORT_ID

View File

@ -0,0 +1,39 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2014 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
from neutron.cmd.sanity import checks
from neutron.tests import base
class OVSSanityTestCase(base.BaseTestCase):
def setUp(self):
super(OVSSanityTestCase, self).setUp()
self.root_helper = 'sudo'
def check_sudo_enabled(self):
if os.environ.get('OS_SUDO_TESTING') not in base.TRUE_STRING:
self.skipTest('testing with sudo is not enabled')
def test_ovs_vxlan_support_runs(self):
"""This test just ensures that the test in neutron-sanity-check
can run through without error, without mocking anything out
"""
self.check_sudo_enabled()
checks.vxlan_supported(self.root_helper)

View File

@ -22,7 +22,7 @@ from neutron.agent.linux import utils
from neutron.common import exceptions
from neutron.openstack.common import jsonutils
from neutron.openstack.common import uuidutils
from neutron.plugins.openvswitch.common import constants
from neutron.plugins.openvswitch.common import constants as const
from neutron.tests import base
from neutron.tests import tools
@ -321,6 +321,15 @@ class OVS_Lib_Test(base.BaseTestCase):
["ovs-vsctl", self.TO, "get", "Interface", pname, "ofport"],
root_helper=self.root_helper)
def test_get_port_ofport_non_int(self):
pname = "tap99"
ofport = "[]"
self.execute.return_value = ofport
self.assertEqual(self.br.get_port_ofport(pname), const.INVALID_OFPORT)
self.execute.assert_called_once_with(
["ovs-vsctl", self.TO, "get", "Interface", pname, "ofport"],
root_helper=self.root_helper)
def test_get_datapath_id(self):
datapath_id = '"0000b67f4fbcc149"'
self.execute.return_value = datapath_id
@ -854,68 +863,6 @@ class OVS_Lib_Test(base.BaseTestCase):
self.assertIsNone(self._test_get_vif_port_by_id('tap99id', data,
"br-ext"))
def _check_ovs_vxlan_version(self, installed_usr_version,
installed_klm_version,
installed_kernel_version,
expecting_ok):
with mock.patch(
'neutron.agent.linux.ovs_lib.get_installed_ovs_klm_version'
) as klm_cmd:
with mock.patch(
'neutron.agent.linux.ovs_lib.get_installed_ovs_usr_version'
) as usr_cmd:
with mock.patch(
'neutron.agent.linux.ovs_lib.get_installed_kernel_version'
) as kernel_cmd:
try:
klm_cmd.return_value = installed_klm_version
usr_cmd.return_value = installed_usr_version
kernel_cmd.return_value = installed_kernel_version
ovs_lib.check_ovs_vxlan_version(root_helper='sudo')
version_ok = True
except SystemError:
version_ok = False
self.assertEqual(version_ok, expecting_ok)
def test_check_minimum_version(self):
min_vxlan_ver = constants.MINIMUM_OVS_VXLAN_VERSION
min_kernel_ver = constants.MINIMUM_LINUX_KERNEL_OVS_VXLAN
self._check_ovs_vxlan_version(min_vxlan_ver, min_vxlan_ver,
min_kernel_ver, expecting_ok=True)
def test_check_future_version(self):
install_ver = str(float(constants.MINIMUM_OVS_VXLAN_VERSION) + 0.01)
min_kernel_ver = constants.MINIMUM_LINUX_KERNEL_OVS_VXLAN
self._check_ovs_vxlan_version(install_ver, install_ver,
min_kernel_ver, expecting_ok=True)
def test_check_fail_version(self):
install_ver = str(float(constants.MINIMUM_OVS_VXLAN_VERSION) - 0.01)
min_kernel_ver = constants.MINIMUM_LINUX_KERNEL_OVS_VXLAN
self._check_ovs_vxlan_version(install_ver, install_ver,
min_kernel_ver, expecting_ok=False)
def test_check_fail_no_version(self):
min_kernel_ver = constants.MINIMUM_LINUX_KERNEL_OVS_VXLAN
self._check_ovs_vxlan_version(None, None,
min_kernel_ver,
expecting_ok=False)
def test_check_fail_klm_version(self):
min_vxlan_ver = constants.MINIMUM_OVS_VXLAN_VERSION
min_kernel_ver = OVS_LINUX_KERN_VERS_WITHOUT_VXLAN
install_ver = str(float(min_vxlan_ver) - 0.01)
self._check_ovs_vxlan_version(min_vxlan_ver,
install_ver,
min_kernel_ver,
expecting_ok=False)
def test_check_pass_kernel_version(self):
min_vxlan_ver = constants.MINIMUM_OVS_VXLAN_VERSION
min_kernel_ver = constants.MINIMUM_LINUX_KERNEL_OVS_VXLAN
self._check_ovs_vxlan_version(min_vxlan_ver, min_vxlan_ver,
min_kernel_ver, expecting_ok=True)
def test_ofctl_arg_supported(self):
with mock.patch('neutron.common.utils.get_random_string') as utils:
utils.return_value = 'test'

View File

@ -291,24 +291,12 @@ class TunnelTest(base.BaseTestCase):
self._verify_mock_calls()
def test_construct_vxlan(self):
with mock.patch.object(ovs_lib, 'get_installed_ovs_klm_version',
return_value="1.10") as klm_ver:
with mock.patch.object(ovs_lib, 'get_installed_ovs_usr_version',
return_value="1.10") as usr_ver:
with mock.patch.object(ovs_lib, 'get_installed_kernel_version',
return_value=(
constants.
MINIMUM_LINUX_KERNEL_OVS_VXLAN
)) as kernel_ver:
ovs_neutron_agent.OVSNeutronAgent(self.INT_BRIDGE,
self.TUN_BRIDGE,
'10.0.0.1',
self.NET_MAPPING,
'sudo', 2, ['vxlan'],
self.VETH_MTU)
klm_ver.assert_called_once_with()
kernel_ver.assert_called_once_with()
usr_ver.assert_called_once_with('sudo')
ovs_neutron_agent.OVSNeutronAgent(self.INT_BRIDGE,
self.TUN_BRIDGE,
'10.0.0.1',
self.NET_MAPPING,
'sudo', 2, ['vxlan'],
self.VETH_MTU)
self._verify_mock_calls()
def test_provision_local_vlan(self):

View File

@ -109,6 +109,7 @@ console_scripts =
neutron-vpn-agent = neutron.services.vpn.agent:main
neutron-metering-agent = neutron.services.metering.agents.metering_agent:main
neutron-ofagent-agent = ryu.cmd.ofa_neutron_agent:main
neutron-sanity-check = neutron.cmd.sanity_check:main
neutron.core_plugins =
bigswitch = neutron.plugins.bigswitch.plugin:NeutronRestProxyV2
brocade = neutron.plugins.brocade.NeutronPlugin:BrocadePluginV2