Adding DSCP mark and inheritance in OVS and LB tunnels outer header

Adding ability to set DSCP field in OVS tunnels outer header, or
inherit it from the inner header's DSCP value for OVS and linuxbridge.

Change-Id: Ia59753ded73cd23019605668e60cfbc8841e803d
Closes-Bug: #1692951
This commit is contained in:
Ali Sanhaji 2017-09-06 15:37:12 +02:00
parent ae26e6b8dc
commit 6bf0788da0
15 changed files with 180 additions and 18 deletions

View File

@ -151,6 +151,36 @@ On the network and compute nodes:
QoS currently works with ml2 only (SR-IOV, Open vSwitch, and linuxbridge
are drivers enabled for QoS).
DSCP marking on outer header for overlay networks
-------------------------------------------------
When using overlay networks (e.g., VxLAN), the DSCP marking rule only
applies to the inner header, and during encapsulation, the DSCP mark is
not automatically copied to the outer header.
#. In order to set the DSCP value of the outer header, modify the ``dscp``
configuration option in ``/etc/neutron/plugins/ml2/<agent_name>_agent.ini``
where ``<agent_name>`` is the name of the agent being used
(e.g., ``openvswitch``):
.. code-block:: ini
[agent]
dscp = 8
#. In order to copy the DSCP field of the inner header to the outer header,
change the ``dscp_inherit`` configuration option to true in
``/etc/neutron/plugins/ml2/<agent_name>_agent.ini`` where ``<agent_name>``
is the name of the agent being used (e.g., ``openvswitch``):
.. code-block:: ini
[agent]
dscp_inherit = true
If the ``dscp_inherit`` option is set to true, the previous ``dscp`` option
is overwritten.
Trusted projects policy.json configuration
------------------------------------------

View File

@ -458,7 +458,8 @@ class OVSBridge(BaseOVS):
tunnel_type=p_const.TYPE_GRE,
vxlan_udp_port=p_const.VXLAN_UDP_PORT,
dont_fragment=True,
tunnel_csum=False):
tunnel_csum=False,
tos=None):
attrs = [('type', tunnel_type)]
# TODO(twilson) This is an OrderedDict solely to make a test happy
options = collections.OrderedDict()
@ -475,6 +476,8 @@ class OVSBridge(BaseOVS):
options['out_key'] = 'flow'
if tunnel_csum:
options['csum'] = str(tunnel_csum).lower()
if tos:
options['tos'] = str(tos)
attrs.append(('options', options))
return self.add_port(port_name, *attrs)

View File

@ -26,6 +26,14 @@ agent_opts = [
help=_("Set new timeout in seconds for new rpc calls after "
"agent receives SIGTERM. If value is set to 0, rpc "
"timeout won't be changed")),
cfg.IntOpt('dscp', min=0, max=63,
help=_("The DSCP value to use for outer headers during tunnel "
"encapsulation.")),
cfg.BoolOpt('dscp_inherit', default=False,
help=_("If set to True, the DSCP value of tunnel "
"interfaces is overwritten and set to inherit. "
"The DSCP value of the inner header is then "
"copied to the outer header.")),
]

View File

