Ingress bandwidth limit rule in Linuxbridge agent

Add support for QoS ingress bandwidth limiting in
linuxbridge agent.
It uses traffic shaping done by tc with tbf qdisc.

DocImpact: Ingress bandwidth limit in QoS supported by
           Linuxbridge agent

Change-Id: Id495b302d31f5527db3e45b51517bc53153e7fc2
Partial-Bug: #1560961
This commit is contained in:
Sławek Kapłoński 2017-06-19 22:13:37 +00:00
parent 05c22d6199
commit da646496e3
6 changed files with 167 additions and 58 deletions

View File

@ -285,7 +285,7 @@ point of view)
+----------------------+----------------+----------------+----------------+
| Rule \ Backend | Open vSwitch | SR-IOV | Linux Bridge |
+----------------------+----------------+----------------+----------------+
| Bandwidth Limit | Egress/Ingress | Egress (1) | Egress |
| Bandwidth Limit | Egress/Ingress | Egress (1) | Egress/Ingress |
+----------------------+----------------+----------------+----------------+
| Minimum Bandwidth | - | Egress | - |
+----------------------+----------------+----------------+----------------+
@ -350,11 +350,19 @@ value.
Linux bridge
~~~~~~~~~~~~
The Linux bridge implementation relies on the new tc_lib functions:
The Linux bridge implementation relies on the new tc_lib functions.
* set_bw_limit
* update_bw_limit
* delete_bw_limit
For egress bandwidth limit rule:
* set_filters_bw_limit
* update_filters_bw_limit
* delete_filters_bw_limit
For ingress bandwidth limit rule:
* set_tbf_bw_limit
* update_tbf_bw_limit
* delete_tbf_bw_limit
The ingress bandwidth limit is configured on the tap port by setting a simple
`tc-tbf <http://linux.die.net/man/8/tc-tbf>`_ queueing discipline (qdisc) on the

View File

