Revert "Linux Bridge: driver support for QoS egress minimum bandwidth"

This reverts commit 84b3ae3ae9.

The logic was incorrect[1]. We cannot achieve QoS egress minimum bandwidth of Linuxbridge by the patch. I also think that the issue is not solved by small patch and we must consider deeply.

[1]: https://bugs.launchpad.net/neutron/+bug/1662582

Change-Id: Id4703b5c63876f16e31b6805cd147b5840a4a591
This commit is contained in:
Hirofumi Ichihara 2017-02-09 12:59:04 +00:00 committed by Rodolfo Alonso Hernandez
parent 67da6a3122
commit e3063496cf
13 changed files with 425 additions and 1288 deletions

View File

@ -357,44 +357,17 @@ 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 * set_bw_limit
* delete_bw * update_bw_limit
* delete_bw_limit
Only egress direction traffic shaping, from the instance point of view, is The ingress bandwidth limit is configured on the tap port by setting a simple
implemented. Traffic shaping is done by a classful Traffic Control qdisq `tc-tbf <http://linux.die.net/man/8/tc-tbf>`_ queueing discipline (qdisc) on the
called Class Based Queueing port. It requires a value of HZ parameter configured in kernel on the host.
(`Classful Queueing Disciplines <http://lartc.org/howto/lartc.qdisc.classful.html>`_). This value is necessary to calculate the minimal burst value which is set in
This shaping algorithm is implemented by tc. Details about how it is calculated can be found in
`tc-htb <https://linux.die.net/man/8/tc-htb>`_, replacing the former one used `here <http://unix.stackexchange.com/a/100797>`_. This solution is similar to Open
`tc-tbf <http://linux.die.net/man/8/tc-tbf>`_. vSwitch implementation.
Traffic shaping is executed only for interface egress traffic. Because the
traffic coming from the instance is considered as ingress traffic by the
interface, shaping is not possible. To solve this, a new element is introduced:
`Intermediate Functional Block <http://linux-ip.net/gl/tc-filters/tc-filters-node3.html>`_,
a pseudo network interface used to redirect all the ingress traffic from the TAP
port to the egress IFB queue. The QoS rules are applied on this IFB
interface associated to the TAP port::
+-----------------+
| [Instance veth] |
+-----------------+
| (egress traffic, instance point of view)
|
| +--------------------------+
\ / [tap port] | [IFB] \ /
+-------------------------------+ | +-------------------------------+
| ingress queue | egress queue | | | ingress queue | egress queue |
| (filter: | | | | | (QoS rules, |
| redir IFB) | | | | | tc-htb) |
+-------------------------------+ | +-------------------------------+
| | | shaped
+--------------------------------+ | traffic
\ /
Traffic in the tap port is redirected (mirrored) to the IFB using a Traffic
Control filter
(`Filter Actions <http://linux-ip.net/gl/tc-filters/tc-filters-node2.html>`_).
QoS driver design QoS driver design
----------------- -----------------

View File

@ -20,12 +20,9 @@ find: RegExpFilter, find, root, find, /sys/class/net, -maxdepth, 1, -type, l, -p
ip_exec: IpNetnsExecFilter, ip, root ip_exec: IpNetnsExecFilter, ip, root
# tc commands needed for QoS support # tc commands needed for QoS support
tc_add_qdisc: RegExpFilter, tc, root, tc, qdisc, add, dev, .+, (root|parent .+), handle, .+, htb tc_replace_tbf: RegExpFilter, tc, root, tc, qdisc, replace, dev, .+, root, tbf, rate, .+, latency, .+, burst, .+
tc_add_qdisc_ingress: RegExpFilter, tc, root, tc, qdisc, add, dev, .+, ingress, handle, .+ tc_add_ingress: RegExpFilter, tc, root, tc, qdisc, add, dev, .+, ingress, handle, .+
tc_delete: RegExpFilter, tc, root, tc, qdisc, del, dev, .+, .+
tc_show_qdisc: RegExpFilter, tc, root, tc, qdisc, show, dev, .+ tc_show_qdisc: RegExpFilter, tc, root, tc, qdisc, show, dev, .+
tc_del_qdisc: RegExpFilter, tc, root, tc, qdisc, del, dev, .+, (root|ingress|parent .+) tc_show_filters: RegExpFilter, tc, root, tc, filter, show, dev, .+, parent, .+
tc_add_class: RegExpFilter, tc, root, tc, class, replace, dev, .+, parent, .+, classid, .+, .+, rate, .+ tc_add_filter: RegExpFilter, tc, root, tc, filter, add, dev, .+, parent, .+, protocol, all, prio, .+, basic, police, rate, .+, burst, .+, mtu, .+, drop
tc_add_class_max: RegExpFilter, tc, root, tc, class, replace, dev, .+, parent, .+, classid, .+, .+, rate, .+, ceil, .+, burst, .+
tc_show_class: RegExpFilter, tc, root, tc, class, show, dev, .+
tc_show_filter: RegExpFilter, tc, root, tc, filter, show, dev, .+, parent, .+
tc_add_filter_ifb: RegExpFilter, tc, root, tc, filter, add, dev, .+, parent, .+, protocol, all, u32, match, u32, 0, 0, action, mirred, egress, redirect, dev, .+

View File

@ -205,15 +205,6 @@ class IPWrapper(SubProcessBase):
self._as_root([], 'link', ('add', name, 'type', 'dummy')) self._as_root([], 'link', ('add', name, 'type', 'dummy'))
return IPDevice(name, namespace=self.namespace) return IPDevice(name, namespace=self.namespace)
def add_ifb(self, name):
"""Create a Linux IFB type interface with the given name."""
self._as_root([], 'link', ('add', name, 'type', 'ifb'))
return IPDevice(name, namespace=self.namespace)
def del_ifb(self, name):
"""Delete a Linux IFB type interface with the given name."""
self._as_root([], 'link', ('del', name))
def ensure_namespace(self, name): def ensure_namespace(self, name):
if not self.netns.exists(name): if not self.netns.exists(name):
ip = self.netns.add(name) ip = self.netns.add(name)

View File