@ -30,7 +30,11 @@ vxlan_opts = [
cfg.IntOpt('ttl',
help=_("TTL for vxlan interface protocol packets.")),
cfg.IntOpt('tos',
help=_("TOS for vxlan interface protocol packets.")),
deprecated_for_removal=True,
help=_("TOS for vxlan interface protocol packets. This option "
"is deprecated in favor of the dscp option in the AGENT "
"section and will be removed in a future release. "
"To convert the TOS value to DSCP, divide by 4.")),
cfg.StrOpt('vxlan_group', default=DEFAULT_VXLAN_GROUP,
help=_("Multicast group(s) for vxlan interface. A range of "
"group addresses may be specified by using CIDR "

View File

@ -96,9 +96,6 @@ ovs_opts = [
]
agent_opts = [
cfg.IntOpt('polling_interval', default=2,
help=_("The number of seconds the agent will wait between "
"polling for local device changes.")),
cfg.BoolOpt('minimize_polling',
default=True,
help=_("Minimize polling by monitoring ovsdb for interface "
@ -128,10 +125,6 @@ agent_opts = [
"outgoing IP packet carrying GRE/VXLAN tunnel.")),
cfg.BoolOpt('enable_distributed_routing', default=False,
help=_("Make the l2 agent run in DVR mode.")),
cfg.IntOpt('quitting_rpc_timeout', default=10,
help=_("Set new timeout in seconds for new rpc calls after "
"agent receives SIGTERM. If value is set to 0, rpc "
"timeout won't be changed")),
cfg.BoolOpt('drop_flows_on_start', default=False,
help=_("Reset flow table on start. Setting this to True will "
"cause brief traffic interruption.")),

View File

@ -328,8 +328,18 @@ class LinuxBridgeManager(amb.CommonAgentManagerBase):
'srcport': (cfg.CONF.VXLAN.udp_srcport_min,
cfg.CONF.VXLAN.udp_srcport_max),
'dstport': cfg.CONF.VXLAN.udp_dstport,
'ttl': cfg.CONF.VXLAN.ttl,
'tos': cfg.CONF.VXLAN.tos}
'ttl': cfg.CONF.VXLAN.ttl}
if cfg.CONF.VXLAN.tos:
args['tos'] = cfg.CONF.VXLAN.tos
if cfg.CONF.AGENT.dscp or cfg.CONF.AGENT.dscp_inherit:
LOG.warning('The deprecated tos option in group VXLAN '
'is set and takes precedence over dscp and '
'dscp_inherit in group AGENT.')
elif cfg.CONF.AGENT.dscp_inherit:
args['tos'] = 'inherit'
elif cfg.CONF.AGENT.dscp:
args['tos'] = int(cfg.CONF.AGENT.dscp) << 2
if self.vxlan_mode == lconst.VXLAN_MCAST:
args['group'] = self.get_vxlan_group(segmentation_id)
if cfg.CONF.VXLAN.l2_population:

View File

@ -15,7 +15,9 @@
from oslo_config import cfg
from neutron.conf.agent import common as config
from neutron.conf.plugins.ml2.drivers import agent
from neutron.conf.plugins.ml2.drivers import ovs_conf
agent.register_agent_opts()
ovs_conf.register_ovs_agent_opts()
config.register_agent_state_opts_helper(cfg.CONF)

View File

@ -195,6 +195,11 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
self.vxlan_udp_port = agent_conf.vxlan_udp_port
self.dont_fragment = agent_conf.dont_fragment
self.tunnel_csum = agent_conf.tunnel_csum
self.tos = ('inherit'
if agent_conf.dscp_inherit
else (int(agent_conf.dscp) << 2
if agent_conf.dscp
else None))
self.tun_br = None
self.patch_int_ofport = constants.OFPORT_INVALID
self.patch_tun_ofport = constants.OFPORT_INVALID
@ -1460,7 +1465,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
tunnel_type,
self.vxlan_udp_port,
self.dont_fragment,
self.tunnel_csum)
self.tunnel_csum,
self.tos)
if ofport == ovs_lib.INVALID_OFPORT:
LOG.error("Failed to set-up %(type)s tunnel port to %(ip)s",
{'type': tunnel_type, 'ip': remote_ip})

View File

@ -30,6 +30,7 @@ from neutron.agent.linux import polling
from neutron.common import utils
from neutron.conf.agent import common as agent_config
from neutron.conf import common as common_config
from neutron.conf.plugins.ml2.drivers import agent
from neutron.conf.plugins.ml2.drivers import ovs_conf
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.ovs_ofctl \
@ -69,6 +70,7 @@ class OVSAgentTestFramework(base.BaseOVSLinuxTestCase):
def _get_config_opts(self):
config = cfg.ConfigOpts()
config.register_opts(common_config.core_opts)
agent.register_agent_opts(config)
ovs_conf.register_ovs_agent_opts(config)
agent_config.register_interface_opts(config)
agent_config.register_interface_driver_opts_helper(config)

View File

