diff --git a/doc/source/admin/config-qos.rst b/doc/source/admin/config-qos.rst index 4fcf20f071d..464eb4486b1 100644 --- a/doc/source/admin/config-qos.rst +++ b/doc/source/admin/config-qos.rst @@ -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.ini`` + where ```` 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.ini`` where ```` + 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 ------------------------------------------ diff --git a/neutron/agent/common/ovs_lib.py b/neutron/agent/common/ovs_lib.py index f3241cc4b8e..41c654a0c68 100644 --- a/neutron/agent/common/ovs_lib.py +++ b/neutron/agent/common/ovs_lib.py @@ -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) diff --git a/neutron/conf/plugins/ml2/drivers/agent.py b/neutron/conf/plugins/ml2/drivers/agent.py index 16347d99146..15194a7f992 100644 --- a/neutron/conf/plugins/ml2/drivers/agent.py +++ b/neutron/conf/plugins/ml2/drivers/agent.py @@ -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.")), ] diff --git a/neutron/conf/plugins/ml2/drivers/linuxbridge.py b/neutron/conf/plugins/ml2/drivers/linuxbridge.py index 6273162f8d7..20d6503427b 100644 --- a/neutron/conf/plugins/ml2/drivers/linuxbridge.py +++ b/neutron/conf/plugins/ml2/drivers/linuxbridge.py @@ -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 " diff --git a/neutron/conf/plugins/ml2/drivers/ovs_conf.py b/neutron/conf/plugins/ml2/drivers/ovs_conf.py index 17b0e343a9d..d6cbc775b6e 100644 --- a/neutron/conf/plugins/ml2/drivers/ovs_conf.py +++ b/neutron/conf/plugins/ml2/drivers/ovs_conf.py @@ -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.")), diff --git a/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py b/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py index 009eb64bb0c..7240322e4cb 100644 --- a/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py @@ -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: diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/common/config.py b/neutron/plugins/ml2/drivers/openvswitch/agent/common/config.py index 3ea4f0c3e04..0cfb0ef46d7 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/common/config.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/common/config.py @@ -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) diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py index a968c7e4ee2..3b5f435e920 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py @@ -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}) diff --git a/neutron/tests/functional/agent/l2/base.py b/neutron/tests/functional/agent/l2/base.py index 40f4aab974f..361cfd4d579 100644 --- a/neutron/tests/functional/agent/l2/base.py +++ b/neutron/tests/functional/agent/l2/base.py @@ -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) diff --git a/neutron/tests/functional/agent/test_ovs_lib.py b/neutron/tests/functional/agent/test_ovs_lib.py index 3bbea8ed53c..d29ce88b842 100644 --- a/neutron/tests/functional/agent/test_ovs_lib.py +++ b/neutron/tests/functional/agent/test_ovs_lib.py @@ -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' diff --git a/neutron/tests/unit/agent/common/test_ovs_lib.py b/neutron/tests/unit/agent/common/test_ovs_lib.py index 9d1b570a08a..9833e98f24d 100644 --- a/neutron/tests/unit/agent/common/test_ovs_lib.py +++ b/neutron/tests/unit/agent/common/test_ovs_lib.py @@ -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": [], diff --git a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py b/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py index e3d65b84bb5..d9e048cdf17 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py @@ -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) diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py index 0b39674d91a..85a4c863a25 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py @@ -1694,7 +1694,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}) @@ -1739,7 +1739,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}) @@ -1760,7 +1760,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}) diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py index 362369cc625..32c607cae5b 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py @@ -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), ] diff --git a/releasenotes/notes/add-dscp-for-tunneling-03e28fe7c2f34e86.yaml b/releasenotes/notes/add-dscp-for-tunneling-03e28fe7c2f34e86.yaml new file mode 100644 index 00000000000..8d5fa395417 --- /dev/null +++ b/releasenotes/notes/add-dscp-for-tunneling-03e28fe7c2f34e86.yaml @@ -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. \ No newline at end of file