@ -13,31 +13,22 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import collections
import math
import re import re
from neutron_lib import exceptions from neutron_lib import exceptions
from oslo_log import log as logging
from neutron._i18n import _ from neutron._i18n import _
from neutron.agent.linux import ip_lib from neutron.agent.linux import ip_lib
from neutron.common import constants
from neutron.services.qos import qos_consts from neutron.services.qos import qos_consts
LOG = logging.getLogger(__name__) INGRESS_QDISC_ID = "ffff:"
MAX_MTU_VALUE = 65535
ROOT_QDISC = "root"
INGRESS_QDISC = "ingress"
INGRESS_QDISC_HEX = "ffff:fff1"
INGRESS_QDISC_HANDLE = "ffff:"
QDISC_TYPE_HTB = "htb"
QDISC_TYPE_DEFAULT = "pfifo_fast"
SI_BASE = 1000 SI_BASE = 1000
IEC_BASE = 1024 IEC_BASE = 1024
LATENCY_UNIT = "ms"
BW_LIMIT_UNIT = "kbit" # kilobits per second in tc's notation BW_LIMIT_UNIT = "kbit" # kilobits per second in tc's notation
BURST_UNIT = "kbit" # kilobits in tc's notation BURST_UNIT = "kbit" # kilobits in tc's notation
@ -49,32 +40,21 @@ UNITS = {
"t": 4 "t": 4
} }
filters_pattern = re.compile(r"police \w+ rate (\w+) burst (\w+)")
tbf_pattern = re.compile(
r"qdisc (\w+) \w+: \w+ refcnt \d rate (\w+) burst (\w+) \w*")
class InvalidKernelHzValue(exceptions.NeutronException):
message = _("Kernel HZ value %(value)s is not valid. This value must be "
"greater than 0.")
class InvalidUnit(exceptions.NeutronException): class InvalidUnit(exceptions.NeutronException):
message = _("Unit name '%(unit)s' is not valid.") message = _("Unit name '%(unit)s' is not valid.")
class InvalidPolicyClassParameters(exceptions.NeutronException): def convert_to_kilobits(value, base):
message = _("'rate' or 'ceil' parameters must be defined")
def kilobits_to_bits(value, base):
return value * base
def bits_to_kilobits(value, base):
return int(math.ceil(float(value) / base))
def bytes_to_bits(value):
return value * 8
def bits_to_bytes(value):
return int(value / 8)
def convert_to_kilo(value, base):
value = value.lower() value = value.lower()
if "bit" in value: if "bit" in value:
input_in_bits = True input_in_bits = True
@ -101,8 +81,23 @@ def convert_to_kilo(value, base):
return bits_to_kilobits(bits_value, base) 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): 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): def _execute_tc_cmd(self, cmd, **kwargs):
cmd = ['tc'] + cmd cmd = ['tc'] + cmd
ip_wrapper = ip_lib.IPWrapper(self.namespace) ip_wrapper = ip_lib.IPWrapper(self.namespace)
@ -116,248 +111,131 @@ class TcCommand(ip_lib.IPDevice):
rate to ensure that limit for TCP traffic will work well rate to ensure that limit for TCP traffic will work well
""" """
if not burst_limit: if not burst_limit:
return int(float(bw_limit) * qos_consts.DEFAULT_BURST_RATE) return float(bw_limit) * qos_consts.DEFAULT_BURST_RATE
return burst_limit return burst_limit
def set_bw(self, max, burst, min, direction): def get_filters_bw_limits(self, qdisc_id=INGRESS_QDISC_ID):
max = kilobits_to_bits(max, SI_BASE) if max else max cmd = ['filter', 'show', 'dev', self.name, 'parent', qdisc_id]
burst = (bits_to_bytes(kilobits_to_bits(burst, IEC_BASE)) if burst cmd_result = self._execute_tc_cmd(cmd)
else burst) if not cmd_result:
min = kilobits_to_bits(min, SI_BASE) if min else min return None, None
if direction == constants.EGRESS_DIRECTION: for line in cmd_result.split("\n"):
return self._set_ingress_bw(max, burst, min) m = filters_pattern.match(line.strip())
else: if m:
raise NotImplementedError() #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(1), 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(2), IEC_BASE)
return bw_limit, burst_limit
return None, None
def delete_bw(self, direction): def get_tbf_bw_limits(self):
if direction == constants.EGRESS_DIRECTION: cmd = ['qdisc', 'show', 'dev', self.name]
return self._delete_ingress() cmd_result = self._execute_tc_cmd(cmd)
else: if not cmd_result:
raise NotImplementedError() return None, None
m = tbf_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 get_limits(self, direction): def set_filters_bw_limit(self, bw_limit, burst_limit):
if direction == constants.EGRESS_DIRECTION: """Set ingress qdisc and filter for police ingress traffic on device
return self._get_ingress_limits()
else:
raise NotImplementedError()
def _set_ingress_bw(self, max, burst, min): This will allow to police traffic incoming to interface. It
self._add_policy_qdisc(INGRESS_QDISC, INGRESS_QDISC_HANDLE) means that it is fine to limit egress traffic from instance point of
self._configure_ifb(max=max, burst=burst, min=min) view.
"""
#because replace of tc filters is not working properly and it's adding
# new filters each time instead of replacing existing one first old
# ingress qdisc should be deleted and then added new one so update will
# be called to do that:
return self.update_filters_bw_limit(bw_limit, burst_limit)
def _delete_ingress(self): def set_tbf_bw_limit(self, bw_limit, burst_limit, latency_value):
ifb = self._find_mirrored_ifb() """Set token bucket filter qdisc on device
if ifb:
self._del_ifb(ifb)
self._del_policy_qdisc(INGRESS_QDISC)
def _add_policy_qdisc(self, parent, handle, qdisc_type=None, dev=None): This will allow to limit speed of packets going out from interface. It
def check_qdisc(qdisc, qdisc_type, handle, parent, device): means that it is fine to limit ingress traffic from instance point of
if not qdisc or qdisc.get('type') == QDISC_TYPE_DEFAULT: view.
return False """
elif ((qdisc_type and (qdisc.get('type') != qdisc_type or return self._replace_tbf_qdisc(bw_limit, burst_limit, latency_value)
qdisc.get('handle') != handle)) or
(not qdisc_type and qdisc.get('handle') != handle)):
self._del_policy_qdisc(parent, dev=device)
return False
return True
device = str(dev) if dev else self.name def update_filters_bw_limit(self, bw_limit, burst_limit,
qdisc = self._show_policy_qdisc(parent, dev=device) qdisc_id=INGRESS_QDISC_ID):
if check_qdisc(qdisc, qdisc_type, handle, parent, device): self.delete_filters_bw_limit()
return return self._set_filters_bw_limit(bw_limit, burst_limit, qdisc_id)
cmd = ['qdisc', 'add', 'dev', device]
if parent in [ROOT_QDISC, INGRESS_QDISC]:
cmd += [parent]
else:
cmd += ['parent', parent]
cmd += ['handle', handle]
if qdisc_type:
cmd += [qdisc_type]
LOG.debug("Add policy qdisc cmd: %s", cmd) def update_tbf_bw_limit(self, bw_limit, burst_limit, latency_value):
return self._execute_tc_cmd(cmd) return self._replace_tbf_qdisc(bw_limit, burst_limit, latency_value)
def _del_policy_qdisc(self, parent, dev=None): def delete_filters_bw_limit(self):
device = str(dev) if dev else self.name #NOTE(slaweq): For limit traffic egress from instance we need to use
if not self._show_policy_qdisc(parent, dev=device): # qdisc "ingress" because it is ingress traffic from interface POV:
return self._delete_qdisc("ingress")
cmd = ['qdisc', 'del', 'dev', device]
if parent in [ROOT_QDISC, INGRESS_QDISC]:
cmd += [parent]
else:
cmd += ['parent', parent]
LOG.debug("Delete policy qdisc cmd: %s", cmd) def delete_tbf_bw_limit(self):
self._delete_qdisc("root")
def _set_filters_bw_limit(self, bw_limit, burst_limit,
qdisc_id=INGRESS_QDISC_ID):
cmd = ['qdisc', 'add', 'dev', self.name, 'ingress',
'handle', qdisc_id]
self._execute_tc_cmd(cmd) self._execute_tc_cmd(cmd)
return self._add_policy_filter(bw_limit, burst_limit)
def _list_policy_qdisc(self, dev=None): def _delete_qdisc(self, qdisc_name):
device = str(dev) if dev else self.name cmd = ['qdisc', 'del', 'dev', self.name, qdisc_name]
cmd = ['qdisc', 'show', 'dev', device] # Return_code=2 is fine because it means
LOG.debug("List policy qdisc cmd: %s", cmd) # "RTNETLINK answers: No such file or directory" what is fine when we
result = self._execute_tc_cmd(cmd) # are trying to delete qdisc
pat = re.compile(r'qdisc (\w+) (\w+\:) (root|parent (\w*\:\w+))') return self._execute_tc_cmd(cmd, extra_ok_codes=[2])
qdiscs = collections.defaultdict(dict)
for match in (pat.match(line) for line in result.splitlines()
if pat.match(line)):
qdisc = {}
qdisc['type'] = match.groups()[0]
qdisc['handle'] = match.groups()[1]
if match.groups()[2] == ROOT_QDISC:
qdisc['parentid'] = ROOT_QDISC
else:
qdisc['parentid'] = match.groups()[3]
qdisc_ref = INGRESS_QDISC if qdisc['parentid'] == \
INGRESS_QDISC_HEX else qdisc['parentid']
qdiscs[qdisc_ref] = qdisc
LOG.debug("List of policy qdiscs: %s", qdiscs) def _get_tbf_burst_value(self, bw_limit, burst_limit):
return qdiscs min_burst_value = float(bw_limit) / float(self.kernel_hz)
return max(min_burst_value, burst_limit)
def _show_policy_qdisc(self, parent, dev=None): def _replace_tbf_qdisc(self, bw_limit, burst_limit, latency_value):
device = str(dev) if dev else self.name burst = "%s%s" % (
return self._list_policy_qdisc(device).get(parent) self._get_tbf_burst_value(bw_limit, burst_limit), BURST_UNIT)
latency = "%s%s" % (latency_value, LATENCY_UNIT)
def _add_policy_class(self, parent, classid, qdisc_type, rate=None, rate_limit = "%s%s" % (bw_limit, BW_LIMIT_UNIT)
ceil=None, burst=None, dev=None): cmd = [
"""Add new TC class""" 'qdisc', 'replace', 'dev', self.name,
device = str(dev) if dev else self.name 'root', 'tbf',
policy = self._show_policy_class(classid, dev=device) 'rate', rate_limit,
if policy: 'latency', latency,
rate = (kilobits_to_bits(policy['rate'], SI_BASE) if not rate 'burst', burst
else rate) ]
ceil = (kilobits_to_bits(policy['ceil'], SI_BASE) if not ceil
else ceil)
burst = (bits_to_bytes(kilobits_to_bits(policy['burst'], IEC_BASE))
if not burst else burst)
if not rate and not ceil:
raise InvalidPolicyClassParameters
if not rate:
rate = ceil
cmd = self._cmd_policy_class(classid, qdisc_type, rate, device, parent,
ceil, burst)
LOG.debug("Add/replace policy class cmd: %s", cmd)
return self._execute_tc_cmd(cmd) return self._execute_tc_cmd(cmd)
def _cmd_policy_class(self, classid, qdisc_type, rate, device, parent, def _add_policy_filter(self, bw_limit, burst_limit,
ceil, burst): qdisc_id=INGRESS_QDISC_ID):
cmd = ['class', 'replace', 'dev', device] rate_limit = "%s%s" % (bw_limit, BW_LIMIT_UNIT)
if parent: burst = "%s%s" % (
cmd += ['parent', parent] self.get_ingress_qdisc_burst_value(bw_limit, burst_limit),
rate = 8 if rate < 8 else rate BURST_UNIT
cmd += ['classid', classid, qdisc_type, 'rate', rate] )
if ceil: #NOTE(slaweq): it is made in exactly same way how openvswitch is doing
ceil = rate if ceil < rate else ceil # it when configuing ingress traffic limit on port. It can be found in
cmd += ['ceil', ceil] # lib/netdev-linux.c#L4698 in openvswitch sources:
if burst: cmd = [
cmd += ['burst', burst] 'filter', 'add', 'dev', self.name,
return cmd 'parent', qdisc_id, 'protocol', 'all',
'prio', '49', 'basic', 'police',
def _list_policy_class(self, dev=None): 'rate', rate_limit,
device = str(dev) if dev else self.name 'burst', burst,
cmd = ['class', 'show', 'dev', device] 'mtu', MAX_MTU_VALUE,
result = self._execute_tc_cmd(cmd, check_exit_code=False) 'drop']
if not result:
return {}
classes = collections.defaultdict(dict)
pat = re.compile(r'class (\S+) ([0-9a-fA-F]+\:[0-9a-fA-F]+) '
r'(root|parent ([0-9a-fA-F]+\:[0-9a-fA-F]+))'
r'( prio ([0-9]+))* rate (\w+) ceil (\w+) burst (\w+)'
r' cburst (\w+)')
for match in (pat.match(line) for line in result.splitlines()
if pat.match(line)):
_class = {}
_class['type'] = match.groups()[0]
classid = match.groups()[1]
if match.groups()[2] == ROOT_QDISC:
_class['parentid'] = None
else:
_class['parentid'] = match.groups()[3]
_class['prio'] = match.groups()[5]
_class['rate'] = convert_to_kilo(match.groups()[6], SI_BASE)
_class['ceil'] = convert_to_kilo(match.groups()[7], SI_BASE)
_class['burst'] = convert_to_kilo(match.groups()[8], IEC_BASE)
_class['cburst'] = convert_to_kilo(match.groups()[9], IEC_BASE)
classes[classid] = _class
LOG.debug("Policy classes: %s", classes)
return classes
def _show_policy_class(self, classid, dev=None):
device = str(dev) if dev else self.name
return self._list_policy_class(device).get(classid)
def _add_policy_filter(self, parent, protocol, filter, dev=None,
action=None):
"""Add a new filter"""
device = str(dev) if dev else self.name
cmd = ['filter', 'add', 'dev', device, 'parent', parent]
cmd += ['protocol'] + protocol
cmd += filter
if action:
cmd += ['action'] + action
LOG.debug("Add policy filter cmd: %s", cmd)
return self._execute_tc_cmd(cmd) return self._execute_tc_cmd(cmd)
def _list_policy_filters(self, parent, dev=None):
"""Returns the output of showing the filters in a device"""
device = dev if dev else self.name
cmd = ['filter', 'show', 'dev', device, 'parent', parent]
LOG.debug("List policy filter cmd: %s", cmd)
return self._execute_tc_cmd(cmd)
def _add_ifb(self, dev_name):
"""Create a new IFB device"""
ns_ip = ip_lib.IPWrapper(namespace=self.namespace)
if self._find_mirrored_ifb():
ifb = ip_lib.IPDevice(dev_name, namespace=self.namespace)
if not ifb.exists():
self._del_ifb(dev_name=dev_name)
ifb = ns_ip.add_ifb(dev_name)
else:
self._del_ifb(dev_name=dev_name)
ifb = ns_ip.add_ifb(dev_name)
ifb.disable_ipv6()
ifb.link.set_up()
return ifb
def _del_ifb(self, dev_name):
"""Delete a IFB device"""
ns_ip = ip_lib.IPWrapper(namespace=self.namespace)
devices = ns_ip.get_devices(exclude_loopback=True)
for device in (dev for dev in devices if dev.name == dev_name):
ns_ip.del_ifb(device.name)
def _find_mirrored_ifb(self):
"""Return the name of the IFB device where the traffic is mirrored"""
ifb_name = self.name.replace("tap", "ifb")
ifb = ip_lib.IPDevice(ifb_name, namespace=self.namespace)
if not ifb.exists():
return None
return ifb_name
def _configure_ifb(self, max=None, burst=None, min=None):
ifb = self._find_mirrored_ifb()
if not ifb:
ifb = self.name.replace("tap", "ifb")
self._add_ifb(ifb)
protocol = ['all', 'u32']
filter = ['match', 'u32', '0', '0']
action = ['mirred', 'egress', 'redirect', 'dev', '%s' % ifb]
self._add_policy_filter(INGRESS_QDISC_HANDLE, protocol, filter,
dev=self.name, action=action)
self._add_policy_qdisc(ROOT_QDISC, "1:", qdisc_type=QDISC_TYPE_HTB,
dev=ifb)
self._add_policy_class("1:", "1:1", QDISC_TYPE_HTB, rate=min,
ceil=max, burst=burst, dev=ifb)
def _get_ingress_limits(self):
ifb = self._find_mirrored_ifb()
if ifb:
policy = self._show_policy_class("1:1", dev=ifb)
if policy:
return policy['ceil'], policy['burst'], policy['rate']
return None, None, None

View File

