From 006d4d07355eb5c77baf3fb1baa5c2cd22098766 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Thu, 13 Dec 2018 11:53:41 +0000 Subject: [PATCH] Add delete_tc_policy_class using pyroute2 Related-Bug: #1560963 Change-Id: Ide5b8975e00f98265da49fb3a4446e17c34af3bc --- neutron/agent/linux/tc_lib.py | 12 ++++ neutron/privileged/agent/linux/tc_lib.py | 31 +++++++++ .../privileged/agent/linux/test_tc_lib.py | 65 +++++++++++++++---- 3 files changed, 96 insertions(+), 12 deletions(-) diff --git a/neutron/agent/linux/tc_lib.py b/neutron/agent/linux/tc_lib.py index b72a94acf60..ccf87936ada 100644 --- a/neutron/agent/linux/tc_lib.py +++ b/neutron/agent/linux/tc_lib.py @@ -444,3 +444,15 @@ def list_tc_policy_class(device, namespace=None): 'burst_kb': burst_kb}) return classes + + +def delete_tc_policy_class(device, parent, classid, namespace=None): + """Delete a TC policy class of a device. + + :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 namespace: (string) (optional) namespace name + """ + priv_tc_lib.delete_tc_policy_class(device, parent, classid, + namespace=namespace) diff --git a/neutron/privileged/agent/linux/tc_lib.py b/neutron/privileged/agent/linux/tc_lib.py index ffb8b4f6994..c11a0fca01e 100644 --- a/neutron/privileged/agent/linux/tc_lib.py +++ b/neutron/privileged/agent/linux/tc_lib.py @@ -18,6 +18,7 @@ import socket from neutron_lib import constants as n_constants import pyroute2 +from neutron._i18n import _ from neutron import privileged from neutron.privileged.agent.linux import ip_lib @@ -26,6 +27,16 @@ _IP_VERSION_FAMILY_MAP = {n_constants.IP_VERSION_4: socket.AF_INET, n_constants.IP_VERSION_6: socket.AF_INET6} +class TrafficControlClassNotFound(RuntimeError): + message = _('Traffic control class %(classid)s not found in namespace ' + '%(namespace)s.') + + def __init__(self, message=None, classid=None, namespace=None): + message = message or self.message % { + 'classid': classid, 'namespace': namespace} + super(TrafficControlClassNotFound, self).__init__(message) + + @privileged.default.entrypoint def add_tc_qdisc(device, namespace=None, **kwargs): """Add TC qdisc""" @@ -111,3 +122,23 @@ def list_tc_policy_classes(device, namespace=None): if e.errno == errno.ENOENT: raise ip_lib.NetworkNamespaceNotFound(netns_name=namespace) raise + + +@privileged.default.entrypoint +def delete_tc_policy_class(device, parent, classid, namespace=None, + **kwargs): + """Delete TC policy class""" + try: + index = ip_lib.get_link_id(device, namespace) + with ip_lib.get_iproute(namespace) as ip: + ip.tc('del-class', index=index, handle=classid, parent=parent, + **kwargs) + except OSError as e: + if e.errno == errno.ENOENT: + raise ip_lib.NetworkNamespaceNotFound(netns_name=namespace) + raise + except pyroute2.NetlinkError as e: + if e.code == errno.ENOENT: + raise TrafficControlClassNotFound(classid=classid, + namespace=namespace) + raise diff --git a/neutron/tests/functional/privileged/agent/linux/test_tc_lib.py b/neutron/tests/functional/privileged/agent/linux/test_tc_lib.py index 5464b372447..d88e9c16071 100644 --- a/neutron/tests/functional/privileged/agent/linux/test_tc_lib.py +++ b/neutron/tests/functional/privileged/agent/linux/test_tc_lib.py @@ -155,39 +155,80 @@ class TcQdiscTestCase(functional_base.BaseSudoTestCase): class TcPolicyClassTestCase(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(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') + 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_policy_class_htb(self): priv_tc_lib.add_tc_qdisc( - self.device, kind='htb', parent=rtnl.TC_H_ROOT, handle='1:', + self.device, parent=rtnl.TC_H_ROOT, kind='htb', 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(): + for classid, rates in self.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)) + self.assertEqual(len(self.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'], + self.assertEqual(self.CLASSES[handle]['rate'], + tca_htb_params['rate']) + self.assertEqual(self.CLASSES[handle]['ceil'], + tca_htb_params['ceil']) + burst = tc_lib._calc_burst(self.CLASSES[handle]['rate'], tca_htb_params['buffer']) - self.assertEqual(classes[handle]['burst'], burst) + self.assertEqual(self.CLASSES[handle]['burst'], burst) + + def test_delete_tc_policy_class_htb(self): + priv_tc_lib.add_tc_qdisc( + self.device, parent=rtnl.TC_H_ROOT, kind='htb', handle='1:', + namespace=self.namespace) + for classid, rates in self.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(self.CLASSES), len(tc_classes)) + + for classid in self.CLASSES: + priv_tc_lib.delete_tc_policy_class( + self.device, '1:', classid, namespace=self.namespace) + tc_classes = priv_tc_lib.list_tc_policy_classes( + self.device, namespace=self.namespace) + for tc_class in tc_classes: + handle = tc_lib._handle_from_hex_to_string(tc_class['handle']) + self.assertIsNot(classid, handle) + + tc_classes = priv_tc_lib.list_tc_policy_classes( + self.device, namespace=self.namespace) + self.assertEqual(0, len(tc_classes)) + + def test_delete_tc_policy_class_no_namespace(self): + self.assertRaises( + priv_ip_lib.NetworkNamespaceNotFound, + priv_tc_lib.delete_tc_policy_class, 'device', 'parent', 'classid', + namespace='non_existing_namespace') + + def test_delete_tc_policy_class_no_class(self): + self.assertRaises( + priv_tc_lib.TrafficControlClassNotFound, + priv_tc_lib.delete_tc_policy_class, self.device, '1:', + '1:1000', namespace=self.namespace)