Merge "DSCP packet marking support in Linuxbridge agent"

This commit is contained in:
Jenkins 2016-12-13 23:04:24 +00:00 committed by Gerrit Code Review
commit 920ddeaf58
7 changed files with 286 additions and 55 deletions

View File

@ -18,7 +18,9 @@ from oslo_log import log
from neutron._i18n import _LI
from neutron.agent.l2.extensions import qos
from neutron.agent.linux import iptables_manager
from neutron.agent.linux import tc_lib
import neutron.common.constants as const
from neutron.plugins.ml2.drivers.linuxbridge.mech_driver import (
mech_linuxbridge)
@ -31,8 +33,28 @@ class QosLinuxbridgeAgentDriver(qos.QosAgentDriver):
mech_linuxbridge.LinuxbridgeMechanismDriver.supported_qos_rule_types
)
IPTABLES_DIRECTION = {const.INGRESS_DIRECTION: 'physdev-out',
const.EGRESS_DIRECTION: 'physdev-in'}
IPTABLES_DIRECTION_PREFIX = {const.INGRESS_DIRECTION: "i",
const.EGRESS_DIRECTION: "o"}
def initialize(self):
LOG.info(_LI("Initializing Linux bridge QoS extension"))
self.iptables_manager = iptables_manager.IptablesManager(use_ipv6=True)
def _dscp_chain_name(self, direction, device):
return iptables_manager.get_chain_name(
"qos-%s%s" % (self.IPTABLES_DIRECTION_PREFIX[direction],
device[3:]))
def _dscp_rule(self, direction, device):
return ('-m physdev --%s %s --physdev-is-bridged '
'-j $%s') % (self.IPTABLES_DIRECTION[direction],
device,
self._dscp_chain_name(direction, device))
def _dscp_rule_tag(self, device):
return "dscp-%s" % device
@log_helpers.log_method_call
def create_bandwidth_limit(self, port, rule):
@ -53,6 +75,74 @@ class QosLinuxbridgeAgentDriver(qos.QosAgentDriver):
tc_wrapper = self._get_tc_wrapper(port)
tc_wrapper.delete_filters_bw_limit()
@log_helpers.log_method_call
def create_dscp_marking(self, port, rule):
with self.iptables_manager.defer_apply():
self._set_outgoing_qos_chain_for_port(port)
self._set_dscp_mark_rule(port, rule.dscp_mark)
@log_helpers.log_method_call
def update_dscp_marking(self, port, rule):
with self.iptables_manager.defer_apply():
self._delete_dscp_mark_rule(port)
self._set_outgoing_qos_chain_for_port(port)
self._set_dscp_mark_rule(port, rule.dscp_mark)
@log_helpers.log_method_call
def delete_dscp_marking(self, port):
with self.iptables_manager.defer_apply():
self._delete_dscp_mark_rule(port)
self._delete_outgoing_qos_chain_for_port(port)
def _set_outgoing_qos_chain_for_port(self, port):
chain_name = self._dscp_chain_name(
const.EGRESS_DIRECTION, port['device'])
chain_rule = self._dscp_rule(
const.EGRESS_DIRECTION, port['device'])
self.iptables_manager.ipv4['mangle'].add_chain(chain_name)
self.iptables_manager.ipv6['mangle'].add_chain(chain_name)
self.iptables_manager.ipv4['mangle'].add_rule('POSTROUTING',
chain_rule)
self.iptables_manager.ipv6['mangle'].add_rule('POSTROUTING',
chain_rule)
def _delete_outgoing_qos_chain_for_port(self, port):
chain_name = self._dscp_chain_name(
const.EGRESS_DIRECTION, port['device'])
chain_rule = self._dscp_rule(
const.EGRESS_DIRECTION, port['device'])
if self._qos_chain_is_empty(port, 4):
self.iptables_manager.ipv4['mangle'].remove_chain(chain_name)
self.iptables_manager.ipv4['mangle'].remove_rule('POSTROUTING',
chain_rule)
if self._qos_chain_is_empty(port, 6):
self.iptables_manager.ipv6['mangle'].remove_chain(chain_name)
self.iptables_manager.ipv6['mangle'].remove_rule('POSTROUTING',
chain_rule)
def _set_dscp_mark_rule(self, port, dscp_value):
chain_name = self._dscp_chain_name(
const.EGRESS_DIRECTION, port['device'])
rule = "-j DSCP --set-dscp %s" % dscp_value
self.iptables_manager.ipv4['mangle'].add_rule(
chain_name, rule, tag=self._dscp_rule_tag(port['device']))
self.iptables_manager.ipv6['mangle'].add_rule(
chain_name, rule, tag=self._dscp_rule_tag(port['device']))
def _delete_dscp_mark_rule(self, port):
self.iptables_manager.ipv4['mangle'].clear_rules_by_tag(
self._dscp_rule_tag(port['device']))
self.iptables_manager.ipv6['mangle'].clear_rules_by_tag(
self._dscp_rule_tag(port['device']))
def _qos_chain_is_empty(self, port, ip_version=4):
chain_name = self._dscp_chain_name(
const.EGRESS_DIRECTION, port['device'])
rules_in_chain = self.iptables_manager.get_chain(
"mangle", chain_name, ip_version=ip_version)
return len(rules_in_chain) == 0
def _get_tc_wrapper(self, port):
return tc_lib.TcCommand(
port['device'],

View File

@ -32,7 +32,8 @@ class LinuxbridgeMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase):
network.
"""
supported_qos_rule_types = [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT]
supported_qos_rule_types = [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT,
qos_consts.RULE_TYPE_DSCP_MARKING]
def __init__(self):
sg_enabled = securitygroups_rpc.is_firewall_enabled()

View File

@ -16,6 +16,7 @@
import re
from neutron.agent.linux import async_process
from neutron.agent.linux import iptables_manager
from neutron.common import utils as common_utils
@ -38,6 +39,15 @@ def extract_mod_nw_tos_action(flows):
return tos_mark
def extract_dscp_value_from_iptables_rules(rules):
pattern = (r"^-A neutron-linuxbri-qos-.* -j DSCP "
"--set-dscp (?P<dscp_value>0x[A-Fa-f0-9]+)$")
for rule in rules:
m = re.match(pattern, rule)
if m:
return int(m.group("dscp_value"), 16)
def wait_until_bandwidth_limit_rule_applied(bridge, port_vif, rule):
def _bandwidth_limit_rule_applied():
bw_rule = bridge.get_egress_bw_limit_for_port(port_vif)
@ -49,7 +59,7 @@ def wait_until_bandwidth_limit_rule_applied(bridge, port_vif, rule):
common_utils.wait_until_true(_bandwidth_limit_rule_applied)
def wait_until_dscp_marking_rule_applied(bridge, port_vif, rule):
def wait_until_dscp_marking_rule_applied_ovs(bridge, port_vif, rule):
def _dscp_marking_rule_applied():
port_num = bridge.get_port_ofport(port_vif)
@ -64,6 +74,20 @@ def wait_until_dscp_marking_rule_applied(bridge, port_vif, rule):
common_utils.wait_until_true(_dscp_marking_rule_applied)
def wait_until_dscp_marking_rule_applied_linuxbridge(
namespace, port_vif, expected_rule):
iptables = iptables_manager.IptablesManager(
namespace=namespace)
def _dscp_marking_rule_applied():
mangle_rules = iptables.get_rules_for_table("mangle")
dscp_mark = extract_dscp_value_from_iptables_rules(mangle_rules)
return dscp_mark == expected_rule
common_utils.wait_until_true(_dscp_marking_rule_applied)
def wait_for_dscp_marked_packet(sender_vm, receiver_vm, dscp_mark):
cmd = ["tcpdump", "-i", receiver_vm.port.name, "-nlt"]
if dscp_mark:

View File

@ -45,14 +45,18 @@ DSCP_MARK = 16
class BaseQoSRuleTestCase(object):
of_interface = None
ovsdb_interface = None
number_of_hosts = 1
def setUp(self):
host_desc = [environment.HostDescription(
l3_agent=False,
of_interface=self.of_interface,
ovsdb_interface=self.ovsdb_interface,
l2_agent_type=self.l2_agent_type)]
env_desc = environment.EnvironmentDescription(qos=True)
host_desc = [
environment.HostDescription(
l3_agent=False,
of_interface=self.of_interface,
ovsdb_interface=self.ovsdb_interface,
l2_agent_type=self.l2_agent_type
) for _ in range(self.number_of_hosts)]
env_desc = environment.EnvironmentDescription(
qos=True)
env = environment.Environment(env_desc, host_desc)
super(BaseQoSRuleTestCase, self).setUp(env)
@ -95,6 +99,9 @@ class BaseQoSRuleTestCase(object):
class _TestBwLimitQoS(BaseQoSRuleTestCase):
number_of_hosts = 1
def _wait_for_bw_rule_removed(self, vm):
# No values are provided when port doesn't have qos policy
self._wait_for_bw_rule_applied(vm, None, None)
@ -172,36 +179,9 @@ class TestBwLimitQoSLinuxbridge(_TestBwLimitQoS, base.BaseFullStackTestCase):
lambda: tc.get_filters_bw_limits() == (limit, burst))
class TestDscpMarkingQoSOvs(BaseQoSRuleTestCase, base.BaseFullStackTestCase):
scenarios = fullstack_utils.get_ovs_interface_scenarios()
l2_agent_type = constants.AGENT_TYPE_OVS
class _TestDscpMarkingQoS(BaseQoSRuleTestCase):
def setUp(self):
host_desc = [
environment.HostDescription(
l3_agent=False,
of_interface=self.of_interface,
ovsdb_interface=self.ovsdb_interface,
l2_agent_type=self.l2_agent_type
) for _ in range(2)]
env_desc = environment.EnvironmentDescription(
qos=True)
env = environment.Environment(env_desc, host_desc)
super(BaseQoSRuleTestCase, self).setUp(env)
self.tenant_id = uuidutils.generate_uuid()
self.network = self.safe_client.create_network(self.tenant_id,
'network-test')
self.subnet = self.safe_client.create_subnet(
self.tenant_id, self.network['id'],
cidr='10.0.0.0/24',
gateway_ip='10.0.0.1',
name='subnet-test',
enable_dhcp=False)
def _wait_for_dscp_marking_rule_applied(self, vm, dscp_mark):
l2_extensions.wait_until_dscp_marking_rule_applied(
vm.bridge, vm.port.name, dscp_mark)
number_of_hosts = 2
def _wait_for_dscp_marking_rule_removed(self, vm):
self._wait_for_dscp_marking_rule_applied(vm, None)
@ -272,19 +252,29 @@ class TestDscpMarkingQoSOvs(BaseQoSRuleTestCase, base.BaseFullStackTestCase):
sender, receiver, DSCP_MARK)
class TestDscpMarkingQoSOvs(_TestDscpMarkingQoS, base.BaseFullStackTestCase):
scenarios = fullstack_utils.get_ovs_interface_scenarios()
l2_agent_type = constants.AGENT_TYPE_OVS
def _wait_for_dscp_marking_rule_applied(self, vm, dscp_mark):
l2_extensions.wait_until_dscp_marking_rule_applied_ovs(
vm.bridge, vm.port.name, dscp_mark)
class TestDscpMarkingQoSLinuxbridge(_TestDscpMarkingQoS,
base.BaseFullStackTestCase):
l2_agent_type = constants.AGENT_TYPE_LINUXBRIDGE
def _wait_for_dscp_marking_rule_applied(self, vm, dscp_mark):
l2_extensions.wait_until_dscp_marking_rule_applied_linuxbridge(
vm.host.host_namespace, vm.port.name, dscp_mark)
class TestQoSWithL2Population(base.BaseFullStackTestCase):
def setUp(self):
# We limit this test to using the openvswitch mech driver, because DSCP
# is presently not implemented for Linux Bridge. The 'rule_types' API
# call only returns rule types that are supported by all configured
# mech drivers. So in a fullstack scenario, where both the OVS and the
# Linux Bridge mech drivers are configured, the DSCP rule type will be
# unavailable since it is not implemented in Linux Bridge.
mech_driver = 'openvswitch'
host_desc = [] # No need to register agents for this test case
env_desc = environment.EnvironmentDescription(qos=True, l2_pop=True,
mech_drivers=mech_driver)
env_desc = environment.EnvironmentDescription(qos=True, l2_pop=True)
env = environment.Environment(env_desc, host_desc)
super(TestQoSWithL2Population, self).setUp(env)

View File

@ -141,7 +141,7 @@ class OVSAgentQoSExtensionTestFramework(base.OVSAgentTestFramework):
self.assertIsNone(tos_mark)
def wait_until_dscp_marking_rule_applied(self, port, dscp_mark):
l2_extensions.wait_until_dscp_marking_rule_applied(
l2_extensions.wait_until_dscp_marking_rule_applied_ovs(
self.agent.int_br, port['vif_name'], dscp_mark)
def _create_port_with_qos(self):

View File

@ -26,6 +26,7 @@ from neutron.tests import base
TEST_LATENCY_VALUE = 100
DSCP_VALUE = 32
class QosLinuxbridgeAgentDriverTestCase(base.BaseTestCase):
@ -35,7 +36,8 @@ class QosLinuxbridgeAgentDriverTestCase(base.BaseTestCase):
cfg.CONF.set_override("tbf_latency", TEST_LATENCY_VALUE, "QOS")
self.qos_driver = qos_driver.QosLinuxbridgeAgentDriver()
self.qos_driver.initialize()
self.rule = self._create_bw_limit_rule_obj()
self.rule_bw_limit = self._create_bw_limit_rule_obj()
self.rule_dscp_marking = self._create_dscp_marking_rule_obj()
self.port = self._create_fake_port(uuidutils.generate_uuid())
def _create_bw_limit_rule_obj(self):
@ -46,32 +48,150 @@ class QosLinuxbridgeAgentDriverTestCase(base.BaseTestCase):
rule_obj.obj_reset_changes()
return rule_obj
def _create_dscp_marking_rule_obj(self):
rule_obj = rule.QosDscpMarkingRule()
rule_obj.id = uuidutils.generate_uuid()
rule_obj.dscp_mark = DSCP_VALUE
rule_obj.obj_reset_changes()
return rule_obj
def _create_fake_port(self, policy_id):
return {'qos_policy_id': policy_id,
'network_qos_policy_id': None,
'device': 'fake_tap'}
def test_create_rule(self):
def _dscp_mark_chain_name(self, device):
return "qos-o%s" % device[3:]
def _dscp_postrouting_rule(self, device):
return ("-m physdev --physdev-in %s --physdev-is-bridged "
"-j $qos-o%s") % (device, device[3:])
def _dscp_rule(self, dscp_mark_value):
return "-j DSCP --set-dscp %s" % dscp_mark_value
def _dscp_rule_tag(self, device):
return "dscp-%s" % device
def test_create_bandwidth_limit(self):
with mock.patch.object(
tc_lib.TcCommand, "set_filters_bw_limit"
) as set_bw_limit:
self.qos_driver.create_bandwidth_limit(self.port, self.rule)
self.qos_driver.create_bandwidth_limit(self.port,
self.rule_bw_limit)
set_bw_limit.assert_called_once_with(
self.rule.max_kbps, self.rule.max_burst_kbps,
self.rule_bw_limit.max_kbps, self.rule_bw_limit.max_burst_kbps,
)
def test_update_rule(self):
def test_update_bandwidth_limit(self):
with mock.patch.object(
tc_lib.TcCommand, "update_filters_bw_limit"
) as update_bw_limit:
self.qos_driver.update_bandwidth_limit(self.port, self.rule)
self.qos_driver.update_bandwidth_limit(self.port,
self.rule_bw_limit)
update_bw_limit.assert_called_once_with(
self.rule.max_kbps, self.rule.max_burst_kbps,
self.rule_bw_limit.max_kbps, self.rule_bw_limit.max_burst_kbps,
)
def test_delete_rule(self):
def test_delete_bandwidth_limit(self):
with mock.patch.object(
tc_lib.TcCommand, "delete_filters_bw_limit"
) as delete_bw_limit:
self.qos_driver.delete_bandwidth_limit(self.port)
delete_bw_limit.assert_called_once_with()
def test_create_dscp_marking(self):
expected_calls = [
mock.call.add_chain(
self._dscp_mark_chain_name(self.port['device'])),
mock.call.add_rule(
"POSTROUTING",
self._dscp_postrouting_rule(self.port['device'])),
mock.call.add_rule(
self._dscp_mark_chain_name(self.port['device']),
self._dscp_rule(DSCP_VALUE),
tag=self._dscp_rule_tag(self.port['device'])
)
]
with mock.patch.object(
self.qos_driver, "iptables_manager") as iptables_manager:
iptables_manager.ip4['mangle'] = mock.Mock()
iptables_manager.ip6['mangle'] = mock.Mock()
self.qos_driver.create_dscp_marking(
self.port, self.rule_dscp_marking)
iptables_manager.ipv4['mangle'].assert_has_calls(expected_calls)
iptables_manager.ipv6['mangle'].assert_has_calls(expected_calls)
def test_update_dscp_marking(self):
expected_calls = [
mock.call.clear_rules_by_tag(
self._dscp_rule_tag(self.port['device'])),
mock.call.add_chain(
self._dscp_mark_chain_name(self.port['device'])),
mock.call.add_rule(
"POSTROUTING",
self._dscp_postrouting_rule(self.port['device'])),
mock.call.add_rule(
self._dscp_mark_chain_name(self.port['device']),
self._dscp_rule(DSCP_VALUE),
tag=self._dscp_rule_tag(self.port['device'])
)
]
with mock.patch.object(
self.qos_driver, "iptables_manager") as iptables_manager:
iptables_manager.ip4['mangle'] = mock.Mock()
iptables_manager.ip6['mangle'] = mock.Mock()
self.qos_driver.update_dscp_marking(
self.port, self.rule_dscp_marking)
iptables_manager.ipv4['mangle'].assert_has_calls(expected_calls)
iptables_manager.ipv6['mangle'].assert_has_calls(expected_calls)
def test_delete_dscp_marking_chain_empty(self):
dscp_chain_name = self._dscp_mark_chain_name(self.port['device'])
expected_calls = [
mock.call.clear_rules_by_tag(
self._dscp_rule_tag(self.port['device'])),
mock.call.remove_chain(
dscp_chain_name),
mock.call.remove_rule(
"POSTROUTING",
self._dscp_postrouting_rule(self.port['device']))
]
with mock.patch.object(
self.qos_driver, "iptables_manager") as iptables_manager:
iptables_manager.ip4['mangle'] = mock.Mock()
iptables_manager.ip6['mangle'] = mock.Mock()
iptables_manager.get_chain = mock.Mock(return_value=[])
self.qos_driver.delete_dscp_marking(self.port)
iptables_manager.ipv4['mangle'].assert_has_calls(expected_calls)
iptables_manager.ipv6['mangle'].assert_has_calls(expected_calls)
iptables_manager.get_chain.assert_has_calls([
mock.call("mangle", dscp_chain_name, ip_version=4),
mock.call("mangle", dscp_chain_name, ip_version=6)
])
def test_delete_dscp_marking_chain_not_empty(self):
dscp_chain_name = self._dscp_mark_chain_name(self.port['device'])
expected_calls = [
mock.call.clear_rules_by_tag(
self._dscp_rule_tag(self.port['device'])),
]
with mock.patch.object(
self.qos_driver, "iptables_manager") as iptables_manager:
iptables_manager.ip4['mangle'] = mock.Mock()
iptables_manager.ip6['mangle'] = mock.Mock()
iptables_manager.get_chain = mock.Mock(
return_value=["some other rule"])
self.qos_driver.delete_dscp_marking(self.port)
iptables_manager.ipv4['mangle'].assert_has_calls(expected_calls)
iptables_manager.ipv6['mangle'].assert_has_calls(expected_calls)
iptables_manager.get_chain.assert_has_calls([
mock.call("mangle", dscp_chain_name, ip_version=4),
mock.call("mangle", dscp_chain_name, ip_version=6)
])
iptables_manager.ipv4['mangle'].remove_chain.assert_not_called()
iptables_manager.ipv4['mangle'].remove_rule.assert_not_called()

View File

@ -0,0 +1,6 @@
---
prelude: >
The LinuxBridge agent now supports QoS DSCP marking.
features:
- The LinuxBridge agent can now configure DSCP marking for packets outgoing
for ports with QoS policy.