@ -724,8 +724,7 @@ def transaction_guard(f):
return inner return inner
def wait_until_true(predicate, timeout=60, sleep=1, exception=None, def wait_until_true(predicate, timeout=60, sleep=1, exception=None):
initial_sleep=0):
""" """
Wait until callable predicate is evaluated as True Wait until callable predicate is evaluated as True
@ -737,7 +736,6 @@ def wait_until_true(predicate, timeout=60, sleep=1, exception=None,
(default) then WaitTimeout exception is raised. (default) then WaitTimeout exception is raised.
""" """
try: try:
eventlet.sleep(initial_sleep)
with eventlet.timeout.Timeout(timeout): with eventlet.timeout.Timeout(timeout):
while not predicate(): while not predicate():
eventlet.sleep(sleep) eventlet.sleep(sleep)

View File

@ -19,6 +19,8 @@ from neutron._i18n import _
DEFAULT_BRIDGE_MAPPINGS = [] DEFAULT_BRIDGE_MAPPINGS = []
DEFAULT_INTERFACE_MAPPINGS = [] DEFAULT_INTERFACE_MAPPINGS = []
DEFAULT_VXLAN_GROUP = '224.0.0.1' DEFAULT_VXLAN_GROUP = '224.0.0.1'
DEFAULT_KERNEL_HZ_VALUE = 250 # [Hz]
DEFAULT_TC_TBF_LATENCY = 50 # [ms]
vxlan_opts = [ vxlan_opts = [
cfg.BoolOpt('enable_vxlan', default=True, cfg.BoolOpt('enable_vxlan', default=True,
@ -74,7 +76,20 @@ bridge_opts = [
help=_("List of <physical_network>:<physical_bridge>")), 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."))
]
def register_linuxbridge_opts(cfg=cfg.CONF): def register_linuxbridge_opts(cfg=cfg.CONF):
cfg.register_opts(vxlan_opts, "VXLAN") cfg.register_opts(vxlan_opts, "VXLAN")
cfg.register_opts(bridge_opts, "LINUX_BRIDGE") cfg.register_opts(bridge_opts, "LINUX_BRIDGE")
cfg.register_opts(qos_options, "QOS")

View File

@ -12,8 +12,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import collections from oslo_config import cfg
from oslo_log import helpers as log_helpers from oslo_log import helpers as log_helpers
from oslo_log import log from oslo_log import log
@ -23,7 +22,6 @@ from neutron.agent.linux import iptables_manager
from neutron.agent.linux import tc_lib from neutron.agent.linux import tc_lib
import neutron.common.constants as const import neutron.common.constants as const
from neutron.services.qos.drivers.linuxbridge import driver from neutron.services.qos.drivers.linuxbridge import driver
from neutron.services.qos import qos_consts
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -42,10 +40,6 @@ class QosLinuxbridgeAgentDriver(qos.QosLinuxAgentDriver):
IPTABLES_DIRECTION_PREFIX = {const.INGRESS_DIRECTION: "i", IPTABLES_DIRECTION_PREFIX = {const.INGRESS_DIRECTION: "i",
const.EGRESS_DIRECTION: "o"} const.EGRESS_DIRECTION: "o"}
def __init__(self):
super(QosLinuxbridgeAgentDriver, self).__init__()
self._port_rules = collections.defaultdict(dict)
def initialize(self): def initialize(self):
LOG.info(_LI("Initializing Linux bridge QoS extension")) LOG.info(_LI("Initializing Linux bridge QoS extension"))
self.iptables_manager = iptables_manager.IptablesManager(use_ipv6=True) self.iptables_manager = iptables_manager.IptablesManager(use_ipv6=True)
@ -66,41 +60,22 @@ class QosLinuxbridgeAgentDriver(qos.QosLinuxAgentDriver):
@log_helpers.log_method_call @log_helpers.log_method_call
def create_bandwidth_limit(self, port, rule): def create_bandwidth_limit(self, port, rule):
self.update_bandwidth_limit(port, rule) tc_wrapper = self._get_tc_wrapper(port)
tc_wrapper.set_filters_bw_limit(
rule.max_kbps, self._get_egress_burst_value(rule)
)
@log_helpers.log_method_call @log_helpers.log_method_call
def update_bandwidth_limit(self, port, rule): def update_bandwidth_limit(self, port, rule):
device = port.get('device') tc_wrapper = self._get_tc_wrapper(port)
port_id = port.get('port_id') tc_wrapper.update_filters_bw_limit(
if not device: rule.max_kbps, self._get_egress_burst_value(rule)
LOG.debug("update_bandwidth_limit was received for port %s but " )
"device was not found. It seems that port is already "
"deleted", port_id)
return
self._port_rules[port_id][qos_consts.RULE_TYPE_BANDWIDTH_LIMIT] = rule
max, burst, min = self._get_port_bw_parameters(port_id)
tc_wrapper = tc_lib.TcCommand(device)
tc_wrapper.set_bw(max, burst, min, const.EGRESS_DIRECTION)
@log_helpers.log_method_call @log_helpers.log_method_call
def delete_bandwidth_limit(self, port): def delete_bandwidth_limit(self, port):
device = port.get('device') tc_wrapper = self._get_tc_wrapper(port)
port_id = port.get('port_id') tc_wrapper.delete_filters_bw_limit()
if not device:
LOG.debug("delete_bandwidth_limit was received for port %s but "
"device was not found. It seems that port is already "
"deleted", port_id)
return
self._port_rules[port_id].pop(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT,
None)
max, burst, min = self._get_port_bw_parameters(port_id)
tc_wrapper = tc_lib.TcCommand(device)
if not min:
tc_wrapper.delete_bw(const.EGRESS_DIRECTION)
else:
tc_wrapper.set_bw(max, burst, min, const.EGRESS_DIRECTION)
@log_helpers.log_method_call @log_helpers.log_method_call
def create_dscp_marking(self, port, rule): def create_dscp_marking(self, port, rule):
@ -170,53 +145,8 @@ class QosLinuxbridgeAgentDriver(qos.QosLinuxAgentDriver):
"mangle", chain_name, ip_version=ip_version) "mangle", chain_name, ip_version=ip_version)
return len(rules_in_chain) == 0 return len(rules_in_chain) == 0
@log_helpers.log_method_call def _get_tc_wrapper(self, port):
def create_minimum_bandwidth(self, port, rule): return tc_lib.TcCommand(
self.update_minimum_bandwidth(port, rule) port['device'],
cfg.CONF.QOS.kernel_hz,
@log_helpers.log_method_call )
def update_minimum_bandwidth(self, port, rule):
device = port.get('device')
port_id = port.get('port_id')
if not device:
LOG.debug("update_minimum_bandwidth was received for port %s but "
"device was not found. It seems that port is already "
"deleted", port_id)
return
self._port_rules[port_id][
qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH] = rule
max, burst, min = self._get_port_bw_parameters(port_id)
tc_wrapper = tc_lib.TcCommand(device)
tc_wrapper.set_bw(max, burst, min, const.EGRESS_DIRECTION)
@log_helpers.log_method_call
def delete_minimum_bandwidth(self, port):
device = port.get('device')
port_id = port.get('port_id')
if not device:
LOG.debug("delete_minimum_bandwidth was received for port %s but "
"device was not found. It seems that port is already "
"deleted", port_id)
return
self._port_rules[port_id].pop(qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH,
None)
max, burst, min = self._get_port_bw_parameters(port_id)
tc_wrapper = tc_lib.TcCommand(device)
if not max and not burst:
tc_wrapper.delete_bw(const.EGRESS_DIRECTION)
else:
tc_wrapper.set_bw(max, burst, min, const.EGRESS_DIRECTION)
def _get_port_bw_parameters(self, port_id):
rules = self._port_rules[port_id]
if not rules:
return None, None, None
rule_min = rules.get(qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH)
rule_limit = rules.get(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
min = rule_min.min_kbps if rule_min else None
max = rule_limit.max_kbps if rule_limit else None
burst = (self._get_egress_burst_value(rule_limit) if rule_limit else
None)
return max, burst, min

View File

@ -176,22 +176,6 @@ class ClientFixture(fixtures.Fixture):
return rule['dscp_marking_rule'] return rule['dscp_marking_rule']
def create_minimum_bandwidth_rule(self, tenant_id, qos_policy_id,
min_bw=None):
rule = {'tenant_id': tenant_id}
if min_bw:
rule['min_kbps'] = min_bw
rule = self.client.create_minimum_bandwidth_rule(
policy=qos_policy_id,
body={'minimum_bandwidth_rule': rule})
self.addCleanup(
_safe_method(self.client.delete_minimum_bandwidth_rule),
rule['minimum_bandwidth_rule']['id'],
qos_policy_id)
return rule['minimum_bandwidth_rule']
def create_trunk(self, tenant_id, port_id, name=None, def create_trunk(self, tenant_id, port_id, name=None,
admin_state_up=None, sub_ports=None): admin_state_up=None, sub_ports=None):
"""Create a trunk via API. """Create a trunk via API.

View File

@ -18,7 +18,6 @@ from neutron_lib import constants
from oslo_utils import uuidutils from oslo_utils import uuidutils
from neutron.agent.linux import tc_lib from neutron.agent.linux import tc_lib
from neutron.common import constants as common_consts
from neutron.common import utils from neutron.common import utils
from neutron.services.qos import qos_consts from neutron.services.qos import qos_consts
from neutron.tests.common.agents import l2_extensions from neutron.tests.common.agents import l2_extensions
@ -28,6 +27,8 @@ from neutron.tests.fullstack.resources import machine
from neutron.tests.fullstack import utils as fullstack_utils from neutron.tests.fullstack import utils as fullstack_utils
from neutron.tests.unit import testlib_api from neutron.tests.unit import testlib_api
from neutron.conf.plugins.ml2.drivers import linuxbridge as \
linuxbridge_agent_config
from neutron.plugins.ml2.drivers.linuxbridge.agent import \ from neutron.plugins.ml2.drivers.linuxbridge.agent import \
linuxbridge_neutron_agent as linuxbridge_agent linuxbridge_neutron_agent as linuxbridge_agent
from neutron.services.qos.drivers.openvswitch import driver as ovs_drv from neutron.services.qos.drivers.openvswitch import driver as ovs_drv
@ -37,22 +38,9 @@ load_tests = testlib_api.module_load_tests
BANDWIDTH_BURST = 100 BANDWIDTH_BURST = 100
BANDWIDTH_LIMIT = 500 BANDWIDTH_LIMIT = 500
MINIMUM_BANDWIDTH = 200
DSCP_MARK = 16 DSCP_MARK = 16
def _check_bw_limits(tc, limit, burst, min):
# NOTE(ralonsoh): once QoS bw limit rule has 'direction' parameter, this
# should be included in the function call. Now EGRESS_DIRECTION is forced.
observed = tc.get_limits(common_consts.EGRESS_DIRECTION)
if not (limit or burst or min):
return observed == (limit, burst, min)
elif not min and (limit or burst):
return observed == (limit, burst, limit)
elif not (limit or burst) and min:
return observed[2] == min
class BaseQoSRuleTestCase(object): class BaseQoSRuleTestCase(object):
of_interface = None of_interface = None
ovsdb_interface = None ovsdb_interface = None
@ -115,7 +103,7 @@ class _TestBwLimitQoS(BaseQoSRuleTestCase):
def _wait_for_bw_rule_removed(self, vm): def _wait_for_bw_rule_removed(self, vm):
# No values are provided when port doesn't have qos policy # No values are provided when port doesn't have qos policy
self._wait_for_bw_rule_applied(vm) self._wait_for_bw_rule_applied(vm, None, None)
def _add_bw_limit_rule(self, limit, burst, qos_policy): def _add_bw_limit_rule(self, limit, burst, qos_policy):
qos_policy_id = qos_policy['id'] qos_policy_id = qos_policy['id']
@ -135,8 +123,7 @@ class _TestBwLimitQoS(BaseQoSRuleTestCase):
BANDWIDTH_LIMIT, BANDWIDTH_BURST)]) BANDWIDTH_LIMIT, BANDWIDTH_BURST)])
bw_rule = qos_policy['rules'][0] bw_rule = qos_policy['rules'][0]
self._wait_for_bw_rule_applied(vm, limit=BANDWIDTH_LIMIT, self._wait_for_bw_rule_applied(vm, BANDWIDTH_LIMIT, BANDWIDTH_BURST)
burst=BANDWIDTH_BURST)
qos_policy_id = qos_policy['id'] qos_policy_id = qos_policy['id']
self.client.delete_bandwidth_limit_rule(bw_rule['id'], qos_policy_id) self.client.delete_bandwidth_limit_rule(bw_rule['id'], qos_policy_id)
@ -150,64 +137,14 @@ class _TestBwLimitQoS(BaseQoSRuleTestCase):
) )
new_rule = self.safe_client.create_bandwidth_limit_rule( new_rule = self.safe_client.create_bandwidth_limit_rule(
self.tenant_id, qos_policy_id, new_limit) self.tenant_id, qos_policy_id, new_limit)
self._wait_for_bw_rule_applied(vm, limit=new_limit, self._wait_for_bw_rule_applied(vm, new_limit, new_expected_burst)
burst=new_expected_burst)
# Update qos policy rule id # Update qos policy rule id
self.client.update_bandwidth_limit_rule( self.client.update_bandwidth_limit_rule(
new_rule['id'], qos_policy_id, new_rule['id'], qos_policy_id,
body={'bandwidth_limit_rule': {'max_kbps': BANDWIDTH_LIMIT, body={'bandwidth_limit_rule': {'max_kbps': BANDWIDTH_LIMIT,
'max_burst_kbps': BANDWIDTH_BURST}}) 'max_burst_kbps': BANDWIDTH_BURST}})
self._wait_for_bw_rule_applied(vm, limit=BANDWIDTH_LIMIT, self._wait_for_bw_rule_applied(vm, BANDWIDTH_LIMIT, BANDWIDTH_BURST)
burst=BANDWIDTH_BURST)
# Remove qos policy from port
self.client.update_port(
vm.neutron_port['id'],
body={'port': {'qos_policy_id': None}})
self._wait_for_bw_rule_removed(vm)
class _TestMinimumBwQoS(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)
def _add_min_bw_rule(self, min, qos_policy):
qos_policy_id = qos_policy['id']
rule = self.safe_client.create_minimum_bandwidth_rule(
self.tenant_id, qos_policy_id, min)
# Make it consistent with GET reply
rule['type'] = qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH
rule['qos_policy_id'] = qos_policy_id
qos_policy['rules'].append(rule)
def test_min_bw_qos_policy_rule_lifecycle(self):
new_min = MINIMUM_BANDWIDTH + 100
# Create port with qos policy attached
vm, qos_policy = self._prepare_vm_with_qos_policy(
[functools.partial(self._add_min_bw_rule, MINIMUM_BANDWIDTH)])
bw_rule = qos_policy['rules'][0]
self._wait_for_bw_rule_applied(vm, min=MINIMUM_BANDWIDTH)
qos_policy_id = qos_policy['id']
self.client.delete_minimum_bandwidth_rule(bw_rule['id'], qos_policy_id)
self._wait_for_bw_rule_removed(vm)
new_rule = self.safe_client.create_minimum_bandwidth_rule(
self.tenant_id, qos_policy_id, new_min)
self._wait_for_bw_rule_applied(vm, min=new_min)
# Update qos policy rule id
self.client.update_minimum_bandwidth_rule(
new_rule['id'], qos_policy_id,
body={'minimum_bandwidth_rule': {'min_kbps': MINIMUM_BANDWIDTH}})
self._wait_for_bw_rule_applied(vm, min=MINIMUM_BANDWIDTH)
# Remove qos policy from port # Remove qos policy from port
self.client.update_port( self.client.update_port(
@ -220,32 +157,25 @@ class TestBwLimitQoSOvs(_TestBwLimitQoS, base.BaseFullStackTestCase):
l2_agent_type = constants.AGENT_TYPE_OVS l2_agent_type = constants.AGENT_TYPE_OVS
scenarios = fullstack_utils.get_ovs_interface_scenarios() scenarios = fullstack_utils.get_ovs_interface_scenarios()
def _wait_for_bw_rule_applied(self, vm, limit=None, burst=None): def _wait_for_bw_rule_applied(self, vm, limit, burst):
utils.wait_until_true( utils.wait_until_true(
lambda: vm.bridge.get_egress_bw_limit_for_port( lambda: vm.bridge.get_egress_bw_limit_for_port(
vm.port.name) == (limit, burst), vm.port.name) == (limit, burst))
initial_sleep=2)
class TestBwLimitQoSLinuxbridge(_TestBwLimitQoS, base.BaseFullStackTestCase): class TestBwLimitQoSLinuxbridge(_TestBwLimitQoS, base.BaseFullStackTestCase):
l2_agent_type = constants.AGENT_TYPE_LINUXBRIDGE l2_agent_type = constants.AGENT_TYPE_LINUXBRIDGE
def _wait_for_bw_rule_applied(self, vm, limit=None, burst=None, min=None): def _wait_for_bw_rule_applied(self, vm, limit, burst):
port_name = linuxbridge_agent.LinuxBridgeManager.get_tap_device_name( port_name = linuxbridge_agent.LinuxBridgeManager.get_tap_device_name(
vm.neutron_port['id']) vm.neutron_port['id'])
tc = tc_lib.TcCommand(port_name, namespace=vm.host.host_namespace) tc = tc_lib.TcCommand(
utils.wait_until_true(lambda: _check_bw_limits(tc, limit, burst, min)) port_name,
linuxbridge_agent_config.DEFAULT_KERNEL_HZ_VALUE,
namespace=vm.host.host_namespace
class TestMinimumBwQoSLinuxbridge(_TestMinimumBwQoS, )
base.BaseFullStackTestCase): utils.wait_until_true(
l2_agent_type = constants.AGENT_TYPE_LINUXBRIDGE lambda: tc.get_filters_bw_limits() == (limit, burst))
def _wait_for_bw_rule_applied(self, vm, limit=None, burst=None, min=None):
port_name = linuxbridge_agent.LinuxBridgeManager.get_tap_device_name(
vm.neutron_port['id'])
tc = tc_lib.TcCommand(port_name, namespace=vm.host.host_namespace)
utils.wait_until_true(lambda: _check_bw_limits(tc, limit, burst, min))
class _TestDscpMarkingQoS(BaseQoSRuleTestCase): class _TestDscpMarkingQoS(BaseQoSRuleTestCase):

View File

@ -17,10 +17,12 @@ from neutron.agent.linux import ip_lib
from neutron.agent.linux import tc_lib from neutron.agent.linux import tc_lib
from neutron.tests.functional import base as functional_base from neutron.tests.functional import base as functional_base
BW_LIMIT = 100 TEST_HZ_VALUE = 250
BURST = 50 LATENCY = 50
BW_MIN = 25 BW_LIMIT = 1024
DIRECTION_EGRESS = 'egress' BURST = 512
BASE_DEV_NAME = "test_tap"
class TcLibTestCase(functional_base.BaseSudoTestCase): class TcLibTestCase(functional_base.BaseSudoTestCase):
@ -36,44 +38,48 @@ class TcLibTestCase(functional_base.BaseSudoTestCase):
self.addCleanup(tap_device.link.delete) self.addCleanup(tap_device.link.delete)
tap_device.link.set_up() tap_device.link.set_up()
def test_bandwidth_limit(self): def test_filters_bandwidth_limit(self):
device_name = "tap_testmax" device_name = "%s_filters" % BASE_DEV_NAME
self.create_device(device_name) self.create_device(device_name)
tc = tc_lib.TcCommand(device_name) tc = tc_lib.TcCommand(device_name, TEST_HZ_VALUE)
tc.set_bw(BW_LIMIT, BURST, None, DIRECTION_EGRESS) tc.set_filters_bw_limit(BW_LIMIT, BURST)
bw_limit, burst, _ = tc.get_limits(DIRECTION_EGRESS) bw_limit, burst = tc.get_filters_bw_limits()
self.assertEqual(BW_LIMIT, bw_limit) self.assertEqual(BW_LIMIT, bw_limit)
self.assertEqual(BURST, burst) self.assertEqual(BURST, burst)
new_bw_limit = BW_LIMIT + 100 new_bw_limit = BW_LIMIT + 500
new_burst = BURST + 50 new_burst = BURST + 50
tc.set_bw(new_bw_limit, new_burst, None, DIRECTION_EGRESS) tc.update_filters_bw_limit(new_bw_limit, new_burst)
bw_limit, burst, _ = tc.get_limits(DIRECTION_EGRESS) bw_limit, burst = tc.get_filters_bw_limits()
self.assertEqual(new_bw_limit, bw_limit) self.assertEqual(new_bw_limit, bw_limit)
self.assertEqual(new_burst, burst) self.assertEqual(new_burst, burst)
tc.delete_bw(DIRECTION_EGRESS) tc.delete_filters_bw_limit()
bw_limit, burst, _ = tc.get_limits(DIRECTION_EGRESS) bw_limit, burst = tc.get_filters_bw_limits()
self.assertIsNone(bw_limit) self.assertIsNone(bw_limit)
self.assertIsNone(burst) self.assertIsNone(burst)
def test_minimum_bandwidth(self): def test_tbf_bandwidth_limit(self):
device_name = "tap_testmin" device_name = "%s_tbf" % BASE_DEV_NAME
self.create_device(device_name) self.create_device(device_name)
tc = tc_lib.TcCommand(device_name) tc = tc_lib.TcCommand(device_name, TEST_HZ_VALUE)
tc.set_bw(None, None, BW_MIN, DIRECTION_EGRESS) tc.set_tbf_bw_limit(BW_LIMIT, BURST, LATENCY)
_, _, bw_min = tc.get_limits(DIRECTION_EGRESS) bw_limit, burst = tc.get_tbf_bw_limits()
self.assertEqual(BW_MIN, bw_min) self.assertEqual(BW_LIMIT, bw_limit)
self.assertEqual(BURST, burst)
new_bw_min = BW_MIN + 50 new_bw_limit = BW_LIMIT + 500
new_burst = BURST + 50
tc.set_bw(None, None, new_bw_min, DIRECTION_EGRESS) tc.update_tbf_bw_limit(new_bw_limit, new_burst, LATENCY)
_, _, bw_min = tc.get_limits(DIRECTION_EGRESS) bw_limit, burst = tc.get_tbf_bw_limits()
self.assertEqual(new_bw_min, bw_min) self.assertEqual(new_bw_limit, bw_limit)
self.assertEqual(new_burst, burst)
tc.delete_bw(DIRECTION_EGRESS) tc.delete_tbf_bw_limit()
_, _, bw_min = tc.get_limits(DIRECTION_EGRESS) bw_limit, burst = tc.get_tbf_bw_limits()
self.assertIsNone(bw_min) self.assertIsNone(bw_limit)
self.assertIsNone(burst)

View File

@ -393,21 +393,6 @@ class TestIpWrapper(base.BaseTestCase):
run_as_root=True, namespace=None, run_as_root=True, namespace=None,
log_fail_as_error=True) log_fail_as_error=True)
def test_add_ifb(self):
ip_lib.IPWrapper().add_ifb('ifb-dummy0')
self.execute.assert_called_once_with([], 'link',
('add', 'ifb-dummy0',
'type', 'ifb'),
run_as_root=True, namespace=None,
log_fail_as_error=True)
def test_del_ifb(self):
ip_lib.IPWrapper().del_ifb('ifb-dummy0')
self.execute.assert_called_once_with([], 'link',
('del', 'ifb-dummy0'),
run_as_root=True, namespace=None,
log_fail_as_error=True)
def test_get_device(self): def test_get_device(self):
dev = ip_lib.IPWrapper(namespace='ns').device('eth0') dev = ip_lib.IPWrapper(namespace='ns').device('eth0')
self.assertEqual(dev.namespace, 'ns') self.assertEqual(dev.namespace, 'ns')