@ -43,6 +43,7 @@ class QosLinuxbridgeAgentDriver(qos.QosLinuxAgentDriver):
def initialize(self):
LOG.info(_LI("Initializing Linux bridge QoS extension"))
self.iptables_manager = iptables_manager.IptablesManager(use_ipv6=True)
self.tbf_latency = cfg.CONF.QOS.tbf_latency
def _dscp_chain_name(self, direction, device):
return iptables_manager.get_chain_name(
@ -61,22 +62,35 @@ class QosLinuxbridgeAgentDriver(qos.QosLinuxAgentDriver):
@log_helpers.log_method_call
def create_bandwidth_limit(self, port, rule):
tc_wrapper = self._get_tc_wrapper(port)
tc_wrapper.set_filters_bw_limit(
rule.max_kbps, self._get_egress_burst_value(rule)
)
if rule.direction == const.INGRESS_DIRECTION:
tc_wrapper.set_tbf_bw_limit(
rule.max_kbps, rule.max_burst_kbps, self.tbf_latency)
else:
tc_wrapper.set_filters_bw_limit(
rule.max_kbps, self._get_egress_burst_value(rule)
)
@log_helpers.log_method_call
def update_bandwidth_limit(self, port, rule):
tc_wrapper = self._get_tc_wrapper(port)
tc_wrapper.update_filters_bw_limit(
rule.max_kbps, self._get_egress_burst_value(rule)
)
if rule.direction == const.INGRESS_DIRECTION:
tc_wrapper.update_tbf_bw_limit(
rule.max_kbps, rule.max_burst_kbps, self.tbf_latency)
else:
tc_wrapper.update_filters_bw_limit(
rule.max_kbps, self._get_egress_burst_value(rule)
)
@log_helpers.log_method_call
def delete_bandwidth_limit(self, port):
tc_wrapper = self._get_tc_wrapper(port)
tc_wrapper.delete_filters_bw_limit()
@log_helpers.log_method_call
def delete_bandwidth_limit_ingress(self, port):
tc_wrapper = self._get_tc_wrapper(port)
tc_wrapper.delete_tbf_bw_limit()
@log_helpers.log_method_call
def create_dscp_marking(self, port, rule):
with self.iptables_manager.defer_apply():

View File

@ -31,7 +31,7 @@ SUPPORTED_RULES = {
qos_consts.MAX_BURST: {
'type:range': [0, constants.DB_INTEGER_MAX_VALUE]},
qos_consts.DIRECTION: {
'type:values': [constants.EGRESS_DIRECTION]}
'type:values': constants.VALID_DIRECTIONS}
},
qos_consts.RULE_TYPE_DSCP_MARKING: {
qos_consts.DSCP_MARK: {'type:values': constants.VALID_DSCP_MARKS}

View File

@ -112,15 +112,10 @@ class _TestBwLimitQoS(BaseQoSRuleTestCase):
number_of_hosts = 1
@staticmethod
def _get_expected_burst_value(limit, direction):
# For egress bandwidth limit this value should be calculated as
# bandwidth_limit * qos_consts.DEFAULT_BURST_RATE
if direction == common_constants.EGRESS_DIRECTION:
return int(
limit * qos_consts.DEFAULT_BURST_RATE
)
else:
return 0
def _get_expected_egress_burst_value(limit):
return int(
limit * qos_consts.DEFAULT_BURST_RATE
)
def _wait_for_bw_rule_removed(self, vm, direction):
# No values are provided when port doesn't have qos policy
@ -176,26 +171,6 @@ class _TestBwLimitQoS(BaseQoSRuleTestCase):
body={'port': {'qos_policy_id': None}})
self._wait_for_bw_rule_removed(vm, self.direction)
class TestBwLimitQoSOvs(_TestBwLimitQoS, base.BaseFullStackTestCase):
l2_agent_type = constants.AGENT_TYPE_OVS
direction_scenarios = [
('ingress', {'direction': common_constants.INGRESS_DIRECTION}),
('egress', {'direction': common_constants.EGRESS_DIRECTION})
]
scenarios = testscenarios.multiply_scenarios(
direction_scenarios, fullstack_utils.get_ovs_interface_scenarios())
def _wait_for_bw_rule_applied(self, vm, limit, burst, direction):
if direction == common_constants.EGRESS_DIRECTION:
utils.wait_until_true(
lambda: vm.bridge.get_egress_bw_limit_for_port(
vm.port.name) == (limit, burst))
elif direction == common_constants.INGRESS_DIRECTION:
utils.wait_until_true(
lambda: vm.bridge.get_ingress_bw_limit_for_port(
vm.port.name) == (limit, burst))
def test_bw_limit_direction_change(self):
# Create port with qos policy attached, with rule self.direction
vm, qos_policy = self._prepare_vm_with_qos_policy(
@ -217,12 +192,65 @@ class TestBwLimitQoSOvs(_TestBwLimitQoS, base.BaseFullStackTestCase):
vm, BANDWIDTH_LIMIT, BANDWIDTH_BURST, self.reverse_direction)
class TestBwLimitQoSOvs(_TestBwLimitQoS, base.BaseFullStackTestCase):
l2_agent_type = constants.AGENT_TYPE_OVS
direction_scenarios = [
('ingress', {'direction': common_constants.INGRESS_DIRECTION}),
('egress', {'direction': common_constants.EGRESS_DIRECTION})
]
scenarios = testscenarios.multiply_scenarios(
direction_scenarios, fullstack_utils.get_ovs_interface_scenarios())
@staticmethod
def _get_expected_burst_value(limit, direction):
# For egress bandwidth limit this value should be calculated as
# bandwidth_limit * qos_consts.DEFAULT_BURST_RATE
if direction == common_constants.EGRESS_DIRECTION:
return TestBwLimitQoSOvs._get_expected_egress_burst_value(limit)
else:
return 0
def _wait_for_bw_rule_applied(self, vm, limit, burst, direction):
if direction == common_constants.EGRESS_DIRECTION:
utils.wait_until_true(
lambda: vm.bridge.get_egress_bw_limit_for_port(
vm.port.name) == (limit, burst))
elif direction == common_constants.INGRESS_DIRECTION:
utils.wait_until_true(
lambda: vm.bridge.get_ingress_bw_limit_for_port(
vm.port.name) == (limit, burst))
class TestBwLimitQoSLinuxbridge(_TestBwLimitQoS, base.BaseFullStackTestCase):
l2_agent_type = constants.AGENT_TYPE_LINUXBRIDGE
scenarios = [
('egress', {'direction': common_constants.EGRESS_DIRECTION})
('egress', {'direction': common_constants.EGRESS_DIRECTION}),
('ingress', {'direction': common_constants.INGRESS_DIRECTION}),
]
@staticmethod
def _get_expected_burst_value(limit, direction):
# For egress bandwidth limit this value should be calculated as
# bandwidth_limit * qos_consts.DEFAULT_BURST_RATE
if direction == common_constants.EGRESS_DIRECTION:
return TestBwLimitQoSLinuxbridge._get_expected_egress_burst_value(
limit)
else:
return TestBwLimitQoSLinuxbridge._get_expected_ingress_burst_value(
limit)
@staticmethod
def _get_expected_ingress_burst_value(limit):
# calculate expected burst in same way as it's done in tc_lib but
# burst value = 0 so it's always value calculated from kernel's hz
# value
# as in tc_lib.bits_to_kilobits result is rounded up that even
# 1 bit gives 1 kbit same should be added here to expected burst
# value
return int(
float(limit) /
float(linuxbridge_agent_config.DEFAULT_KERNEL_HZ_VALUE) + 1)
def _wait_for_bw_rule_applied(self, vm, limit, burst, direction):
port_name = linuxbridge_agent.LinuxBridgeManager.get_tap_device_name(
vm.neutron_port['id'])
@ -231,8 +259,12 @@ class TestBwLimitQoSLinuxbridge(_TestBwLimitQoS, base.BaseFullStackTestCase):
linuxbridge_agent_config.DEFAULT_KERNEL_HZ_VALUE,
namespace=vm.host.host_namespace
)
utils.wait_until_true(
lambda: tc.get_filters_bw_limits() == (limit, burst))
if direction == common_constants.EGRESS_DIRECTION:
utils.wait_until_true(
lambda: tc.get_filters_bw_limits() == (limit, burst))
elif direction == common_constants.INGRESS_DIRECTION:
utils.wait_until_true(
lambda: tc.get_tbf_bw_limits() == (limit, burst))
class _TestDscpMarkingQoS(BaseQoSRuleTestCase):