@ -223,6 +223,21 @@ class OVSBridgeTestCase(OVSBridgeTestBase):
options = self.ovs.db_get_val('Interface', port_name, 'options')
self.assertEqual("12345", options['dst_port'])
def test_add_tunnel_port_tos(self):
attrs = {
'remote_ip': self.get_test_net_address(1),
'local_ip': self.get_test_net_address(2),
'tos': 'inherit',
}
port_name = utils.get_rand_device_name(net_helpers.PORT_PREFIX)
self.br.add_tunnel_port(port_name, attrs['remote_ip'],
attrs['local_ip'], tos=attrs['tos'])
self.assertEqual('gre',
self.ovs.db_get_val('Interface', port_name, 'type'))
options = self.ovs.db_get_val('Interface', port_name, 'options')
for attr, val in attrs.items():
self.assertEqual(val, options[attr])
def test_add_patch_port(self):
local = utils.get_rand_device_name(net_helpers.PORT_PREFIX)
peer = 'remotepeer'

View File

@ -583,6 +583,41 @@ class OVS_Lib_Test(base.BaseTestCase):
tools.verify_mock_calls(self.execute, expected_calls_and_values)
def test_add_vxlan_tos_tunnel_port(self):
pname = "tap99"
local_ip = "1.1.1.1"
remote_ip = "9.9.9.9"
ofport = 6
vxlan_udp_port = "9999"
dont_fragment = True
tunnel_csum = False
tos = 8
command = ["--may-exist", "add-port", self.BR_NAME, pname]
command.extend(["--", "set", "Interface", pname])
command.extend(["type=" + constants.TYPE_VXLAN,
"options:dst_port=" + vxlan_udp_port,
"options:df_default=true",
"options:remote_ip=" + remote_ip,
"options:local_ip=" + local_ip,
"options:in_key=flow",
"options:out_key=flow",
"options:tos=" + str(tos)])
# Each element is a tuple of (expected mock call, return_value)
expected_calls_and_values = [
(self._vsctl_mock(*command), None),
(self._vsctl_mock("--columns=ofport", "list", "Interface", pname),
self._encode_ovs_json(['ofport'], [[ofport]])),
]
tools.setup_mock_calls(self.execute, expected_calls_and_values)
self.assertEqual(
self.br.add_tunnel_port(pname, remote_ip, local_ip,
constants.TYPE_VXLAN, vxlan_udp_port,
dont_fragment, tunnel_csum, tos),
ofport)
tools.verify_mock_calls(self.execute, expected_calls_and_values)
def _encode_ovs_json(self, headings, data):
# See man ovs-vsctl(8) for the encoding details.
r = {"data": [],

View File

@ -392,7 +392,6 @@ class TestLinuxBridgeManager(base.BaseTestCase):
srcport=(0, 0),
dstport=None,
ttl=None,
tos=None,
dev=self.lbm.local_int)
dv6_fn.assert_called_once_with()
cfg.CONF.set_override('l2_population', 'True', 'VXLAN')
@ -403,7 +402,6 @@ class TestLinuxBridgeManager(base.BaseTestCase):
srcport=(0, 0),
dstport=None,
ttl=None,
tos=None,
dev=self.lbm.local_int,
proxy=expected_proxy)
@ -411,6 +409,27 @@ class TestLinuxBridgeManager(base.BaseTestCase):
cfg.CONF.set_override('arp_responder', True, 'VXLAN')
self.test_ensure_vxlan(expected_proxy=True)
def test_ensure_vxlan_dscp_inherit_set(self):
cfg.CONF.set_override('dscp_inherit', 'True', 'AGENT')
seg_id = "12345678"
self.lbm.local_int = 'eth0'
self.lbm.vxlan_mode = lconst.VXLAN_MCAST
with mock.patch.object(ip_lib, 'device_exists', return_value=False):
vxlan_dev = FakeIpDevice()
with mock.patch.object(vxlan_dev, 'disable_ipv6') as dv6_fn,\
mock.patch.object(self.lbm.ip, 'add_vxlan',
return_value=vxlan_dev) as add_vxlan_fn:
self.assertEqual("vxlan-" + seg_id,
self.lbm.ensure_vxlan(seg_id))
add_vxlan_fn.assert_called_with("vxlan-" + seg_id, seg_id,
group="224.0.0.1",
srcport=(0, 0),
dstport=None,
ttl=None,
tos='inherit',
dev=self.lbm.local_int)
dv6_fn.assert_called_once_with()
def test__update_interface_ip_details(self):
gwdict = dict(gateway='1.1.1.1',
metric=50)