View File

@ -13,69 +13,80 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import math
import mock import mock
import testtools
from neutron.agent.linux import ip_lib
from neutron.agent.linux import tc_lib from neutron.agent.linux import tc_lib
from neutron.services.qos import qos_consts from neutron.services.qos import qos_consts
from neutron.tests import base from neutron.tests import base
DEVICE_NAME = "tap_device" DEVICE_NAME = "tap_device"
KERNEL_HZ_VALUE = 1000
BW_LIMIT = 2000 # [kbps] BW_LIMIT = 2000 # [kbps]
BURST = 100 # [kbit] BURST = 100 # [kbit]
LATENCY = 50 # [ms]
TC_QDISC_OUTPUT = (
'qdisc tbf 8011: root refcnt 2 rate %(bw)skbit burst %(burst)skbit '
'lat 50.0ms \n') % {'bw': BW_LIMIT, 'burst': BURST}
TC_FILTERS_OUTPUT = (
'filter protocol all pref 49152 u32 \nfilter protocol all pref '
'49152 u32 fh 800: ht divisor 1 \nfilter protocol all pref 49152 u32 fh '
'800::800 order 2048 key ht 800 \n match 00000000/00000000 at 0\n '
'police 0x1e rate %(bw)skbit burst %(burst)skbit mtu 2Kb action \n'
'drop overhead 0b \n ref 1 bind 1'
) % {'bw': BW_LIMIT, 'burst': BURST}
class BaseUnitConversionTest(object): class BaseUnitConversionTest(object):
def test_convert_to_kilo_bare_value(self): def test_convert_to_kilobits_bare_value(self):
value = "10000" value = "1000"
expected_value = int(math.ceil(float(80000) / self.base_unit)) # kbit expected_value = 8 # kbit
self.assertEqual( self.assertEqual(
expected_value, expected_value,
tc_lib.convert_to_kilo(value, self.base_unit) tc_lib.convert_to_kilobits(value, self.base_unit)
) )
def test_convert_to_kilo_bytes_value(self): def test_convert_to_kilobits_bytes_value(self):
value = "10000b" value = "1000b"
expected_value = int(math.ceil(float(80000) / self.base_unit)) # kbit expected_value = 8 # kbit
self.assertEqual( self.assertEqual(
expected_value, expected_value,
tc_lib.convert_to_kilo(value, self.base_unit) tc_lib.convert_to_kilobits(value, self.base_unit)
) )
def test_convert_to_kilo_bits_value(self): def test_convert_to_kilobits_bits_value(self):
value = "1000bit" value = "1000bit"
expected_value = int(math.ceil(float(1000) / self.base_unit)) expected_value = tc_lib.bits_to_kilobits(1000, self.base_unit)
self.assertEqual( self.assertEqual(
expected_value, expected_value,
tc_lib.convert_to_kilo(value, self.base_unit) tc_lib.convert_to_kilobits(value, self.base_unit)
) )
def test_convert_to_kilo_megabytes_value(self): def test_convert_to_kilobits_megabytes_value(self):
value = "1m" value = "1m"
expected_value = int(math.ceil(float(self.base_unit ** 2 * 8) / expected_value = tc_lib.bits_to_kilobits(
self.base_unit)) self.base_unit ** 2 * 8, self.base_unit)
self.assertEqual( self.assertEqual(
expected_value, expected_value,
tc_lib.convert_to_kilo(value, self.base_unit) tc_lib.convert_to_kilobits(value, self.base_unit)
) )
def test_convert_to_kilo_megabits_value(self): def test_convert_to_kilobits_megabits_value(self):
value = "1mbit" value = "1mbit"
expected_value = int(math.ceil(float(self.base_unit ** 2) / expected_value = tc_lib.bits_to_kilobits(
self.base_unit)) self.base_unit ** 2, self.base_unit)
self.assertEqual( self.assertEqual(
expected_value, expected_value,
tc_lib.convert_to_kilo(value, self.base_unit) tc_lib.convert_to_kilobits(value, self.base_unit)
) )
def test_convert_to_bytes_wrong_unit(self): def test_convert_to_bytes_wrong_unit(self):
value = "1Zbit" value = "1Zbit"
self.assertRaises( self.assertRaises(
tc_lib.InvalidUnit, tc_lib.InvalidUnit,
tc_lib.convert_to_kilo, value, self.base_unit tc_lib.convert_to_kilobits, value, self.base_unit
) )
def test_bytes_to_bits(self): def test_bytes_to_bits(self):
@ -128,659 +139,166 @@ class TestIECUnitConversions(BaseUnitConversionTest, base.BaseTestCase):
class TestTcCommand(base.BaseTestCase): class TestTcCommand(base.BaseTestCase):
MAX_RATE = 10000
BURST_RATE = 8000
CBURST_RATE = 1500
MIN_RATE = 1500
RATE_LIMIT = 8
DIRECTION_EGRESS = 'egress'
DIRECTION_INGRESS = 'ingress'
DEVICE_NAME = 'tap-test-dev'
IFB_NAME = 'ifb-test-dev'
CLASS_PARENT = '10:'
CLASSID = '10:1'
QDISC_PARENT = '20:2'
QDISC_HANDLE = '30:'
QDISC_ROOT = 'root'
QDISC_INGRESS = 'ingress'
QDISC_INGRESS_HANDLE = 'ffff:'
FILTER_PARENT = CLASS_PARENT
FILTER_PROTOCOL = ['all', 'u32']
FILTER_FILTER = ['match', 'u32', '0', '0']
FILTER_ACTION = ['mirred', 'egress', 'redirect', 'dev', IFB_NAME]
TYPE_HTB = 'htb'
def _call_qdisc_add(self, device, parent, handle, qdisc_type):
cmd = ['tc', 'qdisc', 'add', 'dev', device]
if parent in [self.QDISC_ROOT, self.QDISC_INGRESS]:
cmd += [parent]
else:
cmd += ['parent', parent]
qdisc_type = '' if qdisc_type is None else qdisc_type
cmd += ['handle', handle, qdisc_type]
return cmd
def _call_qdisc_del(self, device, parent):
cmd = ['tc', 'qdisc', 'del', 'dev', device]
if parent in [self.QDISC_ROOT, self.QDISC_INGRESS]:
cmd += [parent]
else:
cmd += ['parent', parent]
return cmd
@staticmethod
def _call_qdisc_show(device):
return ['tc', 'qdisc', 'show', 'dev', device]
def _call_class_replace(self, device, parent, classid, type, rate, ceil,
burst):
cmd = ['class', 'replace', 'dev', device]
if parent:
cmd += ['parent', parent]
rate = self.RATE_LIMIT if rate < self.RATE_LIMIT else rate
cmd += ['classid', classid, type, 'rate', rate]
if ceil:
ceil = rate if ceil < rate else ceil
cmd += ['ceil', ceil]
if burst:
cmd += ['burst', burst]
return cmd
@staticmethod
def _call_class_show(device):
return ['tc', 'class', 'show', 'dev', device]
@staticmethod
def _call_filter_add(device, parent, protocol, filter, action):
cmd = ['tc', 'filter', 'add', 'dev', device, 'parent', parent,
'protocol'] + protocol + filter
if action:
cmd += ['action'] + action
return cmd
@staticmethod
def _call_filter_show(device, parent):
return ['tc', 'filter', 'show', 'dev', device, 'parent', parent]
def setUp(self): def setUp(self):
super(TestTcCommand, self).setUp() super(TestTcCommand, self).setUp()
self.tc = tc_lib.TcCommand(self.DEVICE_NAME) 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() self.execute = mock.patch('neutron.agent.common.utils.execute').start()
def test_set_bw_egress(self): def test_check_kernel_hz_lower_then_zero(self):
with mock.patch.object(self.tc, '_set_ingress_bw') as \ self.assertRaises(
mock_set_ingress_bw: tc_lib.InvalidKernelHzValue,
self.tc.set_bw(self.MAX_RATE, tc_lib.TcCommand, DEVICE_NAME, 0
self.BURST_RATE, )
self.MIN_RATE, self.assertRaises(
self.DIRECTION_EGRESS) tc_lib.InvalidKernelHzValue,
mock_set_ingress_bw.assert_called_once_with( tc_lib.TcCommand, DEVICE_NAME, -100
self.MAX_RATE * tc_lib.SI_BASE, )
(self.BURST_RATE * tc_lib.IEC_BASE) / 8,
self.MIN_RATE * tc_lib.SI_BASE)
def test_set_bw_ingress(self): def test_get_filters_bw_limits(self):
with testtools.ExpectedException(NotImplementedError): self.execute.return_value = TC_FILTERS_OUTPUT
self.tc.set_bw(self.MAX_RATE, self.BURST_RATE, self.MIN_RATE, bw_limit, burst_limit = self.tc.get_filters_bw_limits()
self.DIRECTION_INGRESS) self.assertEqual(BW_LIMIT, bw_limit)
self.assertEqual(BURST, burst_limit)
def test_delete_bw_egress(self): def test_get_filters_bw_limits_when_output_not_match(self):
with mock.patch.object(self.tc, '_delete_ingress') as \ output = (
mock_delete_ingress: "Some different "
self.tc.delete_bw(self.DIRECTION_EGRESS) "output from command:"
mock_delete_ingress.assert_called_once_with() "tc filters show dev XXX parent ffff:"
)
self.execute.return_value = output
bw_limit, burst_limit = self.tc.get_filters_bw_limits()
self.assertIsNone(bw_limit)
self.assertIsNone(burst_limit)
def test_delete_bw_ingress(self): def test_get_filters_bw_limits_when_wrong_units(self):
with testtools.ExpectedException(NotImplementedError): output = TC_FILTERS_OUTPUT.replace("kbit", "Xbit")
self.tc.delete_bw(self.DIRECTION_INGRESS) self.execute.return_value = output
self.assertRaises(tc_lib.InvalidUnit, self.tc.get_filters_bw_limits)
def test_set_ingress_bw(self): def test_get_tbf_bw_limits(self):
with mock.patch.object(self.tc, '_add_policy_qdisc') as \ self.execute.return_value = TC_QDISC_OUTPUT
mock_add_policy_qdisc, \ bw_limit, burst_limit = self.tc.get_tbf_bw_limits()
mock.patch.object(self.tc, '_configure_ifb') as \ self.assertEqual(BW_LIMIT, bw_limit)
mock_configure_ifb: self.assertEqual(BURST, burst_limit)
self.tc._set_ingress_bw(self.MAX_RATE, self.BURST_RATE,
self.MIN_RATE)
mock_add_policy_qdisc.assert_called_once_with(
tc_lib.INGRESS_QDISC, tc_lib.INGRESS_QDISC_HANDLE)
mock_configure_ifb.assert_called_once_with(
max=self.MAX_RATE, burst=self.BURST_RATE,
min=self.MIN_RATE)
def test_delete_ingress_no_ifb(self): def test_get_tbf_bw_limits_when_wrong_qdisc(self):
with mock.patch.object(self.tc, '_find_mirrored_ifb', output = TC_QDISC_OUTPUT.replace("tbf", "different_qdisc")
return_value=None) as mock_find_mirrored_ifb, \ self.execute.return_value = output
mock.patch.object(self.tc, '_del_policy_qdisc') as \ bw_limit, burst_limit = self.tc.get_tbf_bw_limits()
mock_del_policy_qdisc: self.assertIsNone(bw_limit)
self.tc._delete_ingress() self.assertIsNone(burst_limit)
mock_find_mirrored_ifb.assert_called_once_with()
mock_del_policy_qdisc.assert_called_once_with(tc_lib.INGRESS_QDISC)
def test_delete_ingress_with_ifb(self): def test_get_tbf_bw_limits_when_wrong_units(self):
with mock.patch.object(self.tc, '_find_mirrored_ifb', output = TC_QDISC_OUTPUT.replace("kbit", "Xbit")
return_value=self.IFB_NAME) as mock_find_mirrored_ifb, \ self.execute.return_value = output
mock.patch.object(self.tc, '_del_policy_qdisc') as \ self.assertRaises(tc_lib.InvalidUnit, self.tc.get_tbf_bw_limits)
mock_del_policy_qdisc, \
mock.patch.object(self.tc, '_del_ifb') as mock_del_ifb:
self.tc._delete_ingress()
mock_find_mirrored_ifb.assert_called_once_with()
mock_del_policy_qdisc.assert_called_once_with(tc_lib.INGRESS_QDISC)
mock_del_ifb.assert_called_once_with(self.IFB_NAME)
def test_add_policy_qdisc_no_qdisc(self): def test_set_tbf_bw_limit(self):
with mock.patch.object(self.tc, '_show_policy_qdisc', self.tc.set_tbf_bw_limit(BW_LIMIT, BURST, LATENCY)
return_value=None) as \ self.execute.assert_called_once_with(
mock_show_policy_qdisc: ["tc", "qdisc", "replace", "dev", DEVICE_NAME,
self.tc._add_policy_qdisc(self.QDISC_PARENT, self.QDISC_HANDLE) "root", "tbf", "rate", self.bw_limit,
mock_show_policy_qdisc.assert_called_once_with( "latency", self.latency,
self.QDISC_PARENT, dev=self.DEVICE_NAME) "burst", self.burst],
run_as_root=True,
check_exit_code=True,
log_fail_as_error=True,
extra_ok_codes=None
)
def test_add_policy_qdisc_existing_qdisc(self): def test_update_filters_bw_limit(self):
with mock.patch.object(self.tc, '_show_policy_qdisc') as \ self.tc.update_filters_bw_limit(BW_LIMIT, BURST)
mock_show_policy_qdisc, \ self.execute.assert_has_calls([
mock.patch.object(self.tc, '_del_policy_qdisc') as \ mock.call(
mock_del_policy_qdisc: ["tc", "qdisc", "del", "dev", DEVICE_NAME, "ingress"],
qdisc = {'type': self.TYPE_HTB, run_as_root=True,
'handle': self.QDISC_HANDLE, check_exit_code=True,
'parentid': 'parent1'} log_fail_as_error=True,
mock_show_policy_qdisc.return_value = qdisc extra_ok_codes=[2]
self.tc._add_policy_qdisc(self.QDISC_PARENT, ),
self.QDISC_HANDLE, qdisc_type=self.TYPE_HTB) mock.call(
mock_show_policy_qdisc.assert_called_once_with( ['tc', 'qdisc', 'add', 'dev', DEVICE_NAME, "ingress",
self.QDISC_PARENT, dev=self.DEVICE_NAME) "handle", tc_lib.INGRESS_QDISC_ID],
mock_del_policy_qdisc.assert_not_called() run_as_root=True,
check_exit_code=True,
log_fail_as_error=True,
extra_ok_codes=None
),
mock.call(
['tc', 'filter', 'add', 'dev', DEVICE_NAME,
'parent', tc_lib.INGRESS_QDISC_ID, 'protocol', 'all',
'prio', '49', 'basic', 'police',
'rate', self.bw_limit,
'burst', self.burst,
'mtu', tc_lib.MAX_MTU_VALUE,
'drop'],
run_as_root=True,
check_exit_code=True,
log_fail_as_error=True,
extra_ok_codes=None
)]
)
def _add_policy_qdisc_parent_type(self, parent, type): def test_update_tbf_bw_limit(self):
with mock.patch.object(self.tc, '_show_policy_qdisc') as \ self.tc.update_tbf_bw_limit(BW_LIMIT, BURST, LATENCY)
mock_show_policy_qdisc, \ self.execute.assert_called_once_with(
mock.patch.object(self.tc, '_del_policy_qdisc') as \ ["tc", "qdisc", "replace", "dev", DEVICE_NAME,
mock_del_policy_qdisc: "root", "tbf", "rate", self.bw_limit,
qdisc = {'type': 'type1', "latency", self.latency,
'handle': 'handle1', "burst", self.burst],
'parentid': 'parent1'} run_as_root=True,
mock_show_policy_qdisc.return_value = qdisc check_exit_code=True,
self.tc._add_policy_qdisc(parent, self.QDISC_HANDLE, log_fail_as_error=True,
qdisc_type=type) extra_ok_codes=None
mock_show_policy_qdisc.assert_called_once_with( )
parent, dev=self.DEVICE_NAME)
mock_del_policy_qdisc.assert_called_once_with(parent,
dev=self.DEVICE_NAME)
cmd = self._call_qdisc_add(self.DEVICE_NAME, parent,
self.QDISC_HANDLE, type)
self.execute.assert_called_once_with(cmd, check_exit_code=True,
extra_ok_codes=None, log_fail_as_error=True, run_as_root=True)
def test_add_policy_qdisc_root_parent(self): def test_delete_filters_bw_limit(self):
self._add_policy_qdisc_parent_type(self.QDISC_ROOT, self.TYPE_HTB) self.tc.delete_filters_bw_limit()
self.execute.assert_called_once_with(
["tc", "qdisc", "del", "dev", DEVICE_NAME, "ingress"],
run_as_root=True,
check_exit_code=True,
log_fail_as_error=True,
extra_ok_codes=[2]
)
def test_add_policy_qdisc_ingress_parent(self): def test_delete_tbf_bw_limit(self):
self._add_policy_qdisc_parent_type(self.QDISC_INGRESS, self.TYPE_HTB) self.tc.delete_tbf_bw_limit()
self.execute.assert_called_once_with(
def test_add_policy_qdisc_other_parent(self): ["tc", "qdisc", "del", "dev", DEVICE_NAME, "root"],
self._add_policy_qdisc_parent_type(self.QDISC_PARENT, self.TYPE_HTB) run_as_root=True,
check_exit_code=True,
def _add_policy_qdisc_no_qdisc_type(self): log_fail_as_error=True,
self._add_policy_qdisc_parent_type(self.QDISC_PARENT, None) extra_ok_codes=[2]
)
def test_del_policy_qdisc(self):
with mock.patch.object(self.tc, '_show_policy_qdisc',
return_value=True):
self.tc._del_policy_qdisc(self.QDISC_PARENT)
cmd = self._call_qdisc_del(self.DEVICE_NAME, self.QDISC_PARENT)
self.execute.assert_called_once_with(cmd, check_exit_code=True,
extra_ok_codes=None, log_fail_as_error=True, run_as_root=True)
def test_del_policy_qdisc_root_parent(self):
with mock.patch.object(self.tc, '_show_policy_qdisc',
return_value=True):
self.tc._del_policy_qdisc(self.QDISC_ROOT)
cmd = self._call_qdisc_del(self.DEVICE_NAME, self.QDISC_ROOT)
self.execute.assert_called_once_with(cmd, check_exit_code=True,
extra_ok_codes=None, log_fail_as_error=True, run_as_root=True)
def test_del_policy_qdisc_no_qdisc(self):
with mock.patch.object(self.tc, '_show_policy_qdisc',
return_value=False):
self.tc._del_policy_qdisc(self.QDISC_ROOT)
self.execute.assert_not_called()
def test_list_policy_qdisc(self):
qdisc_out = 'qdisc htb 1: root refcnt 2 r2q 10 default 0 '
qdisc_out += 'direct_packets_stat 138 direct_qlen 32\n'
qdisc_out += 'qdisc htb 10: parent 1:1 r2q 10 default 0 '
qdisc_out += 'direct_packets_stat 0 direct_qlen 32\n'
qdisc_out += 'qdisc ingress ffff: parent ffff:fff1 ----------------'
self.execute.return_value = qdisc_out
ret_value = self.tc._list_policy_qdisc()
cmd = self._call_qdisc_show(self.DEVICE_NAME)
self.execute.assert_called_once_with(cmd, check_exit_code=True,
extra_ok_codes=None,
log_fail_as_error=True,
run_as_root=True)
qdiscs = {'1:1': {'handle': '10:',
'type': 'htb',
'parentid': '1:1'},
'root': {'handle': '1:',
'type': 'htb',
'parentid': 'root'},
'ingress': {'handle': 'ffff:',
'type': 'ingress',
'parentid': 'ffff:fff1'}}
self.assertEqual(qdiscs, ret_value)
def test_list_policy_qdisc_no_match(self):
self.execute.return_value = 'no matches'
ret_value = self.tc._list_policy_qdisc()
cmd = self._call_qdisc_show(self.DEVICE_NAME)
self.execute.assert_called_once_with(cmd, check_exit_code=True,
extra_ok_codes=None,
log_fail_as_error=True,
run_as_root=True)
qdiscs = {}
self.assertEqual(qdiscs, ret_value)
def test_show_policy_qdisc(self):
with mock.patch.object(self.tc, '_list_policy_qdisc') as \
mock_list_policy_qdisc:
self.tc._show_policy_qdisc(self.QDISC_PARENT)
mock_list_policy_qdisc.assert_called_once_with(self.DEVICE_NAME)
def test_add_policy_class_existing_class_set_min_bw(self):
with mock.patch.object(self.tc, '_show_policy_class') as \
mock_show_policy_class, \
mock.patch.object(self.tc, '_cmd_policy_class') as \
mock_cmd_policy_class:
classes = {'type': self.TYPE_HTB,
'parentid': self.CLASS_PARENT,
'prio': 0,
'rate': self.MIN_RATE + 1,
'ceil': self.MAX_RATE,
'burst': self.BURST_RATE,
'cburst': self.CBURST_RATE}
mock_show_policy_class.return_value = classes
_min = tc_lib.kilobits_to_bits(self.MIN_RATE, tc_lib.SI_BASE)
_max = tc_lib.kilobits_to_bits(self.MAX_RATE, tc_lib.SI_BASE)
_burst = tc_lib.bits_to_bytes(tc_lib.kilobits_to_bits(
self.BURST_RATE, tc_lib.IEC_BASE))
cmd = self._call_class_replace(self.DEVICE_NAME,
self.CLASS_PARENT, self.CLASSID, self.TYPE_HTB, _min,
None, None)
mock_cmd_policy_class.return_value = cmd
self.tc._add_policy_class(self.CLASS_PARENT, self.CLASSID,
self.TYPE_HTB, rate=_min)
mock_show_policy_class.assert_called_once_with(
self.CLASSID, dev=self.DEVICE_NAME)
mock_cmd_policy_class.assert_called_once_with(self.CLASSID,
self.TYPE_HTB, _min, self.DEVICE_NAME, self.CLASS_PARENT,
_max, _burst)
self.execute.assert_called_once_with(['tc'] + cmd,
check_exit_code=True, extra_ok_codes=None,
log_fail_as_error=True, run_as_root=True)
def test_add_policy_class_existing_class_set_bw_limit(self):
with mock.patch.object(self.tc, '_show_policy_class') as \
mock_show_policy_class, \
mock.patch.object(self.tc, '_cmd_policy_class') as \
mock_cmd_policy_class:
classes = {'type': self.TYPE_HTB,
'parentid': self.CLASS_PARENT,
'prio': 0,
'rate': self.MIN_RATE,
'ceil': self.MAX_RATE + 1,
'burst': self.BURST_RATE + 1,
'cburst': self.CBURST_RATE}
mock_show_policy_class.return_value = classes
_min = tc_lib.kilobits_to_bits(self.MIN_RATE, tc_lib.SI_BASE)
_max = tc_lib.kilobits_to_bits(self.MAX_RATE, tc_lib.SI_BASE)
_burst = tc_lib.bits_to_bytes(tc_lib.kilobits_to_bits(
self.BURST_RATE, tc_lib.IEC_BASE))
cmd = ['tc'] + self._call_class_replace(self.DEVICE_NAME,
self.CLASS_PARENT, self.CLASSID, self.TYPE_HTB, _min,
_max, _burst)
mock_cmd_policy_class.return_value = cmd
self.tc._add_policy_class(self.CLASS_PARENT, self.CLASSID,
self.TYPE_HTB, ceil=_max, burst=_burst)
mock_show_policy_class.assert_called_once_with(
self.CLASSID, dev=self.DEVICE_NAME)
mock_cmd_policy_class.assert_called_once_with(self.CLASSID,
self.TYPE_HTB, _min, self.DEVICE_NAME, self.CLASS_PARENT,
_max, _burst)
self.execute.assert_called_once_with(['tc'] + cmd,
check_exit_code=True, extra_ok_codes=None,
log_fail_as_error=True, run_as_root=True)
def test_add_policy_class_non_existing_class(self):
with mock.patch.object(self.tc, '_show_policy_class',
return_value={}) as mock_show_policy_class, \
mock.patch.object(self.tc, '_cmd_policy_class') as \
mock_cmd_policy_class:
_min = tc_lib.kilobits_to_bits(self.MIN_RATE, tc_lib.SI_BASE)
cmd = ['tc'] + self._call_class_replace(self.DEVICE_NAME,
self.CLASS_PARENT, self.CLASSID, self.TYPE_HTB, _min,
None, None)
mock_cmd_policy_class.return_value = cmd
self.tc._add_policy_class(self.CLASS_PARENT, self.CLASSID,
self.TYPE_HTB, rate=_min)
mock_show_policy_class.assert_called_once_with(
self.CLASSID, dev=self.DEVICE_NAME)
mock_cmd_policy_class.assert_called_once_with(self.CLASSID,
self.TYPE_HTB, _min, self.DEVICE_NAME, self.CLASS_PARENT,
None, None)
self.execute.assert_called_once_with(['tc'] + cmd,
check_exit_code=True, extra_ok_codes=None,
log_fail_as_error=True, run_as_root=True)
def test_add_policy_class_no_rate_no_ceil(self):
with testtools.ExpectedException(tc_lib.InvalidPolicyClassParameters):
self.tc._add_policy_class(self.CLASS_PARENT, self.CLASSID,
self.TYPE_HTB, rate=None, ceil=None)
def test_cmd_policy_class(self):
cmd_out = self.tc._cmd_policy_class(self.CLASSID, self.TYPE_HTB,
self.MIN_RATE, self.DEVICE_NAME,
self.CLASS_PARENT, self.MAX_RATE,
self.BURST_RATE)
cmd_ref = self._call_class_replace(self.DEVICE_NAME, self.CLASS_PARENT,
self.CLASSID, self.TYPE_HTB,
self.MIN_RATE, self.MAX_RATE,
self.BURST_RATE)
self.assertEqual(cmd_ref, cmd_out)
def test_cmd_policy_class_no_parent(self):
cmd_out = self.tc._cmd_policy_class(self.CLASSID, self.TYPE_HTB,
self.MIN_RATE, self.DEVICE_NAME,
None, self.MAX_RATE,
self.BURST_RATE)
cmd_ref = self._call_class_replace(self.DEVICE_NAME, None,
self.CLASSID, self.TYPE_HTB,
self.MIN_RATE, self.MAX_RATE,
self.BURST_RATE)
self.assertEqual(cmd_ref, cmd_out)
def test_cmd_policy_class_rate_less_8(self):
cmd_out = self.tc._cmd_policy_class(self.CLASSID, self.TYPE_HTB,
5, self.DEVICE_NAME,
self.CLASS_PARENT, None, None)
cmd_ref = self._call_class_replace(self.DEVICE_NAME, self.CLASS_PARENT,
self.CLASSID, self.TYPE_HTB,
self.RATE_LIMIT, None, None)
self.assertEqual(cmd_ref, cmd_out)
def test_cmd_policy_class_no_ceil(self):
cmd_out = self.tc._cmd_policy_class(self.CLASSID, self.TYPE_HTB,
self.MIN_RATE, self.DEVICE_NAME,
self.CLASS_PARENT, None,
self.BURST_RATE)
cmd_ref = self._call_class_replace(self.DEVICE_NAME, self.CLASS_PARENT,
self.CLASSID, self.TYPE_HTB,
self.MIN_RATE, None,
self.BURST_RATE)
self.assertEqual(cmd_ref, cmd_out)
def test_cmd_policy_class_no_burst(self):
cmd_out = self.tc._cmd_policy_class(self.CLASSID, self.TYPE_HTB,
self.MIN_RATE, self.DEVICE_NAME,
self.CLASS_PARENT, None, None)
cmd_ref = self._call_class_replace(self.DEVICE_NAME, self.CLASS_PARENT,
self.CLASSID, self.TYPE_HTB,
self.MIN_RATE, None, None)
self.assertEqual(cmd_ref, cmd_out)
def test_list_policy_class(self):
class_out = 'class htb 1:1 root rate 300000bit ceil 300000bit burst '
class_out += '2560b cburst 2688b\n'
class_out += 'class htb 1:10 parent 1:1 prio 0 rate 24000bit ceil '
class_out += '300000bit burst 2560b cburst 2688b\n'
class_out += 'class htb 1:20 parent 1:1 prio 1 rate 24000bit ceil '
class_out += '300000bit burst 2560b cburst 2688b'
self.execute.return_value = class_out
ret_val = self.tc._list_policy_class()
cmd = self._call_class_show(self.DEVICE_NAME)
self.execute.assert_called_once_with(cmd, check_exit_code=False,
extra_ok_codes=None,
log_fail_as_error=True,
run_as_root=True)
expected = {'1:1': {'prio': None, 'burst': 20, 'ceil': 300,
'rate': 300, 'parentid': None, 'cburst': 21,
'type': 'htb'},
'1:10': {'prio': '0', 'burst': 20, 'ceil': 300, 'rate': 24,
'parentid': '1:1', 'cburst': 21, 'type': 'htb'},
'1:20': {'prio': '1', 'burst': 20, 'ceil': 300, 'rate': 24,
'parentid': '1:1', 'cburst': 21, 'type': 'htb'}}
self.assertEqual(expected, ret_val)
def test_show_policy_class(self):
with mock.patch.object(self.tc, '_list_policy_class') as \
mock_list_policy_class:
classes = {self.CLASSID: {'prio': None, 'burst': 20, 'ceil': 300,
'rate': 300, 'parentid': None,
'cburst': 21, 'type': 'htb'}}
mock_list_policy_class.return_value = classes
ret_val = self.tc._show_policy_class(self.CLASSID)
mock_list_policy_class.assert_called_once_with(self.DEVICE_NAME)
self.assertEqual(classes[self.CLASSID], ret_val)
def test_add_policy_filter_with_action(self):
self.tc._add_policy_filter(self.FILTER_PARENT, self.FILTER_PROTOCOL,
self.FILTER_FILTER,
action=self.FILTER_ACTION)
cmd = self._call_filter_add(self.DEVICE_NAME, self.FILTER_PARENT,
self.FILTER_PROTOCOL, self.FILTER_FILTER,
self.FILTER_ACTION)
self.execute.assert_called_once_with(cmd, check_exit_code=True,
extra_ok_codes=None,
log_fail_as_error=True,
run_as_root=True)
def test_add_policy_filter_without_action(self):
self.tc._add_policy_filter(self.FILTER_PARENT, self.FILTER_PROTOCOL,
self.FILTER_FILTER)
cmd = self._call_filter_add(self.DEVICE_NAME, self.FILTER_PARENT,
self.FILTER_PROTOCOL, self.FILTER_FILTER,
None)
self.execute.assert_called_once_with(cmd, check_exit_code=True,
extra_ok_codes=None,
log_fail_as_error=True,
run_as_root=True)
def test_list_policy_filters_root_parent(self):
self.tc._list_policy_filters(self.QDISC_ROOT)
cmd = self._call_filter_show(self.DEVICE_NAME,
self.QDISC_ROOT)
self.execute.assert_called_once_with(cmd, extra_ok_codes=None,
log_fail_as_error=True,
check_exit_code=True,
run_as_root=True)
def test_list_policy_filters_other_parent(self):
self.tc._list_policy_filters(self.QDISC_INGRESS_HANDLE)
cmd = self._call_filter_show(self.DEVICE_NAME,
self.QDISC_INGRESS_HANDLE)
self.execute.assert_called_once_with(cmd, extra_ok_codes=None,
log_fail_as_error=True,
check_exit_code=True,
run_as_root=True)
@mock.patch.object(ip_lib.IPWrapper, "add_ifb")
@mock.patch.object(ip_lib.IPDevice, "exists")
@mock.patch.object(ip_lib.IPDevice, "disable_ipv6")
@mock.patch.object(ip_lib.IpLinkCommand, "set_up")
def test_add_ifb_existing_ifb(self, mock_set_up, mock_disable_ipv6,
mock_exists, mock_add_ifb):
with mock.patch.object(self.tc, '_find_mirrored_ifb',
return_value=True):
mock_exists.return_value = True
self.tc._add_ifb(self.DEVICE_NAME)
mock_add_ifb.assert_not_called()
mock_exists.assert_called_once_with()
mock_disable_ipv6.assert_called_once_with()
mock_set_up.assert_called_once_with()
@mock.patch.object(ip_lib.IPWrapper, "add_ifb")
@mock.patch.object(ip_lib.IPDevice, "exists")
@mock.patch.object(ip_lib.IPDevice, "disable_ipv6")
@mock.patch.object(ip_lib.IpLinkCommand, "set_up")
def test_add_ifb_non_existing_ifb(self, mock_set_up, mock_disable_ipv6,
mock_exists,
mock_add_ifb):
with mock.patch.object(self.tc, '_find_mirrored_ifb',
return_value=True), \
mock.patch.object(self.tc, '_del_ifb') as mock_del_ifb:
mock_exists.return_value = False
mock_add_ifb.return_value = ip_lib.IPDevice(self.DEVICE_NAME)
self.tc._add_ifb(self.DEVICE_NAME)
mock_add_ifb.assert_called_once_with(self.DEVICE_NAME)
mock_exists.assert_called_once_with()
mock_del_ifb.assert_called_once_with(dev_name=self.DEVICE_NAME)
mock_disable_ipv6.assert_called_once_with()
mock_set_up.assert_called_once_with()
@mock.patch.object(ip_lib.IPWrapper, "add_ifb")
@mock.patch.object(ip_lib.IPDevice, "disable_ipv6")
@mock.patch.object(ip_lib.IpLinkCommand, "set_up")
def test_add_ifb_not_found(self, mock_set_up, mock_disable_ipv6,
mock_add_ifb):
with mock.patch.object(self.tc, '_find_mirrored_ifb',
return_value=False), \
mock.patch.object(self.tc, '_del_ifb') as mock_del_ifb:
mock_add_ifb.return_value = ip_lib.IPDevice(self.DEVICE_NAME)
self.tc._add_ifb(self.DEVICE_NAME)
mock_add_ifb.assert_called_once_with(self.DEVICE_NAME)
mock_del_ifb.assert_called_once_with(dev_name=self.DEVICE_NAME)
mock_disable_ipv6.assert_called_once_with()
mock_set_up.assert_called_once_with()
@mock.patch.object(ip_lib.IPWrapper, "del_ifb")
@mock.patch.object(ip_lib.IPWrapper, "get_devices")
def test_del_ifb_existing_netdevice(self, mock_get_devices, mock_del_ifb):
ret_val = [ip_lib.IPDevice('other_name'),
ip_lib.IPDevice(self.DEVICE_NAME)]
mock_get_devices.return_value = ret_val
self.tc._del_ifb(self.DEVICE_NAME)
mock_del_ifb.assert_called_once_with(self.DEVICE_NAME)
@mock.patch.object(ip_lib.IPWrapper, "del_ifb")
@mock.patch.object(ip_lib.IPWrapper, "get_devices")
def test_del_ifb_not_existing_netdevice(self, mock_get_devices,
mock_del_ifb):
ret_val = [ip_lib.IPDevice('other_name'),
ip_lib.IPDevice('another_name')]
mock_get_devices.return_value = ret_val
self.tc._del_ifb(self.DEVICE_NAME)
mock_del_ifb.assert_not_called()
@mock.patch.object(ip_lib.IPWrapper, "del_ifb")
@mock.patch.object(ip_lib.IPWrapper, "get_devices")
def test_del_ifb_no_netdevices(self, mock_get_devices, mock_del_ifb):
mock_get_devices.return_value = []
self.tc._del_ifb(self.DEVICE_NAME)
mock_del_ifb.assert_not_called()
@mock.patch.object(ip_lib.IPDevice, "exists")
def test_find_mirrored_ifb(self, mock_ipdevice_exists):
ifb_name = self.tc._name.replace("tap", "ifb")
mock_ipdevice_exists.return_value = True
ret = self.tc._find_mirrored_ifb()
self.assertEqual(ifb_name, ret)
mock_ipdevice_exists.return_value = False
ret = self.tc._find_mirrored_ifb()
self.assertIsNone(ret)
def test_configure_ifb_non_existing_ifb(self):
with mock.patch.object(self.tc, '_find_mirrored_ifb',
return_value=None) as \
mock_find_mirrored_ifb, \
mock.patch.object(self.tc, '_add_ifb',
return_value=self.IFB_NAME) as \
mock_add_ifb, \
mock.patch.object(self.tc, '_add_policy_qdisc') as \
mock_add_policy_qdisc, \
mock.patch.object(self.tc, '_add_policy_class') as \
mock_add_policy_class, \
mock.patch.object(self.tc, '_add_policy_filter') as \
mock_add_policy_filter:
self.tc._configure_ifb(max=self.MAX_RATE, burst=self.BURST_RATE,
min=self.MIN_RATE)
mock_find_mirrored_ifb.assert_called_once_with()
mock_add_ifb.assert_called_once_with(self.IFB_NAME)
mock_add_policy_filter.assert_called_once_with(
self.QDISC_INGRESS_HANDLE, self.FILTER_PROTOCOL,
self.FILTER_FILTER, dev=self.DEVICE_NAME,
action=self.FILTER_ACTION)
mock_add_policy_qdisc.assert_called_once_with(
self.QDISC_ROOT, "1:", qdisc_type=self.TYPE_HTB,
dev=self.IFB_NAME)
mock_add_policy_class.assert_called_once_with("1:", "1:1",
self.TYPE_HTB, rate=self.MIN_RATE, ceil=self.MAX_RATE,
burst=self.BURST_RATE, dev=self.IFB_NAME)
def test_configure_ifb_existing_ifb(self):
with mock.patch.object(self.tc, '_find_mirrored_ifb',
return_value=self.IFB_NAME) as \
mock_find_mirrored_ifb, \
mock.patch.object(self.tc, '_add_ifb',
return_value=self.IFB_NAME) as \
mock_add_ifb, \
mock.patch.object(self.tc, '_add_policy_qdisc') as \
mock_add_policy_qdisc, \
mock.patch.object(self.tc, '_add_policy_class') as \
mock_add_policy_class:
self.tc._configure_ifb(max=self.MAX_RATE, burst=self.BURST_RATE,
min=self.MIN_RATE)
mock_find_mirrored_ifb.assert_called_once_with()
mock_add_ifb.assert_not_called()
mock_add_policy_qdisc.assert_called_once_with(
self.QDISC_ROOT, "1:", qdisc_type=self.TYPE_HTB,
dev=self.IFB_NAME)
mock_add_policy_class.assert_called_once_with("1:", "1:1",
self.TYPE_HTB, rate=self.MIN_RATE, ceil=self.MAX_RATE,
burst=self.BURST_RATE, dev=self.IFB_NAME)
def test_get_ingress_qdisc_burst_value_burst_not_none(self): def test_get_ingress_qdisc_burst_value_burst_not_none(self):
self.assertEqual( self.assertEqual(
BURST, self.tc.get_ingress_qdisc_burst_value(BW_LIMIT, BURST) BURST, self.tc.get_ingress_qdisc_burst_value(BW_LIMIT, BURST)
) )
def test_get_ingress_qdisc_burst_value_no_burst_value_given(self): def test_get_ingress_qdisc_burst_no_burst_value_given(self):
expected_burst = BW_LIMIT * qos_consts.DEFAULT_BURST_RATE expected_burst = BW_LIMIT * qos_consts.DEFAULT_BURST_RATE
self.assertEqual( self.assertEqual(
expected_burst, expected_burst,
self.tc.get_ingress_qdisc_burst_value(BW_LIMIT, None) self.tc.get_ingress_qdisc_burst_value(BW_LIMIT, None)
) )
def test_get_ingress_qdisc_burst_value_burst_value_zero(self): def test_get_ingress_qdisc_burst_burst_value_zero(self):
expected_burst = BW_LIMIT * qos_consts.DEFAULT_BURST_RATE expected_burst = BW_LIMIT * qos_consts.DEFAULT_BURST_RATE
self.assertEqual( self.assertEqual(
expected_burst, expected_burst,
self.tc.get_ingress_qdisc_burst_value(BW_LIMIT, 0) self.tc.get_ingress_qdisc_burst_value(BW_LIMIT, 0)
) )
def test_get_ingress_limits_no_ifb(self): def test__get_tbf_burst_value_when_burst_bigger_then_minimal(self):
with mock.patch.object(self.tc, '_find_mirrored_ifb', result = self.tc._get_tbf_burst_value(BW_LIMIT, BURST)
return_value=None) as \ self.assertEqual(BURST, result)
mock_find_mirrored_ifb, \
mock.patch.object(self.tc, '_show_policy_class') as \
mock_show_policy_class:
max_bw, burst, min_bw = self.tc._get_ingress_limits()
mock_find_mirrored_ifb.assert_called_once_with()
mock_show_policy_class.assert_not_called()
self.assertIsNone(max_bw)
self.assertIsNone(burst)
self.assertIsNone(min_bw)
def test_get_ingress_limits_ifb_present(self): def test__get_tbf_burst_value_when_burst_smaller_then_minimal(self):
with mock.patch.object(self.tc, '_find_mirrored_ifb', result = self.tc._get_tbf_burst_value(BW_LIMIT, 0)
return_value=self.IFB_NAME) as \ self.assertEqual(2, result)
mock_find_mirrored_ifb, \
mock.patch.object(self.tc, '_show_policy_class') as \
mock_show_policy_class:
classes = {'rate': self.MIN_RATE,
'ceil': self.MAX_RATE,
'burst': self.BURST_RATE}
mock_show_policy_class.return_value = classes
max_bw, burst, min_bw = self.tc._get_ingress_limits()
mock_find_mirrored_ifb.assert_called_once_with()
mock_show_policy_class.assert_called_once_with("1:1",
dev=self.IFB_NAME)
self.assertEqual((self.MAX_RATE, self.BURST_RATE, self.MIN_RATE),
(max_bw, burst, min_bw))

