Add support for QoS for LinuxBridge agent

There is a new QoS extension driver for the linuxbridge agent
being added. This driver provides support for QoS configuring
on the linuxbridge agent.
This patch introduces two new config options for Linuxbridge agent:
kernel_hz - HZ value of host kernel,
tbf_latency - value of latency in tbf qdisc to calculate size of queue.

Co-Authored-By: vikram.choudhary <vikram.choudhary@huawei.com>

Change-Id: I457ca2569b5d4a916ba09e71040505cd0ad3257b
Closes-Bug: #1500012
Closes-Bug: #1550514
DocImpact Update agent configuration to show settings related to
          QoS and bandwidth limiting
This commit is contained in:
Sławek Kapłoński 2015-10-17 12:54:32 +02:00
parent 7b0e25d0cd
commit 45410d76be
16 changed files with 701 additions and 19 deletions

View File

@ -283,13 +283,15 @@ with them.
Agent backends
~~~~~~~~~~~~~~
At the moment, QoS is supported by Open vSwitch and SR-IOV ml2 drivers.
At the moment, QoS is supported by Open vSwitch, SR-IOV and Linux bridge
ml2 drivers.
Each agent backend defines a QoS driver that implements the QosAgentDriver
interface:
* Open vSwitch (QosOVSAgentDriver);
* SR-IOV (QosSRIOVAgentDriver).
* SR-IOV (QosSRIOVAgentDriver);
* Linux bridge (QosLinuxbridgeAgentDriver).
Open vSwitch
@ -326,6 +328,22 @@ to 1 Mbps only. If the limit is set to something that does not divide to 1000
kbps chunks, then the effective limit is rounded to the nearest integer Mbps
value.
Linux bridge
~~~~~~~~~~~~
The Linux bridge implementation relies on the new tc_lib functions:
* set_bw_limit
* update_bw_limit
* delete_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
port. It requires a value of HZ parameter configured in kernel on the host.
This value is neccessary to calculate the minimal burst value which is set in
tc. Details about how it is calculated can be found in
`http://unix.stackexchange.com/a/100797`_. This solution is similar to Open
vSwitch implementation.
Configuration
-------------
@ -379,6 +397,11 @@ Additions to ovs_lib to set bandwidth limits on ports are covered in:
* neutron.tests.functional.agent.test_ovs_lib
New functional tests for tc_lib to set bandwidth limits on ports are in:
* neutron.tests.functional.agent.linux.test_tc_lib
API tests
~~~~~~~~~

View File

@ -18,3 +18,8 @@ bridge: CommandFilter, bridge, root
ip: IpFilter, ip, root
find: RegExpFilter, find, root, find, /sys/class/net, -maxdepth, 1, -type, l, -printf, %.*
ip_exec: IpNetnsExecFilter, ip, root
# tc commands needed for QoS support
tc_replace_tbf: RegExpFilter, tc, root, tc, qdisc, replace, dev, .+, root, tbf, rate, .+, latency, .+, burst, .+
tc_delete: RegExpFilter, tc, root, tc, qdisc, del, dev, .+, root
tc_show: RegExpFilter, tc, root, tc, qdisc, show, dev, .+

View File

