Implement delete_tc_qdisc using pyroute2
Related-Bug: #1560963 Change-Id: I79932dfc7464f9d904047df12d0f3f9cd5508a50
This commit is contained in:
parent
d3a131d0b2
commit
17a7a62e84
|
@ -95,6 +95,18 @@ def convert_to_kilobits(value, base):
|
||||||
|
|
||||||
|
|
||||||
def _get_attr(pyroute2_obj, attr_name):
|
def _get_attr(pyroute2_obj, attr_name):
|
||||||
|
"""Get an attribute in a pyroute object
|
||||||
|
|
||||||
|
pyroute2 object attributes are stored under a key called 'attrs'. This key
|
||||||
|
contains a tuple of tuples. E.g.:
|
||||||
|
pyroute2_obj = {'attrs': (('TCA_KIND': 'htb'),
|
||||||
|
('TCA_OPTIONS': {...}))}
|
||||||
|
|
||||||
|
:param pyroute2_obj: (dict) pyroute2 object
|
||||||
|
:param attr_name: (string) first value of the tuple we are looking for
|
||||||
|
:return: (object) second value of the tuple, None if the tuple doesn't
|
||||||
|
exist
|
||||||
|
"""
|
||||||
rule_attrs = pyroute2_obj.get('attrs', [])
|
rule_attrs = pyroute2_obj.get('attrs', [])
|
||||||
for attr in (attr for attr in rule_attrs if attr[0] == attr_name):
|
for attr in (attr for attr in rule_attrs if attr[0] == attr_name):
|
||||||
return attr[1]
|
return attr[1]
|
||||||
|
@ -230,19 +242,14 @@ class TcCommand(ip_lib.IPDevice):
|
||||||
def delete_filters_bw_limit(self):
|
def delete_filters_bw_limit(self):
|
||||||
# NOTE(slaweq): For limit traffic egress from instance we need to use
|
# NOTE(slaweq): For limit traffic egress from instance we need to use
|
||||||
# qdisc "ingress" because it is ingress traffic from interface POV:
|
# qdisc "ingress" because it is ingress traffic from interface POV:
|
||||||
self._delete_qdisc("ingress")
|
delete_tc_qdisc(self.name, is_ingress=True,
|
||||||
|
raise_interface_not_found=False,
|
||||||
|
raise_qdisc_not_found=False, namespace=self.namespace)
|
||||||
|
|
||||||
def delete_tbf_bw_limit(self):
|
def delete_tbf_bw_limit(self):
|
||||||
self._delete_qdisc("root")
|
delete_tc_qdisc(self.name, parent='root',
|
||||||
|
raise_interface_not_found=False,
|
||||||
def _delete_qdisc(self, qdisc_name):
|
raise_qdisc_not_found=False, namespace=self.namespace)
|
||||||
cmd = ['qdisc', 'del', 'dev', self.name, qdisc_name]
|
|
||||||
# Return_code=2 is fine because it means
|
|
||||||
# "RTNETLINK answers: No such file or directory" what is fine when we
|
|
||||||
# are trying to delete qdisc
|
|
||||||
# Return_code=1 means "RTNETLINK answers: Cannot find device <device>".
|
|
||||||
# If the device doesn't exist, the qdisc is already deleted.
|
|
||||||
return self._execute_tc_cmd(cmd, extra_ok_codes=[1, 2])
|
|
||||||
|
|
||||||
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):
|
||||||
|
@ -339,3 +346,26 @@ def list_tc_qdiscs(device, namespace=None):
|
||||||
retval.append(qdisc_attrs)
|
retval.append(qdisc_attrs)
|
||||||
|
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
|
||||||
|
def delete_tc_qdisc(device, parent=None, is_ingress=False,
|
||||||
|
raise_interface_not_found=True, raise_qdisc_not_found=True,
|
||||||
|
namespace=None):
|
||||||
|
"""Delete a TC qdisc of a device
|
||||||
|
|
||||||
|
:param device: (string) device name
|
||||||
|
:param parent: (string) (optional) qdisc parent class ('root', '2:10')
|
||||||
|
:param is_ingress: (bool) (optional) if qdisc type is 'ingress'
|
||||||
|
:param raise_interface_not_found: (bool) (optional) raise exception if the
|
||||||
|
interface doesn't exist
|
||||||
|
:param raise_qdisc_not_found: (bool) (optional) raise exception if the
|
||||||
|
qdisc doesn't exist
|
||||||
|
:param namespace: (string) (optional) namespace name
|
||||||
|
"""
|
||||||
|
qdisc_type = 'ingress' if is_ingress else None
|
||||||
|
if parent:
|
||||||
|
parent = rtnl.TC_H_ROOT if parent == 'root' else parent
|
||||||
|
priv_tc_lib.delete_tc_qdisc(
|
||||||
|
device, parent=parent, kind=qdisc_type,
|
||||||
|
raise_interface_not_found=raise_interface_not_found,
|
||||||
|
raise_qdisc_not_found=raise_qdisc_not_found, namespace=namespace)
|
||||||
|
|
|
@ -16,6 +16,7 @@ import errno
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
from neutron_lib import constants as n_constants
|
from neutron_lib import constants as n_constants
|
||||||
|
import pyroute2
|
||||||
|
|
||||||
from neutron import privileged
|
from neutron import privileged
|
||||||
from neutron.privileged.agent.linux import ip_lib
|
from neutron.privileged.agent.linux import ip_lib
|
||||||
|
@ -49,3 +50,36 @@ def list_tc_qdiscs(device, 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 delete_tc_qdisc(device, parent=None, kind=None, namespace=None,
|
||||||
|
raise_interface_not_found=True,
|
||||||
|
raise_qdisc_not_found=True):
|
||||||
|
"""Delete a TC qdisc of a device"""
|
||||||
|
try:
|
||||||
|
index = ip_lib.get_link_id(device, namespace)
|
||||||
|
args = {}
|
||||||
|
if parent:
|
||||||
|
args['parent'] = parent
|
||||||
|
if kind:
|
||||||
|
args['kind'] = kind
|
||||||
|
with ip_lib.get_iproute(namespace) as ip:
|
||||||
|
ip.tc('del', index=index, **args)
|
||||||
|
except ip_lib.NetworkInterfaceNotFound:
|
||||||
|
if raise_interface_not_found:
|
||||||
|
raise
|
||||||
|
except pyroute2.NetlinkError as e:
|
||||||
|
# NOTE(ralonsoh): tc delete will raise a NetlinkError exception with
|
||||||
|
# code (22, 'Invalid argument') if kind='ingress' and the qdisc does
|
||||||
|
# not exist. This behaviour must be refactored in pyroute2.
|
||||||
|
if ((e.code == errno.ENOENT or
|
||||||
|
(e.code == errno.EINVAL and kind == 'ingress')) and
|
||||||
|
raise_qdisc_not_found is False):
|
||||||
|
# NOTE(ralonsoh): return error code for testing purposes
|
||||||
|
return e.code
|
||||||
|
raise
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.ENOENT:
|
||||||
|
raise ip_lib.NetworkNamespaceNotFound(netns_name=namespace)
|
||||||
|
raise
|
||||||
|
|
|
@ -12,7 +12,10 @@
|
||||||
# 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 errno
|
||||||
|
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
import pyroute2
|
||||||
from pyroute2.netlink import rtnl
|
from pyroute2.netlink import rtnl
|
||||||
|
|
||||||
from neutron.agent.linux import tc_lib
|
from neutron.agent.linux import tc_lib
|
||||||
|
@ -45,6 +48,12 @@ class TcQdiscTestCase(functional_base.BaseSudoTestCase):
|
||||||
self.assertEqual(0x50000, qdiscs[0]['handle'])
|
self.assertEqual(0x50000, qdiscs[0]['handle'])
|
||||||
self.assertEqual('htb', tc_lib._get_attr(qdiscs[0], 'TCA_KIND'))
|
self.assertEqual('htb', tc_lib._get_attr(qdiscs[0], 'TCA_KIND'))
|
||||||
|
|
||||||
|
priv_tc_lib.delete_tc_qdisc(self.device, rtnl.TC_H_ROOT,
|
||||||
|
namespace=self.namespace)
|
||||||
|
qdiscs = priv_tc_lib.list_tc_qdiscs(self.device,
|
||||||
|
namespace=self.namespace)
|
||||||
|
self.assertEqual(0, len(qdiscs))
|
||||||
|
|
||||||
def test_add_tc_qdisc_htb_no_handle(self):
|
def test_add_tc_qdisc_htb_no_handle(self):
|
||||||
priv_tc_lib.add_tc_qdisc(
|
priv_tc_lib.add_tc_qdisc(
|
||||||
self.device, parent=rtnl.TC_H_ROOT, kind='htb',
|
self.device, parent=rtnl.TC_H_ROOT, kind='htb',
|
||||||
|
@ -56,6 +65,12 @@ class TcQdiscTestCase(functional_base.BaseSudoTestCase):
|
||||||
self.assertEqual(0, qdiscs[0]['handle'] & 0xFFFF)
|
self.assertEqual(0, qdiscs[0]['handle'] & 0xFFFF)
|
||||||
self.assertEqual('htb', tc_lib._get_attr(qdiscs[0], 'TCA_KIND'))
|
self.assertEqual('htb', tc_lib._get_attr(qdiscs[0], 'TCA_KIND'))
|
||||||
|
|
||||||
|
priv_tc_lib.delete_tc_qdisc(self.device, parent=rtnl.TC_H_ROOT,
|
||||||
|
namespace=self.namespace)
|
||||||
|
qdiscs = priv_tc_lib.list_tc_qdiscs(self.device,
|
||||||
|
namespace=self.namespace)
|
||||||
|
self.assertEqual(0, len(qdiscs))
|
||||||
|
|
||||||
def test_add_tc_qdisc_tbf(self):
|
def test_add_tc_qdisc_tbf(self):
|
||||||
burst = 192000
|
burst = 192000
|
||||||
rate = 320000
|
rate = 320000
|
||||||
|
@ -76,6 +91,12 @@ class TcQdiscTestCase(functional_base.BaseSudoTestCase):
|
||||||
self.assertEqual(latency, tc_lib._calc_latency_ms(
|
self.assertEqual(latency, tc_lib._calc_latency_ms(
|
||||||
tca_tbf_parms['limit'], burst, tca_tbf_parms['rate']) * 1000)
|
tca_tbf_parms['limit'], burst, tca_tbf_parms['rate']) * 1000)
|
||||||
|
|
||||||
|
priv_tc_lib.delete_tc_qdisc(self.device, parent=rtnl.TC_H_ROOT,
|
||||||
|
namespace=self.namespace)
|
||||||
|
qdiscs = priv_tc_lib.list_tc_qdiscs(self.device,
|
||||||
|
namespace=self.namespace)
|
||||||
|
self.assertEqual(0, len(qdiscs))
|
||||||
|
|
||||||
def test_add_tc_qdisc_ingress(self):
|
def test_add_tc_qdisc_ingress(self):
|
||||||
priv_tc_lib.add_tc_qdisc(self.device, kind='ingress',
|
priv_tc_lib.add_tc_qdisc(self.device, kind='ingress',
|
||||||
namespace=self.namespace)
|
namespace=self.namespace)
|
||||||
|
@ -85,3 +106,48 @@ class TcQdiscTestCase(functional_base.BaseSudoTestCase):
|
||||||
self.assertEqual('ingress', tc_lib._get_attr(qdiscs[0], 'TCA_KIND'))
|
self.assertEqual('ingress', tc_lib._get_attr(qdiscs[0], 'TCA_KIND'))
|
||||||
self.assertEqual(rtnl.TC_H_INGRESS, qdiscs[0]['parent'])
|
self.assertEqual(rtnl.TC_H_INGRESS, qdiscs[0]['parent'])
|
||||||
self.assertEqual(0xffff0000, qdiscs[0]['handle'])
|
self.assertEqual(0xffff0000, qdiscs[0]['handle'])
|
||||||
|
|
||||||
|
priv_tc_lib.delete_tc_qdisc(self.device, kind='ingress',
|
||||||
|
namespace=self.namespace)
|
||||||
|
qdiscs = priv_tc_lib.list_tc_qdiscs(self.device,
|
||||||
|
namespace=self.namespace)
|
||||||
|
self.assertEqual(0, len(qdiscs))
|
||||||
|
|
||||||
|
def test_delete_tc_qdisc_no_device(self):
|
||||||
|
self.assertRaises(
|
||||||
|
priv_ip_lib.NetworkInterfaceNotFound, priv_tc_lib.delete_tc_qdisc,
|
||||||
|
'other_device', rtnl.TC_H_ROOT, namespace=self.namespace)
|
||||||
|
|
||||||
|
def test_delete_tc_qdisc_no_device_no_exception(self):
|
||||||
|
self.assertIsNone(priv_tc_lib.delete_tc_qdisc(
|
||||||
|
'other_device', rtnl.TC_H_ROOT, namespace=self.namespace,
|
||||||
|
raise_interface_not_found=False))
|
||||||
|
|
||||||
|
def test_delete_tc_qdisc_no_qdisc(self):
|
||||||
|
self.assertRaises(
|
||||||
|
pyroute2.NetlinkError, priv_tc_lib.delete_tc_qdisc,
|
||||||
|
self.device, rtnl.TC_H_ROOT, namespace=self.namespace)
|
||||||
|
|
||||||
|
def test_delete_tc_qdisc_no_qdisc_no_exception(self):
|
||||||
|
self.assertEqual(2, priv_tc_lib.delete_tc_qdisc(
|
||||||
|
self.device, rtnl.TC_H_ROOT, namespace=self.namespace,
|
||||||
|
raise_qdisc_not_found=False))
|
||||||
|
|
||||||
|
def test_delete_tc_qdisc_ingress_twice(self):
|
||||||
|
priv_tc_lib.add_tc_qdisc(self.device, kind='ingress',
|
||||||
|
namespace=self.namespace)
|
||||||
|
qdiscs = priv_tc_lib.list_tc_qdiscs(self.device,
|
||||||
|
namespace=self.namespace)
|
||||||
|
self.assertEqual(1, len(qdiscs))
|
||||||
|
self.assertEqual('ingress', tc_lib._get_attr(qdiscs[0], 'TCA_KIND'))
|
||||||
|
self.assertIsNone(
|
||||||
|
priv_tc_lib.delete_tc_qdisc(self.device, kind='ingress',
|
||||||
|
namespace=self.namespace))
|
||||||
|
qdiscs = priv_tc_lib.list_tc_qdiscs(self.device,
|
||||||
|
namespace=self.namespace)
|
||||||
|
self.assertEqual(0, len(qdiscs))
|
||||||
|
self.assertEqual(
|
||||||
|
errno.EINVAL,
|
||||||
|
priv_tc_lib.delete_tc_qdisc(self.device, kind='ingress',
|
||||||
|
namespace=self.namespace,
|
||||||
|
raise_qdisc_not_found=False))
|
||||||
|
|
|
@ -113,6 +113,8 @@ class TestTcCommand(base.BaseTestCase):
|
||||||
'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(
|
||||||
|
tc_lib, 'delete_tc_qdisc').start()
|
||||||
|
|
||||||
def test_check_kernel_hz_lower_then_zero(self):
|
def test_check_kernel_hz_lower_then_zero(self):
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
|
@ -164,50 +166,29 @@ 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_has_calls([
|
self.execute.assert_called_once_with(
|
||||||
mock.call(
|
['tc', 'filter', 'add', 'dev', DEVICE_NAME, 'parent',
|
||||||
["tc", "qdisc", "del", "dev", DEVICE_NAME, "ingress"],
|
tc_lib.INGRESS_QDISC_ID, 'protocol', 'all', 'prio', '49',
|
||||||
run_as_root=True,
|
'basic', 'police', 'rate', self.bw_limit, 'burst', self.burst,
|
||||||
check_exit_code=True,
|
'mtu', tc_lib.MAX_MTU_VALUE, 'drop'], run_as_root=True,
|
||||||
log_fail_as_error=True,
|
check_exit_code=True, log_fail_as_error=True, extra_ok_codes=None)
|
||||||
extra_ok_codes=[1, 2]
|
|
||||||
),
|
|
||||||
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
|
|
||||||
)]
|
|
||||||
)
|
|
||||||
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.tc.name, is_ingress=True, raise_interface_not_found=False,
|
||||||
|
raise_qdisc_not_found=False, namespace=self.tc.namespace)
|
||||||
|
|
||||||
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()
|
||||||
self.execute.assert_called_once_with(
|
self.mock_delete_tc_qdisc.assert_called_once_with(
|
||||||
["tc", "qdisc", "del", "dev", DEVICE_NAME, "ingress"],
|
DEVICE_NAME, is_ingress=True, raise_interface_not_found=False,
|
||||||
run_as_root=True,
|
raise_qdisc_not_found=False, namespace=self.tc.namespace)
|
||||||
check_exit_code=True,
|
|
||||||
log_fail_as_error=True,
|
|
||||||
extra_ok_codes=[1, 2]
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_delete_tbf_bw_limit(self):
|
def test_delete_tbf_bw_limit(self):
|
||||||
self.tc.delete_tbf_bw_limit()
|
self.tc.delete_tbf_bw_limit()
|
||||||
self.execute.assert_called_once_with(
|
self.mock_delete_tc_qdisc.assert_called_once_with(
|
||||||
["tc", "qdisc", "del", "dev", DEVICE_NAME, "root"],
|
DEVICE_NAME, parent='root', raise_interface_not_found=False,
|
||||||
run_as_root=True,
|
raise_qdisc_not_found=False, namespace=self.tc.namespace)
|
||||||
check_exit_code=True,
|
|
||||||
log_fail_as_error=True,
|
|
||||||
extra_ok_codes=[1, 2]
|
|
||||||
)
|
|
||||||
|
|
||||||
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(
|
||||||
|
|
Loading…
Reference in New Issue