View File

@ -1690,7 +1690,7 @@ class TestOvsNeutronAgent(object):
add_tunnel_port_fn.assert_called_once_with(
'gre-1', remote_ip, self.agent.local_ip, n_const.TYPE_GRE,
self.agent.vxlan_udp_port, self.agent.dont_fragment,
self.agent.tunnel_csum)
self.agent.tunnel_csum, self.agent.tos)
log_error_fn.assert_called_once_with(
_("Failed to set-up %(type)s tunnel port to %(ip)s"),
{'type': n_const.TYPE_GRE, 'ip': remote_ip})
@ -1735,7 +1735,7 @@ class TestOvsNeutronAgent(object):
add_tunnel_port_fn.assert_called_once_with(
'gre-1', remote_ip, self.agent.local_ip, n_const.TYPE_GRE,
self.agent.vxlan_udp_port, self.agent.dont_fragment,
self.agent.tunnel_csum)
self.agent.tunnel_csum, self.agent.tos)
log_error_fn.assert_called_once_with(
_("Failed to set-up %(type)s tunnel port to %(ip)s"),
{'type': n_const.TYPE_GRE, 'ip': remote_ip})
@ -1756,7 +1756,27 @@ class TestOvsNeutronAgent(object):
add_tunnel_port_fn.assert_called_once_with(
'gre-1', remote_ip, self.agent.local_ip, n_const.TYPE_GRE,
self.agent.vxlan_udp_port, self.agent.dont_fragment,
self.agent.tunnel_csum)
self.agent.tunnel_csum, self.agent.tos)
log_error_fn.assert_called_once_with(
_("Failed to set-up %(type)s tunnel port to %(ip)s"),
{'type': n_const.TYPE_GRE, 'ip': remote_ip})
self.assertEqual(0, ofport)
def test_setup_tunnel_port_error_negative_tos_inherit(self):
remote_ip = '1.2.3.4'
with mock.patch.object(
self.agent.tun_br,
'add_tunnel_port',
return_value=ovs_lib.INVALID_OFPORT) as add_tunnel_port_fn,\
mock.patch.object(self.mod_agent.LOG, 'error') as log_error_fn:
self.agent.tos = 'inherit'
self.agent.local_ip = '2.3.4.5'
ofport = self.agent._setup_tunnel_port(
self.agent.tun_br, 'gre-1', remote_ip, n_const.TYPE_GRE)
add_tunnel_port_fn.assert_called_once_with(
'gre-1', remote_ip, self.agent.local_ip, n_const.TYPE_GRE,
self.agent.vxlan_udp_port, self.agent.dont_fragment,
self.agent.tunnel_csum, self.agent.tos)
log_error_fn.assert_called_once_with(
_("Failed to set-up %(type)s tunnel port to %(ip)s"),
{'type': n_const.TYPE_GRE, 'ip': remote_ip})

View File

@ -526,7 +526,7 @@ class TunnelTest(object):
self.mock_tun_bridge.add_tunnel_port.return_value = tunnel_port
self.mock_tun_bridge_expected += [
mock.call.add_tunnel_port('gre-0a000a01', '10.0.10.1', '10.0.0.1',
'gre', 4789, True, False),
'gre', 4789, True, False, None),
mock.call.setup_tunnel_port('gre', tunnel_port),
]

View File

@ -0,0 +1,15 @@
---
features:
- The DSCP value for outer headers in openvswitch overlay tunnel ports can
now be set through a configuration option ``dscp`` for both OVS and
linuxbridge agents.
- DSCP can also be inherited from the inner header through a new
boolean configuration option ``dscp_inherit`` for both openvswitch and
linuxbridge. If this option is set to true, then the value of ``dscp``
will be ignored.
deprecations:
- the ``tos`` configuration option in vxlan group for linuxbridge is
deprecated and replaced with the more precise option ``dscp``. The
TOS value is made of DSCP and ECN bits. It is not possible to set the
ECN value through the TOS value, and ECN is always inherited from the
inner in case of tunneling.