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:
parent
ae26e6b8dc
commit
6bf0788da0
@ -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
|
||||
------------------------------------------
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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.")),
|
||||
]
|
||||
|
||||
|
||||
|
@ -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 "
|
||||
|
@ -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.")),
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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})
|
||||
|
@ -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)
|
||||
|
@ -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'
|
||||
|
@ -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": [],
|
||||
|
@ -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)
|
||||
|
@ -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})
|
||||
|
@ -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),
|
||||
]
|
||||
|
||||
|
@ -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.
|
Loading…
x
Reference in New Issue
Block a user