@ -0,0 +1,155 @@
# Copyright 2016 OVH SAS
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import re
from neutron._i18n import _
from neutron.agent.linux import ip_lib
from neutron.common import exceptions
SI_BASE = 1000
IEC_BASE = 1024
LATENCY_UNIT = "ms"
BW_LIMIT_UNIT = "kbit" # kilobits per second in tc's notation
BURST_UNIT = "kbit" # kilobits in tc's notation
# Those are RATES (bits per second) and SIZE (bytes) unit names from tc manual
UNITS = {
"k": 1,
"m": 2,
"g": 3,
"t": 4
}
class InvalidKernelHzValue(exceptions.NeutronException):
message = _("Kernel HZ value %(value)s is not valid. This value must be "
"greater than 0.")
class InvalidUnit(exceptions.NeutronException):
message = _("Unit name '%(unit)s' is not valid.")
def convert_to_kilobits(value, base):
value = value.lower()
if "bit" in value:
input_in_bits = True
value = value.replace("bit", "")
else:
input_in_bits = False
value = value.replace("b", "")
# if it is now bare number then it is in bits, so we return it simply
if value.isdigit():
value = int(value)
if input_in_bits:
return bits_to_kilobits(value, base)
else:
bits_value = bytes_to_bits(value)
return bits_to_kilobits(bits_value, base)
unit = value[-1:]
if unit not in UNITS.keys():
raise InvalidUnit(unit=unit)
val = int(value[:-1])
if input_in_bits:
bits_value = val * (base ** UNITS[unit])
else:
bits_value = bytes_to_bits(val * (base ** UNITS[unit]))
return bits_to_kilobits(bits_value, base)
def bytes_to_bits(value):
return value * 8
def bits_to_kilobits(value, base):
#NOTE(slaweq): round up that even 1 bit will give 1 kbit as a result
return int((value + (base - 1)) / base)
class TcCommand(ip_lib.IPDevice):
def __init__(self, name, kernel_hz, namespace=None):
if kernel_hz <= 0:
raise InvalidKernelHzValue(value=kernel_hz)
super(TcCommand, self).__init__(name, namespace=namespace)
self.kernel_hz = kernel_hz
def _execute_tc_cmd(self, cmd, **kwargs):
cmd = ['tc'] + cmd
ip_wrapper = ip_lib.IPWrapper(self.namespace)
return ip_wrapper.netns.execute(cmd, run_as_root=True, **kwargs)
def get_bw_limits(self):
return self._get_tbf_limits()
def set_bw_limit(self, bw_limit, burst_limit, latency_value):
return self._replace_tbf_qdisc(bw_limit, burst_limit, latency_value)
def update_bw_limit(self, bw_limit, burst_limit, latency_value):
return self._replace_tbf_qdisc(bw_limit, burst_limit, latency_value)
def delete_bw_limit(self):
cmd = ['qdisc', 'del', 'dev', self.name, 'root']
# Return_code=2 is fine because it means
# "RTNETLINK answers: No such file or directory" what is fine when we
# are trying to delete qdisc
return self._execute_tc_cmd(cmd, extra_ok_codes=[2])
def get_burst_value(self, bw_limit, burst_limit):
min_burst_value = self._get_min_burst_value(bw_limit)
return max(min_burst_value, burst_limit)
def _get_min_burst_value(self, bw_limit):
# bw_limit [kbit] / HZ [1/s] = burst [kbit]
return float(bw_limit) / float(self.kernel_hz)
def _get_tbf_limits(self):
cmd = ['qdisc', 'show', 'dev', self.name]
cmd_result = self._execute_tc_cmd(cmd)
if not cmd_result:
return None, None
pattern = re.compile(
r"qdisc (\w+) \w+: \w+ refcnt \d rate (\w+) burst (\w+) \w*"
)
m = pattern.match(cmd_result)
if not m:
return None, None
qdisc_name = m.group(1)
if qdisc_name != "tbf":
return None, None
#NOTE(slaweq): because tc is giving bw limit in SI units
# we need to calculate it as 1000bit = 1kbit:
bw_limit = convert_to_kilobits(m.group(2), SI_BASE)
#NOTE(slaweq): because tc is giving burst limit in IEC units
# we need to calculate it as 1024bit = 1kbit:
burst_limit = convert_to_kilobits(m.group(3), IEC_BASE)
return bw_limit, burst_limit
def _replace_tbf_qdisc(self, bw_limit, burst_limit, latency_value):
burst = "%s%s" % (
self.get_burst_value(bw_limit, burst_limit), BURST_UNIT)
latency = "%s%s" % (latency_value, LATENCY_UNIT)
rate_limit = "%s%s" % (bw_limit, BW_LIMIT_UNIT)
cmd = [
'qdisc', 'replace', 'dev', self.name,
'root', 'tbf',
'rate', rate_limit,
'latency', latency,
'burst', burst
]
return self._execute_tc_cmd(cmd)

View File

