Merge "Add TC filter functions implemented with pyroute2"
This commit is contained in:
commit
6920727fe1
@ -16,16 +16,17 @@
|
|||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import netaddr
|
||||||
from neutron_lib import exceptions
|
from neutron_lib import exceptions
|
||||||
from neutron_lib.exceptions import qos as qos_exc
|
from neutron_lib.exceptions import qos as qos_exc
|
||||||
from neutron_lib.services.qos import constants as qos_consts
|
from neutron_lib.services.qos import constants as qos_consts
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
from pyroute2.iproute import linux as iproute_linux
|
||||||
from pyroute2.netlink import rtnl
|
from pyroute2.netlink import rtnl
|
||||||
from pyroute2.netlink.rtnl.tcmsg import common as rtnl_common
|
from pyroute2.netlink.rtnl.tcmsg import common as rtnl_common
|
||||||
|
|
||||||
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.common import utils
|
from neutron.common import utils
|
||||||
from neutron.privileged.agent.linux import tc_lib as priv_tc_lib
|
from neutron.privileged.agent.linux import tc_lib as priv_tc_lib
|
||||||
|
|
||||||
@ -156,6 +157,37 @@ def _handle_from_hex_to_string(handle):
|
|||||||
return ':'.join([major, minor])
|
return ':'.join([major, minor])
|
||||||
|
|
||||||
|
|
||||||
|
def _mac_to_pyroute2_keys(mac, offset):
|
||||||
|
"""Convert a MAC address to a list of filter keys
|
||||||
|
|
||||||
|
For example:
|
||||||
|
MAC: '01:23:45:67:89:0a', offset: 8
|
||||||
|
keys: ['0x01234567/0xffffffff+8', '0x890a0000/0xffff0000+12']
|
||||||
|
|
||||||
|
:param mac: (string) MAC address
|
||||||
|
:param offset: (int) natural number, offset bytes number from the IP header
|
||||||
|
"""
|
||||||
|
int_mac = int(netaddr.EUI(mac))
|
||||||
|
high_value = int_mac >> 16
|
||||||
|
high_mask = 0xffffffff
|
||||||
|
high_offset = offset
|
||||||
|
high = {'value': high_value,
|
||||||
|
'mask': high_mask,
|
||||||
|
'offset': high_offset,
|
||||||
|
'key': (hex(high_value) + '/' + hex(high_mask) + '+' +
|
||||||
|
str(high_offset))}
|
||||||
|
|
||||||
|
low_value = (int_mac & 0xffff) << 16
|
||||||
|
low_mask = 0xffff0000
|
||||||
|
low_offset = offset + 4
|
||||||
|
low = {'value': low_value,
|
||||||
|
'mask': low_mask,
|
||||||
|
'offset': low_offset,
|
||||||
|
'key': hex(low_value) + '/' + hex(low_mask) + '+' + str(low_offset)}
|
||||||
|
|
||||||
|
return [high, low]
|
||||||
|
|
||||||
|
|
||||||
class TcCommand(ip_lib.IPDevice):
|
class TcCommand(ip_lib.IPDevice):
|
||||||
|
|
||||||
def __init__(self, name, kernel_hz, namespace=None):
|
def __init__(self, name, kernel_hz, namespace=None):
|
||||||
@ -164,11 +196,6 @@ class TcCommand(ip_lib.IPDevice):
|
|||||||
super(TcCommand, self).__init__(name, namespace=namespace)
|
super(TcCommand, self).__init__(name, namespace=namespace)
|
||||||
self.kernel_hz = kernel_hz
|
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)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_ingress_qdisc_burst_value(bw_limit, burst_limit):
|
def get_ingress_qdisc_burst_value(bw_limit, burst_limit):
|
||||||
"""Return burst value used in ingress qdisc.
|
"""Return burst value used in ingress qdisc.
|
||||||
@ -181,21 +208,11 @@ class TcCommand(ip_lib.IPDevice):
|
|||||||
return burst_limit
|
return burst_limit
|
||||||
|
|
||||||
def get_filters_bw_limits(self, qdisc_id=INGRESS_QDISC_ID):
|
def get_filters_bw_limits(self, qdisc_id=INGRESS_QDISC_ID):
|
||||||
cmd = ['filter', 'show', 'dev', self.name, 'parent', qdisc_id]
|
filters = list_tc_filters(self.name, qdisc_id,
|
||||||
cmd_result = self._execute_tc_cmd(cmd)
|
namespace=self.namespace)
|
||||||
if not cmd_result:
|
if filters:
|
||||||
return None, None
|
return filters[0].get('rate_kbps'), filters[0].get('burst_kb')
|
||||||
for line in cmd_result.split("\n"):
|
|
||||||
m = filters_pattern.match(line.strip())
|
|
||||||
if m:
|
|
||||||
# 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), constants.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), constants.IEC_BASE)
|
|
||||||
return bw_limit, burst_limit
|
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
def get_tbf_bw_limits(self):
|
def get_tbf_bw_limits(self):
|
||||||
@ -253,23 +270,11 @@ class TcCommand(ip_lib.IPDevice):
|
|||||||
|
|
||||||
def _add_policy_filter(self, bw_limit, burst_limit,
|
def _add_policy_filter(self, bw_limit, burst_limit,
|
||||||
qdisc_id=INGRESS_QDISC_ID):
|
qdisc_id=INGRESS_QDISC_ID):
|
||||||
rate_limit = "%s%s" % (bw_limit, BW_LIMIT_UNIT)
|
|
||||||
burst = "%s%s" % (
|
|
||||||
self.get_ingress_qdisc_burst_value(bw_limit, burst_limit),
|
|
||||||
BURST_UNIT
|
|
||||||
)
|
|
||||||
# NOTE(slaweq): it is made in exactly same way how openvswitch is doing
|
# NOTE(slaweq): it is made in exactly same way how openvswitch is doing
|
||||||
# it when configuing ingress traffic limit on port. It can be found in
|
# it when configuing ingress traffic limit on port. It can be found in
|
||||||
# lib/netdev-linux.c#L4698 in openvswitch sources:
|
# lib/netdev-linux.c#L4698 in openvswitch sources:
|
||||||
cmd = [
|
add_tc_filter_policy(self.name, qdisc_id, bw_limit, burst_limit,
|
||||||
'filter', 'add', 'dev', self.name,
|
MAX_MTU_VALUE, 'drop', priority=49)
|
||||||
'parent', qdisc_id, 'protocol', 'all',
|
|
||||||
'prio', '49', 'basic', 'police',
|
|
||||||
'rate', rate_limit,
|
|
||||||
'burst', burst,
|
|
||||||
'mtu', MAX_MTU_VALUE,
|
|
||||||
'drop']
|
|
||||||
return self._execute_tc_cmd(cmd)
|
|
||||||
|
|
||||||
|
|
||||||
def add_tc_qdisc(device, qdisc_type, parent=None, handle=None, latency_ms=None,
|
def add_tc_qdisc(device, qdisc_type, parent=None, handle=None, latency_ms=None,
|
||||||
@ -456,3 +461,95 @@ def delete_tc_policy_class(device, parent, classid, namespace=None):
|
|||||||
"""
|
"""
|
||||||
priv_tc_lib.delete_tc_policy_class(device, parent, classid,
|
priv_tc_lib.delete_tc_policy_class(device, parent, classid,
|
||||||
namespace=namespace)
|
namespace=namespace)
|
||||||
|
|
||||||
|
|
||||||
|
def add_tc_filter_match_mac(device, parent, classid, mac, offset=0, priority=0,
|
||||||
|
protocol=None, namespace=None):
|
||||||
|
"""Add a TC filter in a device to match a MAC address.
|
||||||
|
|
||||||
|
:param device: (string) device name
|
||||||
|
:param parent: (string) qdisc parent class ('root', 'ingress', '2:10')
|
||||||
|
:param classid: (string) major:minor handler identifier ('10:20')
|
||||||
|
:param mac: (string) MAC address to match
|
||||||
|
:param offset: (int) (optional) match offset, starting from the outer
|
||||||
|
packet IP header
|
||||||
|
:param priority: (int) (optional) filter priority (lower priority, higher
|
||||||
|
preference)
|
||||||
|
:param protocol: (int) (optional) traffic filter protocol; if None, all
|
||||||
|
will be matched.
|
||||||
|
:param namespace: (string) (optional) namespace name
|
||||||
|
|
||||||
|
"""
|
||||||
|
keys = [key['key'] for key in _mac_to_pyroute2_keys(mac, offset)]
|
||||||
|
priv_tc_lib.add_tc_filter_match32(device, parent, priority, classid, keys,
|
||||||
|
protocol=protocol, namespace=namespace)
|
||||||
|
|
||||||
|
|
||||||
|
def add_tc_filter_policy(device, parent, rate_kbps, burst_kb, mtu, action,
|
||||||
|
priority=0, protocol=None, namespace=None):
|
||||||
|
"""Add a TC filter in a device to set a policy.
|
||||||
|
|
||||||
|
:param device: (string) device name
|
||||||
|
:param parent: (string) qdisc parent class ('root', 'ingress', '2:10')
|
||||||
|
:param rate_kbps: (int) rate in kbits/second
|
||||||
|
:param burst_kb: (int) burst in kbits
|
||||||
|
:param mtu: (int) MTU size (bytes)
|
||||||
|
:param action: (string) filter policy action
|
||||||
|
:param priority: (int) (optional) filter priority (lower priority, higher
|
||||||
|
preference)
|
||||||
|
:param protocol: (int) (optional) traffic filter protocol; if None, all
|
||||||
|
will be matched.
|
||||||
|
:param namespace: (string) (optional) namespace name
|
||||||
|
|
||||||
|
"""
|
||||||
|
rate = int(rate_kbps * 1024 / 8)
|
||||||
|
burst = int(burst_kb * 1024 / 8)
|
||||||
|
priv_tc_lib.add_tc_filter_policy(device, parent, priority, rate, burst,
|
||||||
|
mtu, action, protocol=protocol,
|
||||||
|
namespace=namespace)
|
||||||
|
|
||||||
|
|
||||||
|
def list_tc_filters(device, parent, namespace=None):
|
||||||
|
"""List TC filter in a device
|
||||||
|
|
||||||
|
:param device: (string) device name
|
||||||
|
:param parent: (string) qdisc parent class ('root', 'ingress', '2:10')
|
||||||
|
:param namespace: (string) (optional) namespace name
|
||||||
|
|
||||||
|
"""
|
||||||
|
parent = iproute_linux.transform_handle(parent)
|
||||||
|
filters = priv_tc_lib.list_tc_filters(device, parent, namespace=namespace)
|
||||||
|
retval = []
|
||||||
|
for filter in filters:
|
||||||
|
tca_options = _get_attr(filter, 'TCA_OPTIONS')
|
||||||
|
if not tca_options:
|
||||||
|
continue
|
||||||
|
tca_u32_sel = _get_attr(tca_options, 'TCA_U32_SEL')
|
||||||
|
if not tca_u32_sel:
|
||||||
|
continue
|
||||||
|
keys = []
|
||||||
|
for key in tca_u32_sel['keys']:
|
||||||
|
key_off = key['key_off']
|
||||||
|
value = 0
|
||||||
|
for i in range(4):
|
||||||
|
value = (value << 8) + (key_off & 0xff)
|
||||||
|
key_off = key_off >> 8
|
||||||
|
keys.append({'value': value,
|
||||||
|
'mask': key['key_val'],
|
||||||
|
'offset': key['key_offmask']})
|
||||||
|
|
||||||
|
value = {'keys': keys}
|
||||||
|
|
||||||
|
tca_u32_police = _get_attr(tca_options, 'TCA_U32_POLICE')
|
||||||
|
if tca_u32_police:
|
||||||
|
tca_police_tbf = _get_attr(tca_u32_police, 'TCA_POLICE_TBF')
|
||||||
|
if tca_police_tbf:
|
||||||
|
value['rate_kbps'] = int(tca_police_tbf['rate'] * 8 / 1024)
|
||||||
|
value['burst_kb'] = int(
|
||||||
|
_calc_burst(tca_police_tbf['rate'],
|
||||||
|
tca_police_tbf['burst']) * 8 / 1024)
|
||||||
|
value['mtu'] = tca_police_tbf['mtu']
|
||||||
|
|
||||||
|
retval.append(value)
|
||||||
|
|
||||||
|
return retval
|
||||||
|
@ -17,6 +17,7 @@ import socket
|
|||||||
|
|
||||||
from neutron_lib import constants as n_constants
|
from neutron_lib import constants as n_constants
|
||||||
import pyroute2
|
import pyroute2
|
||||||
|
from pyroute2 import protocols as pyroute2_protocols
|
||||||
|
|
||||||
from neutron._i18n import _
|
from neutron._i18n import _
|
||||||
from neutron import privileged
|
from neutron import privileged
|
||||||
@ -141,4 +142,62 @@ def delete_tc_policy_class(device, parent, classid, namespace=None,
|
|||||||
if e.code == errno.ENOENT:
|
if e.code == errno.ENOENT:
|
||||||
raise TrafficControlClassNotFound(classid=classid,
|
raise TrafficControlClassNotFound(classid=classid,
|
||||||
namespace=namespace)
|
namespace=namespace)
|
||||||
|
|
||||||
|
|
||||||
|
@privileged.default.entrypoint
|
||||||
|
def add_tc_filter_match32(device, parent, priority, class_id, keys,
|
||||||
|
protocol=None, namespace=None, **kwargs):
|
||||||
|
"""Add TC filter, type: match u32"""
|
||||||
|
# NOTE(ralonsoh): by default (protocol=None), every packet is filtered.
|
||||||
|
protocol = protocol or pyroute2_protocols.ETH_P_ALL
|
||||||
|
try:
|
||||||
|
index = ip_lib.get_link_id(device, namespace)
|
||||||
|
with ip_lib.get_iproute(namespace) as ip:
|
||||||
|
ip.tc('add-filter', kind='u32', index=index,
|
||||||
|
parent=parent, priority=priority, target=class_id,
|
||||||
|
protocol=protocol, keys=keys, **kwargs)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.ENOENT:
|
||||||
|
raise ip_lib.NetworkNamespaceNotFound(netns_name=namespace)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@privileged.default.entrypoint
|
||||||
|
def add_tc_filter_policy(device, parent, priority, rate, burst, mtu, action,
|
||||||
|
protocol=None, keys=None, flowid=1, namespace=None,
|
||||||
|
**kwargs):
|
||||||
|
"""Add TC filter, type: policy filter
|
||||||
|
|
||||||
|
By default (protocol=None), that means every packet is shaped. "keys"
|
||||||
|
and "target" (flowid) parameters are mandatory. If the filter is
|
||||||
|
applied on a classless qdisc, "target" is irrelevant and a default value
|
||||||
|
can be passed. If all packets must be shaped, an empty filter ("keys")
|
||||||
|
can be passed.
|
||||||
|
"""
|
||||||
|
keys = keys if keys else ['0x0/0x0']
|
||||||
|
protocol = protocol or pyroute2_protocols.ETH_P_ALL
|
||||||
|
try:
|
||||||
|
index = ip_lib.get_link_id(device, namespace)
|
||||||
|
with ip_lib.get_iproute(namespace) as ip:
|
||||||
|
ip.tc('add-filter', kind='u32', index=index,
|
||||||
|
parent=parent, priority=priority, protocol=protocol,
|
||||||
|
rate=rate, burst=burst, mtu=mtu, action=action,
|
||||||
|
keys=keys, target=flowid, **kwargs)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.ENOENT:
|
||||||
|
raise ip_lib.NetworkNamespaceNotFound(netns_name=namespace)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@privileged.default.entrypoint
|
||||||
|
def list_tc_filters(device, parent, namespace=None, **kwargs):
|
||||||
|
"""List TC filters"""
|
||||||
|
try:
|
||||||
|
index = ip_lib.get_link_id(device, namespace)
|
||||||
|
with ip_lib.get_iproute(namespace) as ip:
|
||||||
|
return ip_lib.make_serializable(
|
||||||
|
ip.get_filters(index=index, parent=parent, **kwargs))
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.ENOENT:
|
||||||
|
raise ip_lib.NetworkNamespaceNotFound(netns_name=namespace)
|
||||||
raise
|
raise
|
||||||
|
@ -232,3 +232,62 @@ class TcPolicyClassTestCase(functional_base.BaseSudoTestCase):
|
|||||||
priv_tc_lib.TrafficControlClassNotFound,
|
priv_tc_lib.TrafficControlClassNotFound,
|
||||||
priv_tc_lib.delete_tc_policy_class, self.device, '1:',
|
priv_tc_lib.delete_tc_policy_class, self.device, '1:',
|
||||||
'1:1000', namespace=self.namespace)
|
'1:1000', namespace=self.namespace)
|
||||||
|
|
||||||
|
|
||||||
|
class TcFilterClassTestCase(functional_base.BaseSudoTestCase):
|
||||||
|
|
||||||
|
CLASSES = {'1:1': {'rate': 10000, 'ceil': 20000, 'burst': 1500},
|
||||||
|
'1:3': {'rate': 20000, 'ceil': 50000, 'burst': 1600},
|
||||||
|
'1:5': {'rate': 30000, 'ceil': 90000, 'burst': 1700},
|
||||||
|
'1:7': {'rate': 35001, 'ceil': 90000, 'burst': 1701}}
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TcFilterClassTestCase, self).setUp()
|
||||||
|
self.namespace = 'ns_test-' + uuidutils.generate_uuid()
|
||||||
|
priv_ip_lib.create_netns(self.namespace)
|
||||||
|
self.addCleanup(self._remove_ns, self.namespace)
|
||||||
|
self.device = 'int_dummy'
|
||||||
|
priv_ip_lib.create_interface('int_dummy', self.namespace, 'dummy')
|
||||||
|
|
||||||
|
def _remove_ns(self, namespace):
|
||||||
|
priv_ip_lib.remove_netns(namespace)
|
||||||
|
|
||||||
|
def test_add_tc_filter_match32(self):
|
||||||
|
priv_tc_lib.add_tc_qdisc(
|
||||||
|
self.device, parent=rtnl.TC_H_ROOT, kind='htb', handle='1:',
|
||||||
|
namespace=self.namespace)
|
||||||
|
priv_tc_lib.add_tc_policy_class(
|
||||||
|
self.device, '1:', '1:10', 'htb', namespace=self.namespace,
|
||||||
|
rate=10000)
|
||||||
|
keys = tc_lib._mac_to_pyroute2_keys('7a:8c:f9:1f:e5:cb', 41)
|
||||||
|
priv_tc_lib.add_tc_filter_match32(
|
||||||
|
self.device, '1:0', 10, '1:10', [keys[0]['key'], keys[1]['key']],
|
||||||
|
namespace=self.namespace)
|
||||||
|
|
||||||
|
filters = tc_lib.list_tc_filters(
|
||||||
|
self.device, '1:0', namespace=self.namespace)
|
||||||
|
self.assertEqual(1, len(filters))
|
||||||
|
filter_keys = filters[0]['keys']
|
||||||
|
self.assertEqual(len(keys), len(filter_keys))
|
||||||
|
for index, value in enumerate(keys):
|
||||||
|
value.pop('key')
|
||||||
|
self.assertEqual(value, filter_keys[index])
|
||||||
|
|
||||||
|
def test_add_tc_filter_policy(self):
|
||||||
|
priv_tc_lib.add_tc_qdisc(
|
||||||
|
self.device, parent=rtnl.TC_H_ROOT, kind='ingress',
|
||||||
|
namespace=self.namespace)
|
||||||
|
|
||||||
|
# NOTE(ralonsoh):
|
||||||
|
# - rate: 320000 bytes/sec (pyroute2 units) = 2500 kbits/sec (OS units)
|
||||||
|
# - burst: 192000 bytes/sec = 1500 kbits/sec
|
||||||
|
priv_tc_lib.add_tc_filter_policy(
|
||||||
|
self.device, 'ffff:', 49, 320000, 192000, 1200, 'drop',
|
||||||
|
namespace=self.namespace)
|
||||||
|
|
||||||
|
filters = tc_lib.list_tc_filters(
|
||||||
|
self.device, 'ffff:', namespace=self.namespace)
|
||||||
|
self.assertEqual(1, len(filters))
|
||||||
|
self.assertEqual(2500, filters[0]['rate_kbps'])
|
||||||
|
self.assertEqual(1500, filters[0]['burst_kb'])
|
||||||
|
self.assertEqual(1200, filters[0]['mtu'])
|
||||||
|
@ -105,16 +105,16 @@ class TestTcCommand(base.BaseTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestTcCommand, self).setUp()
|
super(TestTcCommand, self).setUp()
|
||||||
self.tc = tc_lib.TcCommand(DEVICE_NAME, KERNEL_HZ_VALUE)
|
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.mock_list_tc_qdiscs = mock.patch.object(tc_lib,
|
self.mock_list_tc_qdiscs = mock.patch.object(tc_lib,
|
||||||
'list_tc_qdiscs').start()
|
'list_tc_qdiscs').start()
|
||||||
self.mock_add_tc_qdisc = mock.patch.object(tc_lib,
|
self.mock_add_tc_qdisc = mock.patch.object(tc_lib,
|
||||||
'add_tc_qdisc').start()
|
'add_tc_qdisc').start()
|
||||||
self.mock_delete_tc_qdisc = mock.patch.object(
|
self.mock_delete_tc_qdisc = mock.patch.object(
|
||||||
tc_lib, 'delete_tc_qdisc').start()
|
tc_lib, 'delete_tc_qdisc').start()
|
||||||
|
self.mock_list_tc_filters = mock.patch.object(
|
||||||
|
tc_lib, 'list_tc_filters').start()
|
||||||
|
self.mock_add_tc_filter_policy = mock.patch.object(
|
||||||
|
tc_lib, 'add_tc_filter_policy').start()
|
||||||
|
|
||||||
def test_check_kernel_hz_lower_then_zero(self):
|
def test_check_kernel_hz_lower_then_zero(self):
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
@ -127,26 +127,23 @@ class TestTcCommand(base.BaseTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_get_filters_bw_limits(self):
|
def test_get_filters_bw_limits(self):
|
||||||
self.execute.return_value = TC_FILTERS_OUTPUT
|
self.mock_list_tc_filters.return_value = [{'rate_kbps': BW_LIMIT,
|
||||||
|
'burst_kb': BURST}]
|
||||||
bw_limit, burst_limit = self.tc.get_filters_bw_limits()
|
bw_limit, burst_limit = self.tc.get_filters_bw_limits()
|
||||||
self.assertEqual(BW_LIMIT, bw_limit)
|
self.assertEqual(BW_LIMIT, bw_limit)
|
||||||
self.assertEqual(BURST, burst_limit)
|
self.assertEqual(BURST, burst_limit)
|
||||||
|
|
||||||
def test_get_filters_bw_limits_when_output_not_match(self):
|
def test_get_filters_bw_limits_no_filters(self):
|
||||||
output = (
|
self.mock_list_tc_filters.return_value = []
|
||||||
"Some different "
|
|
||||||
"output from command:"
|
|
||||||
"tc filters show dev XXX parent ffff:"
|
|
||||||
)
|
|
||||||
self.execute.return_value = output
|
|
||||||
bw_limit, burst_limit = self.tc.get_filters_bw_limits()
|
bw_limit, burst_limit = self.tc.get_filters_bw_limits()
|
||||||
self.assertIsNone(bw_limit)
|
self.assertIsNone(bw_limit)
|
||||||
self.assertIsNone(burst_limit)
|
self.assertIsNone(burst_limit)
|
||||||
|
|
||||||
def test_get_filters_bw_limits_when_wrong_units(self):
|
def test_get_filters_bw_limits_no_rate_info(self):
|
||||||
output = TC_FILTERS_OUTPUT.replace("kbit", "Xbit")
|
self.mock_list_tc_filters.return_value = [{'other_values': 1}]
|
||||||
self.execute.return_value = output
|
bw_limit, burst_limit = self.tc.get_filters_bw_limits()
|
||||||
self.assertRaises(tc_lib.InvalidUnit, self.tc.get_filters_bw_limits)
|
self.assertIsNone(bw_limit)
|
||||||
|
self.assertIsNone(burst_limit)
|
||||||
|
|
||||||
def test_get_tbf_bw_limits(self):
|
def test_get_tbf_bw_limits(self):
|
||||||
self.mock_list_tc_qdiscs.return_value = [
|
self.mock_list_tc_qdiscs.return_value = [
|
||||||
@ -166,17 +163,14 @@ class TestTcCommand(base.BaseTestCase):
|
|||||||
|
|
||||||
def test_update_filters_bw_limit(self):
|
def test_update_filters_bw_limit(self):
|
||||||
self.tc.update_filters_bw_limit(BW_LIMIT, BURST)
|
self.tc.update_filters_bw_limit(BW_LIMIT, BURST)
|
||||||
self.execute.assert_called_once_with(
|
|
||||||
['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)
|
|
||||||
self.mock_add_tc_qdisc.assert_called_once_with(
|
self.mock_add_tc_qdisc.assert_called_once_with(
|
||||||
self.tc.name, 'ingress', namespace=self.tc.namespace)
|
self.tc.name, 'ingress', namespace=self.tc.namespace)
|
||||||
self.mock_delete_tc_qdisc.assert_called_once_with(
|
self.mock_delete_tc_qdisc.assert_called_once_with(
|
||||||
self.tc.name, is_ingress=True, raise_interface_not_found=False,
|
self.tc.name, is_ingress=True, raise_interface_not_found=False,
|
||||||
raise_qdisc_not_found=False, namespace=self.tc.namespace)
|
raise_qdisc_not_found=False, namespace=self.tc.namespace)
|
||||||
|
self.mock_add_tc_filter_policy.assert_called_once_with(
|
||||||
|
self.tc.name, tc_lib.INGRESS_QDISC_ID, BW_LIMIT, BURST,
|
||||||
|
tc_lib.MAX_MTU_VALUE, 'drop', priority=49)
|
||||||
|
|
||||||
def test_delete_filters_bw_limit(self):
|
def test_delete_filters_bw_limit(self):
|
||||||
self.tc.delete_filters_bw_limit()
|
self.tc.delete_filters_bw_limit()
|
||||||
@ -362,3 +356,21 @@ class TcPolicyClassTestCase(base.BaseTestCase):
|
|||||||
'max_kbps': 2000,
|
'max_kbps': 2000,
|
||||||
'burst_kb': 1200}
|
'burst_kb': 1200}
|
||||||
self.assertEqual(reference, _class)
|
self.assertEqual(reference, _class)
|
||||||
|
|
||||||
|
|
||||||
|
class TcFilterTestCase(base.BaseTestCase):
|
||||||
|
|
||||||
|
def test__mac_to_pyroute2_keys(self):
|
||||||
|
mac = '01:23:45:67:89:ab'
|
||||||
|
offset = 10
|
||||||
|
keys = tc_lib._mac_to_pyroute2_keys(mac, offset)
|
||||||
|
high = {'value': 0x1234567,
|
||||||
|
'mask': 0xffffffff,
|
||||||
|
'offset': 10,
|
||||||
|
'key': '0x1234567/0xffffffff+10'}
|
||||||
|
low = {'value': 0x89ab0000,
|
||||||
|
'mask': 0xffff0000,
|
||||||
|
'offset': 14,
|
||||||
|
'key': '0x89ab0000/0xffff0000+14'}
|
||||||
|
self.assertEqual(high, keys[0])
|
||||||
|
self.assertEqual(low, keys[1])
|
||||||
|
Loading…
Reference in New Issue
Block a user