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):
|
||||
"""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', [])
|
||||
for attr in (attr for attr in rule_attrs if attr[0] == attr_name):
|
||||
return attr[1]
|
||||
|
@ -230,19 +242,14 @@ class TcCommand(ip_lib.IPDevice):
|
|||
def delete_filters_bw_limit(self):
|
||||
# NOTE(slaweq): For limit traffic egress from instance we need to use
|
||||
# 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):
|
||||
self._delete_qdisc("root")
|
||||
|
||||
def _delete_qdisc(self, qdisc_name):
|
||||
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])
|
||||
delete_tc_qdisc(self.name, parent='root',
|
||||
raise_interface_not_found=False,
|
||||
raise_qdisc_not_found=False, namespace=self.namespace)
|
||||
|
||||
def _add_policy_filter(self, bw_limit, burst_limit,
|
||||
qdisc_id=INGRESS_QDISC_ID):
|
||||
|
@ -339,3 +346,26 @@ def list_tc_qdiscs(device, namespace=None):
|
|||
retval.append(qdisc_attrs)
|
||||
|
||||
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
|
||||
|
||||
from neutron_lib import constants as n_constants
|
||||
import pyroute2
|
||||
|
||||
from neutron import privileged
|
||||
from neutron.privileged.agent.linux import ip_lib
|
||||
|
@ -49,3 +50,36 @@ def list_tc_qdiscs(device, namespace=None):
|
|||
if e.errno == errno.ENOENT:
|
||||
raise ip_lib.NetworkNamespaceNotFound(netns_name=namespace)
|
||||
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
|
||||
# under the License.
|
||||
|
||||
import errno
|
||||
|
||||
from oslo_utils import uuidutils
|
||||
import pyroute2
|
||||
from pyroute2.netlink import rtnl
|
||||
|
||||
from neutron.agent.linux import tc_lib
|
||||
|
@ -45,6 +48,12 @@ class TcQdiscTestCase(functional_base.BaseSudoTestCase):
|
|||
self.assertEqual(0x50000, qdiscs[0]['handle'])
|
||||
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):
|
||||
priv_tc_lib.add_tc_qdisc(
|
||||
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('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):
|
||||
burst = 192000
|
||||
rate = 320000
|
||||
|
@ -76,6 +91,12 @@ class TcQdiscTestCase(functional_base.BaseSudoTestCase):
|
|||
self.assertEqual(latency, tc_lib._calc_latency_ms(
|
||||
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):
|
||||
priv_tc_lib.add_tc_qdisc(self.device, kind='ingress',
|
||||
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(rtnl.TC_H_INGRESS, qdiscs[0]['parent'])
|
||||
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()
|
||||
self.mock_add_tc_qdisc = mock.patch.object(tc_lib,
|
||||
'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):
|
||||
self.assertRaises(
|
||||
|
@ -164,50 +166,29 @@ class TestTcCommand(base.BaseTestCase):
|
|||
|
||||
def test_update_filters_bw_limit(self):
|
||||
self.tc.update_filters_bw_limit(BW_LIMIT, BURST)
|
||||
self.execute.assert_has_calls([
|
||||
mock.call(
|
||||
["tc", "qdisc", "del", "dev", DEVICE_NAME, "ingress"],
|
||||
run_as_root=True,
|
||||
check_exit_code=True,
|
||||
log_fail_as_error=True,
|
||||
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.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.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):
|
||||
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=[1, 2]
|
||||
)
|
||||
self.mock_delete_tc_qdisc.assert_called_once_with(
|
||||
DEVICE_NAME, is_ingress=True, raise_interface_not_found=False,
|
||||
raise_qdisc_not_found=False, namespace=self.tc.namespace)
|
||||
|
||||
def test_delete_tbf_bw_limit(self):
|
||||
self.tc.delete_tbf_bw_limit()
|
||||
self.execute.assert_called_once_with(
|
||||
["tc", "qdisc", "del", "dev", DEVICE_NAME, "root"],
|
||||
run_as_root=True,
|
||||
check_exit_code=True,
|
||||
log_fail_as_error=True,
|
||||
extra_ok_codes=[1, 2]
|
||||
)
|
||||
self.mock_delete_tc_qdisc.assert_called_once_with(
|
||||
DEVICE_NAME, parent='root', raise_interface_not_found=False,
|
||||
raise_qdisc_not_found=False, namespace=self.tc.namespace)
|
||||
|
||||
def test_get_ingress_qdisc_burst_value_burst_not_none(self):
|
||||
self.assertEqual(
|
||||
|
|
Loading…
Reference in New Issue