Support iproute2 4.15 in l3_tc_lib

In version 4.15 of iproute2 there was added support
for chain index in tc_filter [1].
Such version is available e.g. in Ubuntu 18.04 and it
has to be supported in l3_tc_lib regex to match
properly output of "tc filter" command.

[1] https://lwn.net/Articles/745643/

Closes-bug: #1809497
Change-Id: Id4066b5cff933ccd0dd3c751bf67b5d58af662d1
(cherry picked from commit e788d29458)
This commit is contained in:
Slawek Kaplonski 2019-01-14 22:29:25 +01:00
parent 9ab3d21789
commit e04ed368ad
2 changed files with 110 additions and 34 deletions

View File

@ -23,7 +23,10 @@ LOG = logging.getLogger(__name__)
QDISC_IN_REGEX = re.compile(r"qdisc ingress (\w+:) *")
QDISC_OUT_REGEX = re.compile(r"qdisc htb (\w+:) *")
FILTER_ID_REGEX = re.compile(r"filter protocol ip u32 fh (\w+::\w+) *")
# NOTE(slaweq): in iproute 4.15 chain value was added to filter output and this
# needs to be included in REGEX
FILTER_ID_REGEX = re.compile(
r"filter protocol ip u32 (fh|chain \d+ fh) (\w+::\w+) *")
FILTER_STATS_REGEX = re.compile(r"Sent (\w+) bytes (\w+) pkts *")
@ -72,7 +75,7 @@ class FloatingIPTcCommandBase(ip_lib.IPDevice):
line = line.strip()
m = FILTER_ID_REGEX.match(line)
if m:
filter_id = m.group(1)
filter_id = m.group(2)
# It matched, so ip/32 is not here. continue
continue
elif not line.startswith('match'):
@ -102,7 +105,7 @@ class FloatingIPTcCommandBase(ip_lib.IPDevice):
line = line.strip()
m = FILTER_ID_REGEX.match(line)
if m:
filter_id = m.group(1)
filter_id = m.group(2)
filterids.append(filter_id)
return filterids

View File

