Merge "Add add_tc_policy_class and list_tc_policy_classes using pyroute2"
This commit is contained in:
commit
ae2031bbe9
@ -369,3 +369,78 @@ def delete_tc_qdisc(device, parent=None, is_ingress=False,
|
|||||||
device, parent=parent, kind=qdisc_type,
|
device, parent=parent, kind=qdisc_type,
|
||||||
raise_interface_not_found=raise_interface_not_found,
|
raise_interface_not_found=raise_interface_not_found,
|
||||||
raise_qdisc_not_found=raise_qdisc_not_found, namespace=namespace)
|
raise_qdisc_not_found=raise_qdisc_not_found, namespace=namespace)
|
||||||
|
|
||||||
|
|
||||||
|
def add_tc_policy_class(device, parent, classid, qdisc_type,
|
||||||
|
min_kbps=None, max_kbps=None, burst_kb=None,
|
||||||
|
namespace=None):
|
||||||
|
"""Add a TC policy class
|
||||||
|
|
||||||
|
: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 qdisc_type: (string) qdisc type ("sfq", "htb", "u32", etc)
|
||||||
|
:param min_kbps: (int) (optional) minimum bandwidth in kbps
|
||||||
|
:param max_kbps: (int) (optional) maximum bandwidth in kbps
|
||||||
|
:param burst_kb: (int) (optional) burst size in kb
|
||||||
|
:param namespace: (string) (optional) namespace name
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
parent = TC_QDISC_PARENT.get(parent, parent)
|
||||||
|
args = {}
|
||||||
|
# NOTE(ralonsoh): pyroute2 input parameters and units [1]:
|
||||||
|
# - rate (min bw): bytes/second
|
||||||
|
# - ceil (max bw): bytes/second
|
||||||
|
# - burst: bytes
|
||||||
|
# [1] https://www.systutorials.com/docs/linux/man/8-tc/
|
||||||
|
if min_kbps:
|
||||||
|
args['rate'] = int(min_kbps * 1024 / 8)
|
||||||
|
if max_kbps:
|
||||||
|
args['ceil'] = int(max_kbps * 1024 / 8)
|
||||||
|
if burst_kb:
|
||||||
|
args['burst'] = int(burst_kb * 1024 / 8)
|
||||||
|
priv_tc_lib.add_tc_policy_class(device, parent, classid, qdisc_type,
|
||||||
|
namespace=namespace, **args)
|
||||||
|
|
||||||
|
|
||||||
|
def list_tc_policy_class(device, namespace=None):
|
||||||
|
"""List all TC policy classes of a device
|
||||||
|
|
||||||
|
:param device: (string) device name
|
||||||
|
:param namespace: (string) (optional) namespace name
|
||||||
|
:return: (list) TC policy classes
|
||||||
|
"""
|
||||||
|
def get_params(tca_options, qdisc_type):
|
||||||
|
if qdisc_type not in TC_QDISC_TYPES:
|
||||||
|
return None, None, None
|
||||||
|
|
||||||
|
tca_params = _get_attr(tca_options,
|
||||||
|
'TCA_' + qdisc_type.upper() + '_PARMS')
|
||||||
|
burst_kb = int(
|
||||||
|
_calc_burst(tca_params['rate'], tca_params['buffer']) * 8 / 1024)
|
||||||
|
max_kbps = int(tca_params['ceil'] * 8 / 1024)
|
||||||
|
min_kbps = int(tca_params['rate'] * 8 / 1024)
|
||||||
|
return max_kbps, min_kbps, burst_kb
|
||||||
|
|
||||||
|
tc_classes = priv_tc_lib.list_tc_policy_classes(device,
|
||||||
|
namespace=namespace)
|
||||||
|
classes = []
|
||||||
|
for tc_class in tc_classes:
|
||||||
|
index = tc_class['index']
|
||||||
|
parent = TC_QDISC_PARENT_NAME.get(
|
||||||
|
tc_class['parent'], _handle_from_hex_to_string(tc_class['parent']))
|
||||||
|
classid = _handle_from_hex_to_string(tc_class['handle'])
|
||||||
|
qdisc_type = _get_attr(tc_class, 'TCA_KIND')
|
||||||
|
tca_options = _get_attr(tc_class, 'TCA_OPTIONS')
|
||||||
|
max_kbps, min_kbps, burst_kb = get_params(tca_options, qdisc_type)
|
||||||
|
classes.append({'device': device,
|
||||||
|
'index': index,
|
||||||
|
'namespace': namespace,
|
||||||
|
'parent': parent,
|
||||||
|
'classid': classid,
|
||||||
|
'qdisc_type': qdisc_type,
|
||||||
|
'min_kbps': min_kbps,
|
||||||
|
'max_kbps': max_kbps,
|
||||||
|
'burst_kb': burst_kb})
|
||||||
|
|
||||||
|
return classes
|
||||||
|
@ -83,3 +83,31 @@ def delete_tc_qdisc(device, parent=None, kind=None, namespace=None,
|
|||||||
if e.errno == errno.ENOENT:
|
if e.errno == errno.ENOENT:
|
||||||
raise ip_lib.NetworkNamespaceNotFound(netns_name=namespace)
|
raise ip_lib.NetworkNamespaceNotFound(netns_name=namespace)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@privileged.default.entrypoint
|
||||||
|
def add_tc_policy_class(device, parent, classid, qdisc_type, namespace=None,
|
||||||
|
**kwargs):
|
||||||
|
"""Add/replace TC policy class"""
|
||||||
|
try:
|
||||||
|
index = ip_lib.get_link_id(device, namespace)
|
||||||
|
with ip_lib.get_iproute(namespace) as ip:
|
||||||
|
ip.tc('replace-class', kind=qdisc_type, index=index,
|
||||||
|
handle=classid, parent=parent, **kwargs)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.ENOENT:
|
||||||
|
raise ip_lib.NetworkNamespaceNotFound(netns_name=namespace)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@privileged.default.entrypoint
|
||||||
|
def list_tc_policy_classes(device, namespace=None):
|
||||||
|
"""List all TC policy classes of a device"""
|
||||||
|
try:
|
||||||
|
index = ip_lib.get_link_id(device, namespace)
|
||||||
|
with ip_lib.get_iproute(namespace) as ip:
|
||||||
|
return ip_lib.make_serializable(ip.get_classes(index=index))
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.ENOENT:
|
||||||
|
raise ip_lib.NetworkNamespaceNotFound(netns_name=namespace)
|
||||||
|
raise
|
||||||
|
@ -151,3 +151,43 @@ class TcQdiscTestCase(functional_base.BaseSudoTestCase):
|
|||||||
priv_tc_lib.delete_tc_qdisc(self.device, kind='ingress',
|
priv_tc_lib.delete_tc_qdisc(self.device, kind='ingress',
|
||||||
namespace=self.namespace,
|
namespace=self.namespace,
|
||||||
raise_qdisc_not_found=False))
|
raise_qdisc_not_found=False))
|
||||||
|
|
||||||
|
|
||||||
|
class TcPolicyClassTestCase(functional_base.BaseSudoTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TcPolicyClassTestCase, 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(self.device, self.namespace, 'dummy')
|
||||||
|
|
||||||
|
def _remove_ns(self, namespace):
|
||||||
|
priv_ip_lib.remove_netns(namespace)
|
||||||
|
|
||||||
|
def test_add_tc_policy_class_htb(self):
|
||||||
|
priv_tc_lib.add_tc_qdisc(
|
||||||
|
self.device, kind='htb', parent=rtnl.TC_H_ROOT, handle='1:',
|
||||||
|
namespace=self.namespace)
|
||||||
|
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}}
|
||||||
|
for classid, rates in classes.items():
|
||||||
|
priv_tc_lib.add_tc_policy_class(
|
||||||
|
self.device, '1:', classid, 'htb', namespace=self.namespace,
|
||||||
|
**rates)
|
||||||
|
|
||||||
|
tc_classes = priv_tc_lib.list_tc_policy_classes(
|
||||||
|
self.device, namespace=self.namespace)
|
||||||
|
self.assertEqual(len(classes), len(tc_classes))
|
||||||
|
for tc_class in tc_classes:
|
||||||
|
handle = tc_lib._handle_from_hex_to_string(tc_class['handle'])
|
||||||
|
tca_options = tc_lib._get_attr(tc_class, 'TCA_OPTIONS')
|
||||||
|
tca_htb_params = tc_lib._get_attr(tca_options, 'TCA_HTB_PARMS')
|
||||||
|
self.assertEqual(classes[handle]['rate'], tca_htb_params['rate'])
|
||||||
|
self.assertEqual(classes[handle]['ceil'], tca_htb_params['ceil'])
|
||||||
|
burst = tc_lib._calc_burst(classes[handle]['rate'],
|
||||||
|
tca_htb_params['buffer'])
|
||||||
|
self.assertEqual(classes[handle]['burst'], burst)
|
||||||
|
@ -321,3 +321,44 @@ class TcTestCase(base.BaseTestCase):
|
|||||||
def test__get_tbf_burst_value_when_burst_smaller_then_minimal(self):
|
def test__get_tbf_burst_value_when_burst_smaller_then_minimal(self):
|
||||||
result = tc_lib._get_tbf_burst_value(BW_LIMIT, 0, KERNEL_HZ_VALUE)
|
result = tc_lib._get_tbf_burst_value(BW_LIMIT, 0, KERNEL_HZ_VALUE)
|
||||||
self.assertEqual(2, result)
|
self.assertEqual(2, result)
|
||||||
|
|
||||||
|
|
||||||
|
class TcPolicyClassTestCase(base.BaseTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TcPolicyClassTestCase, self).setUp()
|
||||||
|
self.mock_add_tc_policy_class = mock.patch.object(
|
||||||
|
priv_tc_lib, 'add_tc_policy_class').start()
|
||||||
|
self.mock_list_tc_policy_classes = mock.patch.object(
|
||||||
|
priv_tc_lib, 'list_tc_policy_classes').start()
|
||||||
|
self.namespace = 'namespace'
|
||||||
|
|
||||||
|
def test_add_tc_policy_class(self):
|
||||||
|
tc_lib.add_tc_policy_class(
|
||||||
|
'device', 'root', '1:10', 'qdisc_type', min_kbps=1000,
|
||||||
|
max_kbps=2000, burst_kb=1600, namespace=self.namespace)
|
||||||
|
self.mock_add_tc_policy_class.assert_called_once_with(
|
||||||
|
'device', rtnl.TC_H_ROOT, '1:10', 'qdisc_type', rate=1000 * 128,
|
||||||
|
ceil=2000 * 128, burst=1600 * 128, namespace=self.namespace)
|
||||||
|
|
||||||
|
def test_list_tc_policy_classes(self):
|
||||||
|
htb_params = {'buffer': 12500000, 'ceil': 256000, 'rate': 192000}
|
||||||
|
self.mock_list_tc_policy_classes.return_value = tuple([
|
||||||
|
{'index': 3, 'handle': 65537, 'parent': 4294967295,
|
||||||
|
'attrs': (
|
||||||
|
('TCA_KIND', 'htb'),
|
||||||
|
('TCA_OPTIONS', {
|
||||||
|
'attrs': tuple([('TCA_HTB_PARMS', htb_params)])}))
|
||||||
|
}])
|
||||||
|
_class = tc_lib.list_tc_policy_class('device',
|
||||||
|
namespace=self.namespace)[0]
|
||||||
|
reference = {'device': 'device',
|
||||||
|
'index': 3,
|
||||||
|
'namespace': self.namespace,
|
||||||
|
'parent': 'root',
|
||||||
|
'classid': '1:1',
|
||||||
|
'qdisc_type': 'htb',
|
||||||
|
'min_kbps': 1500,
|
||||||
|
'max_kbps': 2000,
|
||||||
|
'burst_kb': 1200}
|
||||||
|
self.assertEqual(reference, _class)
|
||||||
|
Loading…
Reference in New Issue
Block a user