View File

@ -18,6 +18,7 @@ from oslo_config import cfg
from oslo_utils import uuidutils
from neutron.agent.linux import tc_lib
from neutron.common import constants
from neutron.objects.qos import rule
from neutron.plugins.ml2.drivers.linuxbridge.agent.common import config # noqa
from neutron.plugins.ml2.drivers.linuxbridge.agent.extension_drivers import (
@ -36,15 +37,19 @@ 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_bw_limit = self._create_bw_limit_rule_obj()
self.rule_egress_bw_limit = self._create_bw_limit_rule_obj(
constants.EGRESS_DIRECTION)
self.rule_ingress_bw_limit = self._create_bw_limit_rule_obj(
constants.INGRESS_DIRECTION)
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):
def _create_bw_limit_rule_obj(self, direction):
rule_obj = rule.QosBandwidthLimitRule()
rule_obj.id = uuidutils.generate_uuid()
rule_obj.max_kbps = 2
rule_obj.max_burst_kbps = 200
rule_obj.direction = direction
rule_obj.obj_reset_changes()
return rule_obj
@ -73,32 +78,77 @@ class QosLinuxbridgeAgentDriverTestCase(base.BaseTestCase):
def _dscp_rule_tag(self, device):
return "dscp-%s" % device
def test_create_bandwidth_limit(self):
def test_create_egress_bandwidth_limit(self):
with mock.patch.object(
tc_lib.TcCommand, "set_filters_bw_limit"
) as set_bw_limit:
) as set_filters_bw_limit, mock.patch.object(
tc_lib.TcCommand, "set_tbf_bw_limit"
) as set_tbf_limit:
self.qos_driver.create_bandwidth_limit(self.port,
self.rule_bw_limit)
set_bw_limit.assert_called_once_with(
self.rule_bw_limit.max_kbps, self.rule_bw_limit.max_burst_kbps,
self.rule_egress_bw_limit)
set_filters_bw_limit.assert_called_once_with(
self.rule_egress_bw_limit.max_kbps,
self.rule_egress_bw_limit.max_burst_kbps,
)
set_tbf_limit.assert_not_called()
def test_create_ingress_bandwidth_limit(self):
with mock.patch.object(
tc_lib.TcCommand, "set_filters_bw_limit"
) as set_filters_bw_limit, mock.patch.object(
tc_lib.TcCommand, "set_tbf_bw_limit"
) as set_tbf_limit:
self.qos_driver.create_bandwidth_limit(self.port,
self.rule_ingress_bw_limit)
set_filters_bw_limit.assert_not_called()
set_tbf_limit.assert_called_once_with(
self.rule_ingress_bw_limit.max_kbps,
self.rule_ingress_bw_limit.max_burst_kbps,
TEST_LATENCY_VALUE
)
def test_update_bandwidth_limit(self):
def test_update_egress_bandwidth_limit(self):
with mock.patch.object(
tc_lib.TcCommand, "update_filters_bw_limit"
) as update_bw_limit:
) as update_filters_bw_limit, mock.patch.object(
tc_lib.TcCommand, "update_tbf_bw_limit"
) as update_tbf_bw_limit:
self.qos_driver.update_bandwidth_limit(self.port,
self.rule_bw_limit)
update_bw_limit.assert_called_once_with(
self.rule_bw_limit.max_kbps, self.rule_bw_limit.max_burst_kbps,
self.rule_egress_bw_limit)
update_filters_bw_limit.assert_called_once_with(
self.rule_egress_bw_limit.max_kbps,
self.rule_egress_bw_limit.max_burst_kbps,
)
update_tbf_bw_limit.assert_not_called()
def test_update_ingress_bandwidth_limit(self):
with mock.patch.object(
tc_lib.TcCommand, "update_filters_bw_limit"
) as update_filters_bw_limit, mock.patch.object(
tc_lib.TcCommand, "update_tbf_bw_limit"
) as update_tbf_bw_limit:
self.qos_driver.update_bandwidth_limit(self.port,
self.rule_ingress_bw_limit)
update_filters_bw_limit.assert_not_called()
update_tbf_bw_limit.assert_called_once_with(
self.rule_egress_bw_limit.max_kbps,
self.rule_egress_bw_limit.max_burst_kbps,
TEST_LATENCY_VALUE
)
def test_delete_bandwidth_limit(self):
with mock.patch.object(
tc_lib.TcCommand, "delete_filters_bw_limit"
) as delete_bw_limit:
) as delete_filters_bw_limit:
self.qos_driver.delete_bandwidth_limit(self.port)
delete_bw_limit.assert_called_once_with()
delete_filters_bw_limit.assert_called_once_with()
def test_delete_ingress_bandwidth_limit(self):
with mock.patch.object(
tc_lib.TcCommand, "delete_tbf_bw_limit"
) as delete_tbf_bw_limit:
self.qos_driver.delete_bandwidth_limit_ingress(self.port)
delete_tbf_bw_limit.assert_called_once_with()
def test_create_dscp_marking(self):
expected_calls = [

View File

@ -0,0 +1,5 @@
---
prelude: >
Linuxbridge L2 agent supports ingress bandwidth limit.
features:
- The linuxbridge L2 agent now supports bi-directional bandwidth limiting.