@ -97,6 +97,10 @@ class CommonAgentLoop(service.Service):
heartbeat = loopingcall.FixedIntervalLoopingCall(
self._report_state)
heartbeat.start(interval=report_interval)
# The initialization is complete; we can start receiving messages
self.connection.consume_in_threads()
self.daemon_loop()
def stop(self, graceful=True):
@ -152,7 +156,8 @@ class CommonAgentLoop(service.Service):
consumers = self.mgr.get_rpc_consumers()
self.connection = agent_rpc.create_consumers(self.endpoints,
self.topic,
consumers)
consumers,
start_listening=False)
def init_extension_manager(self, connection):
ext_manager.register_opts(cfg.CONF)

View File

@ -19,7 +19,8 @@ from neutron._i18n import _
DEFAULT_BRIDGE_MAPPINGS = []
DEFAULT_INTERFACE_MAPPINGS = []
DEFAULT_VXLAN_GROUP = '224.0.0.1'
DEFAULT_KERNEL_HZ_VALUE = 250 # [Hz]
DEFAULT_TC_TBF_LATENCY = 50 # [ms]
vxlan_opts = [
cfg.BoolOpt('enable_vxlan', default=True,
@ -62,6 +63,19 @@ bridge_opts = [
help=_("List of <physical_network>:<physical_bridge>")),
]
qos_options = [
cfg.IntOpt('kernel_hz', default=DEFAULT_KERNEL_HZ_VALUE,
help=_("Value of host kernel tick rate (hz) for calculating "
"minimum burst value in bandwidth limit rules for "
"a port with QoS. See kernel configuration file for "
"HZ value and tc-tbf manual for more information.")),
cfg.IntOpt('tbf_latency', default=DEFAULT_TC_TBF_LATENCY,
help=_("Value of latency (ms) for calculating size of queue "
"for a port with QoS. See tc-tbf manual for more "
"information."))
]
cfg.CONF.register_opts(vxlan_opts, "VXLAN")
cfg.CONF.register_opts(bridge_opts, "LINUX_BRIDGE")
cfg.CONF.register_opts(qos_options, "QOS")

View File

@ -0,0 +1,60 @@
# Copyright 2016 OVH SAS
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
from oslo_log import helpers as log_helpers
from oslo_log import log
from neutron._i18n import _LI
from neutron.agent.l2.extensions import qos
from neutron.agent.linux import tc_lib
from neutron.plugins.ml2.drivers.linuxbridge.mech_driver import (
mech_linuxbridge)
LOG = log.getLogger(__name__)
class QosLinuxbridgeAgentDriver(qos.QosAgentDriver):
SUPPORTED_RULES = (
mech_linuxbridge.LinuxbridgeMechanismDriver.supported_qos_rule_types
)
def initialize(self):
LOG.info(_LI("Initializing Linux bridge QoS extension"))
@log_helpers.log_method_call
def create_bandwidth_limit(self, port, rule):
tc_wrapper = self._get_tc_wrapper(port)
tc_wrapper.set_bw_limit(
rule.max_kbps, rule.max_burst_kbps, cfg.CONF.QOS.tbf_latency
)
@log_helpers.log_method_call
def update_bandwidth_limit(self, port, rule):
tc_wrapper = self._get_tc_wrapper(port)
tc_wrapper.update_bw_limit(
rule.max_kbps, rule.max_burst_kbps, cfg.CONF.QOS.tbf_latency
)
@log_helpers.log_method_call
def delete_bandwidth_limit(self, port):
tc_wrapper = self._get_tc_wrapper(port)
tc_wrapper.delete_bw_limit()
def _get_tc_wrapper(self, port):
return tc_lib.TcCommand(
port['device'],
cfg.CONF.QOS.kernel_hz,
)

View File

@ -18,6 +18,7 @@ from neutron.common import constants
from neutron.extensions import portbindings
from neutron.plugins.common import constants as p_constants
from neutron.plugins.ml2.drivers import mech_agent
from neutron.services.qos import qos_consts
class LinuxbridgeMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase):
@ -30,6 +31,8 @@ class LinuxbridgeMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase):
network.
"""
supported_qos_rule_types = [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT]
def __init__(self):
sg_enabled = securitygroups_rpc.is_firewall_enabled()
super(LinuxbridgeMechanismDriver, self).__init__(

View File

@ -114,10 +114,14 @@ class ML2ConfigFixture(ConfigFixture):
super(ML2ConfigFixture, self).__init__(
env_desc, host_desc, temp_dir, base_filename='ml2_conf.ini')
mechanism_drivers = 'openvswitch,linuxbridge'
if self.env_desc.l2_pop:
mechanism_drivers += ',l2population'
self.config.update({
'ml2': {
'tenant_network_types': tenant_network_types,
'mechanism_drivers': self.mechanism_drivers,
'mechanism_drivers': mechanism_drivers,
},
'ml2_type_vlan': {
'network_vlan_ranges': 'physnet1:1000:2999',
@ -134,16 +138,6 @@ class ML2ConfigFixture(ConfigFixture):
self.config['ml2']['extension_drivers'] =\
qos_ext.QOS_EXT_DRIVER_ALIAS
@property
def mechanism_drivers(self):
mechanism_drivers = set(['openvswitch'])
for host in self.host_desc:
if host.l2_agent_type == constants.AGENT_TYPE_LINUXBRIDGE:
mechanism_drivers.add('linuxbridge')
if self.env_desc.l2_pop:
mechanism_drivers.add('l2population')
return ','.join(mechanism_drivers)
class OVSConfigFixture(ConfigFixture):
@ -226,6 +220,12 @@ class LinuxBridgeConfigFixture(ConfigFixture):
'l2_population': str(self.env_desc.l2_pop),
}
})
if env_desc.qos:
self.config.update({
'AGENT': {
'extensions': 'qos'
}
})
if self.env_desc.tunneling_enabled:
self.config.update({
'LINUX_BRIDGE': {

View File

@ -13,39 +13,77 @@
# under the License.
from oslo_utils import uuidutils
import testscenarios
from neutron.agent.common import ovs_lib
from neutron.agent.linux import bridge_lib
from neutron.agent.linux import tc_lib
from neutron.agent.linux import utils
from neutron.common import constants
from neutron.services.qos import qos_consts
from neutron.tests.fullstack import base
from neutron.tests.fullstack.resources import environment
from neutron.tests.fullstack.resources import machine
from neutron.plugins.ml2.drivers.linuxbridge.agent.common import \
config as linuxbridge_agent_config
from neutron.plugins.ml2.drivers.linuxbridge.agent import \
linuxbridge_neutron_agent as linuxbridge_agent
from neutron.plugins.ml2.drivers.openvswitch.mech_driver import \
mech_openvswitch as mech_ovs
load_tests = testscenarios.load_tests_apply_scenarios
BANDWIDTH_LIMIT = 500
BANDWIDTH_BURST = 100
def _wait_for_rule_applied(vm, limit, burst):
def _wait_for_rule_applied_ovs_agent(vm, limit, burst):
utils.wait_until_true(
lambda: vm.bridge.get_egress_bw_limit_for_port(
vm.port.name) == (limit, burst))
def _wait_for_rule_applied_linuxbridge_agent(vm, limit, burst):
port_name = linuxbridge_agent.LinuxBridgeManager.get_tap_device_name(
vm.neutron_port['id'])
tc = tc_lib.TcCommand(
port_name,
linuxbridge_agent_config.DEFAULT_KERNEL_HZ_VALUE,
namespace=vm.host.host_namespace
)
utils.wait_until_true(
lambda: tc.get_bw_limits() == (limit, burst))
def _wait_for_rule_applied(vm, limit, burst):
if isinstance(vm.bridge, ovs_lib.OVSBridge):
_wait_for_rule_applied_ovs_agent(vm, limit, burst)
if isinstance(vm.bridge, bridge_lib.BridgeDevice):
_wait_for_rule_applied_linuxbridge_agent(vm, limit, burst)
def _wait_for_rule_removed(vm):
# No values are provided when port doesn't have qos policy
_wait_for_rule_applied(vm, None, None)
class TestQoSWithOvsAgent(base.BaseFullStackTestCase):
class TestQoSWithL2Agent(base.BaseFullStackTestCase):
scenarios = [
("ovs", {'l2_agent_type': constants.AGENT_TYPE_OVS}),
("linuxbridge", {'l2_agent_type': constants.AGENT_TYPE_LINUXBRIDGE})
]
def setUp(self):
host_desc = [environment.HostDescription(l3_agent=False)]
host_desc = [environment.HostDescription(
l3_agent=False,
l2_agent_type=self.l2_agent_type)]
env_desc = environment.EnvironmentDescription(qos=True)
env = environment.Environment(env_desc, host_desc)
super(TestQoSWithOvsAgent, self).setUp(env)
super(TestQoSWithL2Agent, self).setUp(env)
def _create_qos_policy(self):
return self.safe_client.create_qos_policy(

View File

@ -0,0 +1,69 @@
# Copyright (c) 2016 OVH SAS
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log as logging
from neutron.agent.linux import ip_lib
from neutron.agent.linux import tc_lib
from neutron.tests.functional import base as functional_base
LOG = logging.getLogger(__name__)
TEST_HZ_VALUE = 250
LATENCY = 50
BW_LIMIT = 1024
BURST = 512
DEV_NAME = "test_tap"
MAC_ADDRESS = "fa:16:3e:01:01:01"
class TcLibTestCase(functional_base.BaseSudoTestCase):
def setUp(self):
super(TcLibTestCase, self).setUp()
self.create_device()
self.tc = tc_lib.TcCommand(DEV_NAME, TEST_HZ_VALUE)
def create_device(self):
"""Create a tuntap with the specified attributes.
The device is cleaned up at the end of the test.
"""
ip = ip_lib.IPWrapper()
tap_device = ip.add_tuntap(DEV_NAME)
self.addCleanup(tap_device.link.delete)
tap_device.link.set_address(MAC_ADDRESS)
tap_device.link.set_up()
def test_bandwidth_limit(self):
self.tc.set_bw_limit(BW_LIMIT, BURST, LATENCY)
bw_limit, burst = self.tc.get_bw_limits()
self.assertEqual(BW_LIMIT, bw_limit)
self.assertEqual(BURST, burst)
new_bw_limit = BW_LIMIT + 500
new_burst = BURST + 50
self.tc.update_bw_limit(new_bw_limit, new_burst, LATENCY)
bw_limit, burst = self.tc.get_bw_limits()
self.assertEqual(new_bw_limit, bw_limit)
self.assertEqual(new_burst, burst)
self.tc.delete_bw_limit()
bw_limit, burst = self.tc.get_bw_limits()
self.assertIsNone(bw_limit)
self.assertIsNone(burst)

View File

@ -0,0 +1,217 @@
# Copyright 2016 OVH SAS
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from neutron.agent.linux import tc_lib
from neutron.tests import base
DEVICE_NAME = "tap_device"
KERNEL_HZ_VALUE = 1000
BW_LIMIT = 2000 # [kbps]
BURST = 100 # [kbit]
LATENCY = 50 # [ms]
TC_OUTPUT = (
'qdisc tbf 8011: root refcnt 2 rate %(bw)skbit burst %(burst)skbit '
'lat 50.0ms \n') % {'bw': BW_LIMIT, 'burst': BURST}
class BaseUnitConversionTest(object):
def test_convert_to_kilobits_bare_value(self):
value = "1000"
expected_value = 8 # kbit
self.assertEqual(
expected_value,
tc_lib.convert_to_kilobits(value, self.base_unit)
)
def test_convert_to_kilobits_bytes_value(self):
value = "1000b"
expected_value = 8 # kbit
self.assertEqual(
expected_value,
tc_lib.convert_to_kilobits(value, self.base_unit)
)
def test_convert_to_kilobits_bits_value(self):
value = "1000bit"
expected_value = tc_lib.bits_to_kilobits(1000, self.base_unit)
self.assertEqual(
expected_value,
tc_lib.convert_to_kilobits(value, self.base_unit)
)
def test_convert_to_kilobits_megabytes_value(self):
value = "1m"
expected_value = tc_lib.bits_to_kilobits(
self.base_unit ** 2 * 8, self.base_unit)
self.assertEqual(
expected_value,
tc_lib.convert_to_kilobits(value, self.base_unit)
)
def test_convert_to_kilobits_megabits_value(self):
value = "1mbit"
expected_value = tc_lib.bits_to_kilobits(
self.base_unit ** 2, self.base_unit)
self.assertEqual(
expected_value,
tc_lib.convert_to_kilobits(value, self.base_unit)
)
def test_convert_to_bytes_wrong_unit(self):
value = "1Zbit"
self.assertRaises(
tc_lib.InvalidUnit,
tc_lib.convert_to_kilobits, value, self.base_unit
)
def test_bytes_to_bits(self):
test_values = [
(0, 0), # 0 bytes should be 0 bits
(1, 8) # 1 byte should be 8 bits
]
for input_bytes, expected_bits in test_values:
self.assertEqual(
expected_bits, tc_lib.bytes_to_bits(input_bytes)
)
class TestSIUnitConversions(BaseUnitConversionTest, base.BaseTestCase):
base_unit = tc_lib.SI_BASE
def test_bits_to_kilobits(self):
test_values = [
(0, 0), # 0 bites should be 0 kilobites
(1, 1), # 1 bit should be 1 kilobit
(999, 1), # 999 bits should be 1 kilobit
(1000, 1), # 1000 bits should be 1 kilobit
(1001, 2) # 1001 bits should be 2 kilobits
]
for input_bits, expected_kilobits in test_values:
self.assertEqual(
expected_kilobits,
tc_lib.bits_to_kilobits(input_bits, self.base_unit)
)
class TestIECUnitConversions(BaseUnitConversionTest, base.BaseTestCase):
base_unit = tc_lib.IEC_BASE
def test_bits_to_kilobits(self):
test_values = [
(0, 0), # 0 bites should be 0 kilobites
(1, 1), # 1 bit should be 1 kilobit
(1023, 1), # 1023 bits should be 1 kilobit
(1024, 1), # 1024 bits should be 1 kilobit
(1025, 2) # 1025 bits should be 2 kilobits
]
for input_bits, expected_kilobits in test_values:
self.assertEqual(
expected_kilobits,
tc_lib.bits_to_kilobits(input_bits, self.base_unit)
)
class TestTcCommand(base.BaseTestCase):
def setUp(self):
super(TestTcCommand, self).setUp()
self.tc = tc_lib.TcCommand(DEVICE_NAME, KERNEL_HZ_VALUE)
self.bw_limit = "%s%s" % (BW_LIMIT, tc_lib.BW_LIMIT_UNIT)
self.burst = "%s%s" % (BURST, tc_lib.BURST_UNIT)
self.latency = "%s%s" % (LATENCY, tc_lib.LATENCY_UNIT)
self.execute = mock.patch('neutron.agent.common.utils.execute').start()
def test_check_kernel_hz_lower_then_zero(self):
self.assertRaises(
tc_lib.InvalidKernelHzValue,
tc_lib.TcCommand, DEVICE_NAME, 0
)
self.assertRaises(
tc_lib.InvalidKernelHzValue,
tc_lib.TcCommand, DEVICE_NAME, -100
)
def test_get_bw_limits(self):
self.execute.return_value = TC_OUTPUT
bw_limit, burst_limit = self.tc.get_bw_limits()
self.assertEqual(BW_LIMIT, bw_limit)
self.assertEqual(BURST, burst_limit)
def test_get_bw_limits_when_wrong_qdisc(self):
output = TC_OUTPUT.replace("tbf", "different_qdisc")
self.execute.return_value = output
bw_limit, burst_limit = self.tc.get_bw_limits()
self.assertIsNone(bw_limit)
self.assertIsNone(burst_limit)
def test_get_bw_limits_when_wrong_units(self):
output = TC_OUTPUT.replace("kbit", "Xbit")
self.execute.return_value = output
self.assertRaises(tc_lib.InvalidUnit, self.tc.get_bw_limits)
def test_set_bw_limit(self):
self.tc.set_bw_limit(BW_LIMIT, BURST, LATENCY)
self.execute.assert_called_once_with(
["tc", "qdisc", "replace", "dev", DEVICE_NAME,
"root", "tbf", "rate", self.bw_limit,
"latency", self.latency,
"burst", self.burst],
run_as_root=True,
check_exit_code=True,
log_fail_as_error=True,
extra_ok_codes=None
)
def test_update_bw_limit(self):
self.tc.update_bw_limit(BW_LIMIT, BURST, LATENCY)
self.execute.assert_called_once_with(
["tc", "qdisc", "replace", "dev", DEVICE_NAME,
"root", "tbf", "rate", self.bw_limit,
"latency", self.latency,
"burst", self.burst],
run_as_root=True,
check_exit_code=True,
log_fail_as_error=True,
extra_ok_codes=None
)
def test_delete_bw_limit(self):
self.tc.delete_bw_limit()
self.execute.assert_called_once_with(
["tc", "qdisc", "del", "dev", DEVICE_NAME, "root"],
run_as_root=True,
check_exit_code=True,
log_fail_as_error=True,
extra_ok_codes=[2]
)
def test_burst_value_when_burst_bigger_then_minimal(self):
result = self.tc.get_burst_value(BW_LIMIT, BURST)
self.assertEqual(BURST, result)
def test_burst_value_when_burst_smaller_then_minimal(self):
result = self.tc.get_burst_value(BW_LIMIT, 0)
self.assertEqual(2, result)
def test__get_min_burst_value_in_bits(self):
result = self.tc._get_min_burst_value(BW_LIMIT)
#if input is 2000kbit and kernel_hz is configured to 1000 then
# min_burst should be 2 kbit
self.assertEqual(2, result)

View File

@ -0,0 +1,79 @@
# Copyright 2016 OVH SAS
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from oslo_config import cfg
from oslo_utils import uuidutils
from neutron.agent.linux import tc_lib
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 (
qos_driver)
from neutron.tests import base
TEST_LATENCY_VALUE = 100
class QosLinuxbridgeAgentDriverTestCase(base.BaseTestCase):
def setUp(self):
super(QosLinuxbridgeAgentDriverTestCase, self).setUp()
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.port = self._create_fake_port(uuidutils.generate_uuid())
def _create_bw_limit_rule_obj(self):
rule_obj = rule.QosBandwidthLimitRule()
rule_obj.id = uuidutils.generate_uuid()
rule_obj.max_kbps = 2
rule_obj.max_burst_kbps = 200
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):
with mock.patch.object(
tc_lib.TcCommand, "set_bw_limit"
) as set_bw_limit:
self.qos_driver.create_bandwidth_limit(self.port, self.rule)
set_bw_limit.assert_called_once_with(
self.rule.max_kbps, self.rule.max_burst_kbps,
TEST_LATENCY_VALUE
)
def test_update_rule(self):
with mock.patch.object(
tc_lib.TcCommand, "update_bw_limit"
) as update_bw_limit:
self.qos_driver.update_bandwidth_limit(self.port, self.rule)
update_bw_limit.assert_called_once_with(
self.rule.max_kbps, self.rule.max_burst_kbps,
TEST_LATENCY_VALUE
)
def test_delete_rule(self):
with mock.patch.object(
tc_lib.TcCommand, "delete_bw_limit"
) as delete_bw_limit:
self.qos_driver.delete_bandwidth_limit(self.port)
delete_bw_limit.assert_called_once_with()

View File

@ -0,0 +1,13 @@
---
prelude: >
The LinuxBridge agent now supports QoS bandwidth limiting.
features:
- The LinuxBridge agent can now configure basic bandwidth limiting
QoS rules set for ports and networks.
It introduces two new config options for LinuxBridge agent.
First is 'kernel_hz' option which is value of host kernel HZ
setting. It is necessary for proper calculation of minimum burst
value in tbf qdisc setting.
Second is 'tbf_latency' which is value of latency to be configured
in tc-tbf setting. Details about this option can be found in
`tc-tbf manual <http://linux.die.net/man/8/tc-tbf>`_.

View File

@ -113,6 +113,7 @@ neutron.agent.l2.extensions =
neutron.qos.agent_drivers =
ovs = neutron.plugins.ml2.drivers.openvswitch.agent.extension_drivers.qos_driver:QosOVSAgentDriver
sriov = neutron.plugins.ml2.drivers.mech_sriov.agent.extension_drivers.qos_driver:QosSRIOVAgentDriver
linuxbridge = neutron.plugins.ml2.drivers.linuxbridge.agent.extension_drivers.qos_driver:QosLinuxbridgeAgentDriver
neutron.agent.linux.pd_drivers =
dibbler = neutron.agent.linux.dibbler:PDDibbler
neutron.services.external_dns_drivers =