View File

@ -12,68 +12,39 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import collections
import mock import mock
import uuid
from oslo_config import cfg
from oslo_utils import uuidutils from oslo_utils import uuidutils
from neutron.agent.l2.extensions import qos_linux as qos_extensions
from neutron.agent.linux import tc_lib from neutron.agent.linux import tc_lib
from neutron.objects.qos import rule 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 ( from neutron.plugins.ml2.drivers.linuxbridge.agent.extension_drivers import (
qos_driver) qos_driver)
from neutron.services.qos import qos_consts
from neutron.tests import base from neutron.tests import base
TEST_LATENCY_VALUE = 100
DSCP_VALUE = 32 DSCP_VALUE = 32
class FakeVifPort(object):
ofport = 99
port_name = 'name'
vif_mac = 'aa:bb:cc:11:22:33'
class QosLinuxbridgeAgentDriverTestCase(base.BaseTestCase): class QosLinuxbridgeAgentDriverTestCase(base.BaseTestCase):
POLICY_ID = uuid.uuid4().hex
DEVICE_NAME = 'fake_tap'
ACTION_CREATE = 'create'
ACTION_DELETE = 'delete'
RULE_MAX = 4000
RULE_MIN = 1000
RULE_BURST = 800
RULE_DIRECTION_EGRESS = 'egress'
def setUp(self): def setUp(self):
super(QosLinuxbridgeAgentDriverTestCase, self).setUp() super(QosLinuxbridgeAgentDriverTestCase, self).setUp()
cfg.CONF.set_override("tbf_latency", TEST_LATENCY_VALUE, "QOS")
self.qos_driver = qos_driver.QosLinuxbridgeAgentDriver() self.qos_driver = qos_driver.QosLinuxbridgeAgentDriver()
self.qos_driver.initialize() self.qos_driver.initialize()
self.rule_bw_limit = 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.rule_dscp_marking = self._create_dscp_marking_rule_obj()
self.get_egress_burst_value = mock.patch.object(
qos_extensions.QosLinuxAgentDriver, "_get_egress_burst_value")
self.mock_get_egress_burst_value = self.get_egress_burst_value.start()
self.mock_get_egress_burst_value.return_value = self.RULE_BURST
self.rule_bw_limit = self._create_bw_limit_rule_obj()
self.rule_min_bw = self._create_min_bw_rule_obj()
self.port = self._create_fake_port(uuidutils.generate_uuid()) self.port = self._create_fake_port(uuidutils.generate_uuid())
self._ports = collections.defaultdict(dict)
def _create_bw_limit_rule_obj(self): def _create_bw_limit_rule_obj(self):
rule_obj = rule.QosBandwidthLimitRule() rule_obj = rule.QosBandwidthLimitRule()
rule_obj.id = uuidutils.generate_uuid() rule_obj.id = uuidutils.generate_uuid()
rule_obj.max_kbps = self.RULE_MAX rule_obj.max_kbps = 2
rule_obj.max_burst_kbps = self.RULE_BURST rule_obj.max_burst_kbps = 200
rule_obj.obj_reset_changes()
return rule_obj
def _create_min_bw_rule_obj(self):
rule_obj = rule.QosMinimumBandwidthRule()
rule_obj.id = uuidutils.generate_uuid()
rule_obj.min_kbps = self.RULE_MAX
rule_obj.direction = self.RULE_DIRECTION_EGRESS
rule_obj.obj_reset_changes() rule_obj.obj_reset_changes()
return rule_obj return rule_obj
@ -87,9 +58,7 @@ class QosLinuxbridgeAgentDriverTestCase(base.BaseTestCase):
def _create_fake_port(self, policy_id): def _create_fake_port(self, policy_id):
return {'qos_policy_id': policy_id, return {'qos_policy_id': policy_id,
'network_qos_policy_id': None, 'network_qos_policy_id': None,
'device': self.DEVICE_NAME, 'device': 'fake_tap'}
'port_id': uuid.uuid4(),
'vif_port': FakeVifPort()}
def _dscp_mark_chain_name(self, device): def _dscp_mark_chain_name(self, device):
return "qos-o%s" % device[3:] return "qos-o%s" % device[3:]
@ -104,49 +73,32 @@ class QosLinuxbridgeAgentDriverTestCase(base.BaseTestCase):
def _dscp_rule_tag(self, device): def _dscp_rule_tag(self, device):
return "dscp-%s" % device return "dscp-%s" % device
@mock.patch.object(tc_lib.TcCommand, "set_bw") def test_create_bandwidth_limit(self):
def test_update_bandwidth_limit(self, mock_set_bw): with mock.patch.object(
with mock.patch.object(self.qos_driver, '_get_port_bw_parameters') as \ tc_lib.TcCommand, "set_filters_bw_limit"
mock_bw_param: ) as set_bw_limit:
mock_bw_param.return_value = (self.rule_bw_limit.max_kbps, self.qos_driver.create_bandwidth_limit(self.port,
self.rule_bw_limit.max_burst_kbps, self.rule_bw_limit)
None) set_bw_limit.assert_called_once_with(
self.rule_bw_limit.max_kbps, self.rule_bw_limit.max_burst_kbps,
)
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.qos_driver.update_bandwidth_limit(self.port,
self.rule_bw_limit) self.rule_bw_limit)
mock_set_bw.assert_called_once_with( update_bw_limit.assert_called_once_with(
self.rule_bw_limit.max_kbps, self.rule_bw_limit.max_burst_kbps, self.rule_bw_limit.max_kbps, self.rule_bw_limit.max_burst_kbps,
None, self.RULE_DIRECTION_EGRESS) )
mock_bw_param.assert_called_once_with(self.port['port_id'])
@mock.patch.object(tc_lib.TcCommand, "delete_bw") def test_delete_bandwidth_limit(self):
def test_delete_bandwidth_limit(self, mock_delete_bw): with mock.patch.object(
with mock.patch.object(self.qos_driver, '_get_port_bw_parameters') as \ tc_lib.TcCommand, "delete_filters_bw_limit"
mock_bw_param: ) as delete_bw_limit:
mock_bw_param.return_value = (None, None, None)
self.qos_driver.delete_bandwidth_limit(self.port) self.qos_driver.delete_bandwidth_limit(self.port)
mock_delete_bw.assert_called_once_with(self.RULE_DIRECTION_EGRESS) delete_bw_limit.assert_called_once_with()
mock_bw_param.assert_called_once_with(self.port['port_id'])
@mock.patch.object(tc_lib.TcCommand, "set_bw")
def test_update_minimum_bandwidth(self, mock_set_bw):
with mock.patch.object(self.qos_driver, '_get_port_bw_parameters') as \
mock_bw_param:
mock_bw_param.return_value = (None, None,
self.rule_min_bw.min_kbps)
self.qos_driver.update_minimum_bandwidth(self.port,
self.rule_min_bw)
mock_set_bw.assert_called_once_with(None, None,
self.rule_min_bw.min_kbps, self.RULE_DIRECTION_EGRESS)
mock_bw_param.assert_called_once_with(self.port['port_id'])
@mock.patch.object(tc_lib.TcCommand, "delete_bw")
def test_delete_minimum_bandwidth(self, mock_delete_bw):
with mock.patch.object(self.qos_driver, '_get_port_bw_parameters') as \
mock_bw_param:
mock_bw_param.return_value = (None, None, None)
self.qos_driver.delete_minimum_bandwidth(self.port)
mock_delete_bw.assert_called_once_with(self.RULE_DIRECTION_EGRESS)
mock_bw_param.assert_called_once_with(self.port['port_id'])
def test_create_dscp_marking(self): def test_create_dscp_marking(self):
expected_calls = [ expected_calls = [
@ -243,23 +195,3 @@ class QosLinuxbridgeAgentDriverTestCase(base.BaseTestCase):
]) ])
iptables_manager.ipv4['mangle'].remove_chain.assert_not_called() iptables_manager.ipv4['mangle'].remove_chain.assert_not_called()
iptables_manager.ipv4['mangle'].remove_rule.assert_not_called() iptables_manager.ipv4['mangle'].remove_rule.assert_not_called()
def test_get_port_bw_parameters_existing_port(self):
port_id = 'port_id_1'
self.qos_driver._port_rules[port_id][
qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH] = self.rule_min_bw
self.qos_driver._port_rules[port_id][
qos_consts.RULE_TYPE_BANDWIDTH_LIMIT] = self.rule_bw_limit
max, burst, min = self.qos_driver._get_port_bw_parameters(port_id)
self.assertEqual(self.rule_bw_limit.max_kbps, max)
self.assertEqual(self.rule_bw_limit.max_burst_kbps, burst)
self.assertEqual(self.rule_min_bw.min_kbps, min)
self.mock_get_egress_burst_value.assert_called_once_with(
self.rule_bw_limit)
def test_get_port_bw_parameters_not_existing_port(self):
port_id = 'port_id_1'
max, burst, min = self.qos_driver._get_port_bw_parameters(port_id)
self.assertIsNone(max)
self.assertIsNone(burst)
self.assertIsNone(min)