From da646496e3a48d78c13a015b56c187ce9ac9b5ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C5=82awek=20Kap=C5=82o=C5=84ski?= Date: Mon, 19 Jun 2017 22:13:37 +0000 Subject: [PATCH] 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 --- doc/source/devref/quality_of_service.rst | 18 +++- .../agent/extension_drivers/qos_driver.py | 26 +++-- .../qos/drivers/linuxbridge/driver.py | 2 +- neutron/tests/fullstack/test_qos.py | 96 ++++++++++++------- .../extension_drivers/test_qos_driver.py | 78 ++++++++++++--- ...in-linuxbridge-agent-50a2dad610401474.yaml | 5 + 6 files changed, 167 insertions(+), 58 deletions(-) create mode 100644 releasenotes/notes/ingress-bandwidth-limit-in-linuxbridge-agent-50a2dad610401474.yaml diff --git a/doc/source/devref/quality_of_service.rst b/doc/source/devref/quality_of_service.rst index 5aaa11ff53f..3c55b2c710d 100644 --- a/doc/source/devref/quality_of_service.rst +++ b/doc/source/devref/quality_of_service.rst @@ -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 `_ queueing discipline (qdisc) on the diff --git a/neutron/plugins/ml2/drivers/linuxbridge/agent/extension_drivers/qos_driver.py b/neutron/plugins/ml2/drivers/linuxbridge/agent/extension_drivers/qos_driver.py index 7a09799c83b..4e8849442e8 100644 --- a/neutron/plugins/ml2/drivers/linuxbridge/agent/extension_drivers/qos_driver.py +++ b/neutron/plugins/ml2/drivers/linuxbridge/agent/extension_drivers/qos_driver.py @@ -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(): diff --git a/neutron/services/qos/drivers/linuxbridge/driver.py b/neutron/services/qos/drivers/linuxbridge/driver.py index 7beeedbb0cc..17eac760dca 100644 --- a/neutron/services/qos/drivers/linuxbridge/driver.py +++ b/neutron/services/qos/drivers/linuxbridge/driver.py @@ -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} diff --git a/neutron/tests/fullstack/test_qos.py b/neutron/tests/fullstack/test_qos.py index 64e84fd4460..65b0d4c21e3 100644 --- a/neutron/tests/fullstack/test_qos.py +++ b/neutron/tests/fullstack/test_qos.py @@ -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): diff --git a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/extension_drivers/test_qos_driver.py b/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/extension_drivers/test_qos_driver.py index 46ec14eb8e6..52f9d9d7d70 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/extension_drivers/test_qos_driver.py +++ b/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/extension_drivers/test_qos_driver.py @@ -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 = [ diff --git a/releasenotes/notes/ingress-bandwidth-limit-in-linuxbridge-agent-50a2dad610401474.yaml b/releasenotes/notes/ingress-bandwidth-limit-in-linuxbridge-agent-50a2dad610401474.yaml new file mode 100644 index 00000000000..69a745ee6b8 --- /dev/null +++ b/releasenotes/notes/ingress-bandwidth-limit-in-linuxbridge-agent-50a2dad610401474.yaml @@ -0,0 +1,5 @@ +--- +prelude: > + Linuxbridge L2 agent supports ingress bandwidth limit. +features: + - The linuxbridge L2 agent now supports bi-directional bandwidth limiting.