@ -25,10 +25,10 @@ FLOATING_IP_2 = "172.16.10.105"
FILETER_ID_1 = "800::800"
FILETER_ID_2 = "800::801"
TC_INGRESS_FILTERS = (
TC_INGRESS_FILTERS_BASE = (
'filter protocol ip u32 \n'
'filter protocol ip u32 fh 800: ht divisor 1 \n'
'filter protocol ip u32 fh %(filter_id1)s order 2048 key '
'filter protocol ip u32 %(chain_value)sfh 800: ht divisor 1 \n'
'filter protocol ip u32 %(chain_value)sfh %(filter_id1)s order 2048 key '
'ht 800 bkt 0 '
'flowid :1 (rule hit 0 success 0)\n'
' match IP dst %(fip1)s/32 (success 0 ) \n'
@ -36,7 +36,33 @@ TC_INGRESS_FILTERS = (
'ref 1 bind 1\n'
'\n'
' Sent 111 bytes 222 pkts (dropped 0, overlimits 0) \n'
'filter protocol ip u32 fh %(filter_id2)s order 2049 key '
'filter protocol ip u32 %(chain_value)sfh %(filter_id2)s order 2049 key '
'ht 800 bkt 0 '
'flowid :1 (rule hit 0 success 0)\n'
' match IP dst %(fip2)s/32 (success 0 ) \n'
' police 0x1b rate 22000Kbit burst 22Mb mtu 64Kb action drop '
'overhead 0b \n'
'ref 1 bind 1\n'
'\n'
' Sent 111 bytes 222 pkts (dropped 0, overlimits 0)\n')
TC_INGRESS_FILTERS_WITHOUT_CHAIN = TC_INGRESS_FILTERS_BASE % {
"chain_value": "",
"filter_id1": FILETER_ID_1,
"fip1": FLOATING_IP_1,
"filter_id2": FILETER_ID_2,
"fip2": FLOATING_IP_2}
# NOTE(slaweq): in iproute 4.15 chain value was added to filter output
TC_INGRESS_FILTERS_WITH_CHAIN = TC_INGRESS_FILTERS_BASE % {
"chain_value": "chain 1 ",
"filter_id1": FILETER_ID_1,
"fip1": FLOATING_IP_1,
"filter_id2": FILETER_ID_2,
"fip2": FLOATING_IP_2}
TC_INGRESS_FILTERS_DUP_WITHOUT_CHAIN = TC_INGRESS_FILTERS_WITHOUT_CHAIN + (
'filter protocol ip u32 %(chain_value)sfh %(filter_id2)s order 2049 key '
'ht 800 bkt 0 '
'flowid :1 (rule hit 0 success 0)\n'
' match IP dst %(fip2)s/32 (success 0 ) \n'
@ -45,13 +71,12 @@ TC_INGRESS_FILTERS = (
'ref 1 bind 1\n'
'\n'
' Sent 111 bytes 222 pkts (dropped 0, overlimits 0)\n') % {
"filter_id1": FILETER_ID_1,
"fip1": FLOATING_IP_1,
"chain_value": "",
"filter_id2": FILETER_ID_2,
"fip2": FLOATING_IP_2}
TC_INGRESS_FILTERS_DUP = TC_INGRESS_FILTERS + (
'filter protocol ip u32 fh %(filter_id2)s order 2049 key '
TC_INGRESS_FILTERS_DUP_WITH_CHAIN = TC_INGRESS_FILTERS_WITH_CHAIN + (
'filter protocol ip u32 %(chain_value)sfh %(filter_id2)s order 2049 key '
'ht 800 bkt 0 '
'flowid :1 (rule hit 0 success 0)\n'
' match IP dst %(fip2)s/32 (success 0 ) \n'
@ -60,13 +85,14 @@ TC_INGRESS_FILTERS_DUP = TC_INGRESS_FILTERS + (
'ref 1 bind 1\n'
'\n'
' Sent 111 bytes 222 pkts (dropped 0, overlimits 0)\n') % {
"chain_value": "chain 1 ",
"filter_id2": FILETER_ID_2,
"fip2": FLOATING_IP_2}
TC_EGRESS_FILTERS = (
TC_EGRESS_FILTERS_BASE = (
'filter protocol ip u32 \n'
'filter protocol ip u32 fh 800: ht divisor 1 \n'
'filter protocol ip u32 fh %(filter_id1)s order 2048 key '
'filter protocol ip u32 %(chain_name)sfh 800: ht divisor 1 \n'
'filter protocol ip u32 %(chain_name)sfh %(filter_id1)s order 2048 key '
'ht 800 bkt 0 '
'flowid :1 (rule hit 0 success 0)\n'
' match IP src %(fip1)s/32 (success 0 ) \n'
@ -74,7 +100,7 @@ TC_EGRESS_FILTERS = (
'ref 1 bind 1\n'
'\n'
' Sent 111 bytes 222 pkts (dropped 0, overlimits 0) \n'
'filter protocol ip u32 fh %(filter_id2)s order 2049 key '
'filter protocol ip u32 %(chain_name)sfh %(filter_id2)s order 2049 key '
'ht 800 bkt 0 '
'flowid :1 (rule hit 0 success 0)\n'
' match IP src %(fip2)s/32 (success 0 ) \n'
@ -82,13 +108,21 @@ TC_EGRESS_FILTERS = (
'overhead 0b \n'
'ref 1 bind 1\n'
'\n'
' Sent 111 bytes 222 pkts (dropped 0, overlimits 0)\n') % {
"filter_id1": FILETER_ID_1,
"fip1": FLOATING_IP_1,
"filter_id2": FILETER_ID_2,
"fip2": FLOATING_IP_2}
FILTERS_IDS = {constants.INGRESS_DIRECTION: TC_INGRESS_FILTERS,
constants.EGRESS_DIRECTION: TC_EGRESS_FILTERS}
' Sent 111 bytes 222 pkts (dropped 0, overlimits 0)\n')
TC_EGRESS_FILTERS_WITHOUT_CHAIN = TC_EGRESS_FILTERS_BASE % {
"chain_name": "",
"filter_id1": FILETER_ID_1,
"fip1": FLOATING_IP_1,
"filter_id2": FILETER_ID_2,
"fip2": FLOATING_IP_2}
TC_EGRESS_FILTERS_WITH_CHAIN = TC_EGRESS_FILTERS_BASE % {
"chain_name": "chain 1 ",
"filter_id1": FILETER_ID_1,
"fip1": FLOATING_IP_1,
"filter_id2": FILETER_ID_2,
"fip2": FLOATING_IP_2}
INGRESS_QSIC_ID = "ffff:"
EGRESS_QDISC_ID = "1:"
@ -164,13 +198,19 @@ class TestFloatingIPTcCommandBase(base.BaseTestCase):
extra_ok_codes=None
)
def test__get_filterid_for_ip(self):
def _test__get_filterid_for_ip(self, filters):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_filters') as get_filters:
get_filters.return_value = TC_EGRESS_FILTERS
get_filters.return_value = filters
f_id = self.tc._get_filterid_for_ip(INGRESS_QSIC_ID, FLOATING_IP_1)
self.assertEqual(FILETER_ID_1, f_id)
def test__get_filterid_for_ip_without_chain(self):
self._test__get_filterid_for_ip(TC_EGRESS_FILTERS_WITHOUT_CHAIN)
def test__get_filterid_for_ip_with_chain(self):
self._test__get_filterid_for_ip(TC_EGRESS_FILTERS_WITH_CHAIN)
def test__get_filterid_for_ip_no_output(self):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_filters') as get_filters:
@ -179,22 +219,37 @@ class TestFloatingIPTcCommandBase(base.BaseTestCase):
self.tc._get_filterid_for_ip,
INGRESS_QSIC_ID, FLOATING_IP_1)
def test__get_filterid_for_ip_duplicated(self):
def _test__get_filterid_for_ip_duplicated(self, filters):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_filters') as get_filters:
get_filters.return_value = TC_INGRESS_FILTERS_DUP
get_filters.return_value = filters
self.assertRaises(exceptions.MultipleFilterIDForIPFound,
self.tc._get_filterid_for_ip,
INGRESS_QSIC_ID, FLOATING_IP_2)
def test__get_filterid_for_ip_not_found(self):
def test__get_filterid_for_ip_duplicated_without_chain(self):
self._test__get_filterid_for_ip_duplicated(
TC_INGRESS_FILTERS_DUP_WITHOUT_CHAIN)
def test__get_filterid_for_ip_duplicated_with_chain(self):
self._test__get_filterid_for_ip_duplicated(
TC_INGRESS_FILTERS_DUP_WITH_CHAIN)
def _test__get_filterid_for_ip_not_found(self, filters):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_filters') as get_filters:
get_filters.return_value = TC_EGRESS_FILTERS
get_filters.return_value = filters
self.assertRaises(exceptions.FilterIDForIPNotFound,
self.tc._get_filterid_for_ip,
INGRESS_QSIC_ID, "1.1.1.1")
def test__get_filterid_for_ip_not_found_without_chain(self):
self._test__get_filterid_for_ip_not_found(
TC_EGRESS_FILTERS_WITHOUT_CHAIN)
def test__get_filterid_for_ip_not_found_with_chain(self):
self._test__get_filterid_for_ip_not_found(TC_EGRESS_FILTERS_WITH_CHAIN)
def test__del_filter_by_id(self):
self.tc._del_filter_by_id(INGRESS_QSIC_ID, FLOATING_IP_1)
self.execute.assert_called_once_with(
@ -208,13 +263,19 @@ class TestFloatingIPTcCommandBase(base.BaseTestCase):
extra_ok_codes=None
)
def test__get_qdisc_filters(self):
def _test__get_qdisc_filters(self, filters):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_filters') as get_filters:
get_filters.return_value = TC_EGRESS_FILTERS
get_filters.return_value = filters
f_ids = self.tc._get_qdisc_filters(INGRESS_QSIC_ID)
self.assertEqual([FILETER_ID_1, FILETER_ID_2], f_ids)
def test__get_qdisc_filters_without_chain(self):
self._test__get_qdisc_filters(TC_EGRESS_FILTERS_WITHOUT_CHAIN)
def test__get_qdisc_filters_with_chain(self):
self._test__get_qdisc_filters(TC_EGRESS_FILTERS_WITH_CHAIN)
def test__get_qdisc_filters_no_output(self):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_filters') as get_filters:
@ -280,16 +341,22 @@ class TestFloatingIPTcCommand(base.BaseTestCase):
namespace=FLOATING_IP_ROUTER_NAMESPACE)
self.execute = mock.patch('neutron.agent.common.utils.execute').start()
def test_clear_all_filters(self):
def _test_clear_all_filters(self, filters):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_qdisc_id_for_filter') as get_disc:
get_disc.return_value = EGRESS_QDISC_ID
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_filters') as get_filters:
get_filters.return_value = TC_EGRESS_FILTERS
get_filters.return_value = filters
self.tc.clear_all_filters(constants.EGRESS_DIRECTION)
self.assertEqual(2, self.execute.call_count)
def test_clear_all_filters_without_chain(self):
self._test_clear_all_filters(TC_EGRESS_FILTERS_WITHOUT_CHAIN)
def test_clear_all_filters_with_chain(self):
self._test_clear_all_filters(TC_EGRESS_FILTERS_WITH_CHAIN)
def test_set_ip_rate_limit_filter_existed(self):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_qdisc_id_for_filter') as get_disc:
@ -310,7 +377,7 @@ class TestFloatingIPTcCommand(base.BaseTestCase):
EGRESS_QDISC_ID, constants.EGRESS_DIRECTION,
ip, 1, 1)
def test_set_ip_rate_limit_no_qdisc(self):
def _test_set_ip_rate_limit_no_qdisc(self, filters):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_qdisc_id_for_filter') as get_disc:
get_disc.return_value = None
@ -318,7 +385,7 @@ class TestFloatingIPTcCommand(base.BaseTestCase):
'_add_qdisc'):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_filters') as get_filters:
get_filters.return_value = TC_INGRESS_FILTERS
get_filters.return_value = filters
get_disc.return_value = INGRESS_QSIC_ID
ip = "111.111.111.111"
self.tc.set_ip_rate_limit(constants.INGRESS_DIRECTION,
@ -342,6 +409,12 @@ class TestFloatingIPTcCommand(base.BaseTestCase):
extra_ok_codes=None
)
def test_set_ip_rate_limit_no_qdisc_without_chain(self):
self._test_set_ip_rate_limit_no_qdisc(TC_INGRESS_FILTERS_WITHOUT_CHAIN)
def test_set_ip_rate_limit_no_qdisc_with_chain(self):
self._test_set_ip_rate_limit_no_qdisc(TC_INGRESS_FILTERS_WITH_CHAIN)
def test_clear_ip_rate_limit(self):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_qdisc_id_for_filter') as get_disc: