Files
neutron/neutron/tests/unit/agent/linux/test_l3_tc_lib.py
LIU Yulong 5ddb2a4762 Make L3 IP tc filter rate limit more accurate
Currently the L3 agent qos extension does not set the mtu for
tc filter rules, the default value is 2kb. Tc filter rules use
such mtu will result in an inaccurate bandwidth.

So in order to improve the precision, we set the mtu to 64kb.
For the test results, you can read the bug description.

For more information you can read the linux tc man page:
[1] https://linux.die.net/man/8/tc-tbf
[2] http://man7.org/linux/man-pages/man8/tc-police.8.html

Closes-Bug: #1777598
Change-Id: I7a167ec6139ccc55988b34f82080a182116d02a3
2018-06-20 02:21:28 +00:00

397 lines
18 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from neutron_lib import constants
from neutron.agent.linux import l3_tc_lib as tc_lib
from neutron.common import exceptions
from neutron.tests import base
FLOATING_IP_DEVICE_NAME = "qg-device_rfp"
FLOATING_IP_ROUTER_NAMESPACE = "qrouter-namespace_snat-namespace"
FLOATING_IP_1 = "172.16.5.146"
FLOATING_IP_2 = "172.16.10.105"
FILETER_ID_1 = "800::800"
FILETER_ID_2 = "800::801"
TC_INGRESS_FILTERS = (
'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 '
'ht 800 bkt 0 '
'flowid :1 (rule hit 0 success 0)\n'
' match IP dst %(fip1)s/32 (success 0 ) \n'
' police 0x3 rate 3000Kbit burst 3Mb mtu 64Kb action drop overhead 0b \n'
'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 '
'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') % {
"filter_id1": FILETER_ID_1,
"fip1": FLOATING_IP_1,
"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 '
'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') % {
"filter_id2": FILETER_ID_2,
"fip2": FLOATING_IP_2}
TC_EGRESS_FILTERS = (
'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 '
'ht 800 bkt 0 '
'flowid :1 (rule hit 0 success 0)\n'
' match IP src %(fip1)s/32 (success 0 ) \n'
' police 0x4 rate 3000Kbit burst 3Mb mtu 64Kb action drop overhead 0b \n'
'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 '
'ht 800 bkt 0 '
'flowid :1 (rule hit 0 success 0)\n'
' match IP src %(fip2)s/32 (success 0 ) \n'
' police 0x1c 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') % {
"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}
INGRESS_QSIC_ID = "ffff:"
EGRESS_QDISC_ID = "1:"
QDISC_IDS = {constants.INGRESS_DIRECTION: INGRESS_QSIC_ID,
constants.EGRESS_DIRECTION: EGRESS_QDISC_ID}
TC_QDISCS = (
'qdisc htb %(egress)s root refcnt 2 r2q 10 default 0 '
'direct_packets_stat 6\n'
'qdisc ingress %(ingress)s parent ffff:fff1 ----------------\n') % {
"egress": EGRESS_QDISC_ID,
"ingress": INGRESS_QSIC_ID}
class TestFloatingIPTcCommandBase(base.BaseTestCase):
def setUp(self):
super(TestFloatingIPTcCommandBase, self).setUp()
self.tc = tc_lib.FloatingIPTcCommandBase(
FLOATING_IP_DEVICE_NAME,
namespace=FLOATING_IP_ROUTER_NAMESPACE)
self.execute = mock.patch('neutron.agent.common.utils.execute').start()
def test__get_qdiscs(self):
self.tc._get_qdiscs()
self.execute.assert_called_once_with(
['ip', 'netns', 'exec', FLOATING_IP_ROUTER_NAMESPACE,
'tc', 'qdisc', 'show', 'dev', FLOATING_IP_DEVICE_NAME],
run_as_root=True,
check_exit_code=True,
log_fail_as_error=True,
extra_ok_codes=None
)
def test__get_qdisc_id_for_filter(self):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_qdiscs') as get_qdiscs:
get_qdiscs.return_value = TC_QDISCS
q1 = self.tc._get_qdisc_id_for_filter(constants.INGRESS_DIRECTION)
self.assertEqual(INGRESS_QSIC_ID, q1)
q2 = self.tc._get_qdisc_id_for_filter(constants.EGRESS_DIRECTION)
self.assertEqual(EGRESS_QDISC_ID, q2)
def test__add_qdisc(self):
self.tc._add_qdisc(constants.INGRESS_DIRECTION)
self.execute.assert_called_with(
['ip', 'netns', 'exec', FLOATING_IP_ROUTER_NAMESPACE,
'tc', 'qdisc', 'add', 'dev', FLOATING_IP_DEVICE_NAME, 'ingress'],
run_as_root=True,
check_exit_code=True,
log_fail_as_error=True,
extra_ok_codes=None
)
self.tc._add_qdisc(constants.EGRESS_DIRECTION)
self.execute.assert_called_with(
['ip', 'netns', 'exec', FLOATING_IP_ROUTER_NAMESPACE,
'tc', 'qdisc', 'add', 'dev',
FLOATING_IP_DEVICE_NAME] + ['root', 'handle', '1:', 'htb'],
run_as_root=True,
check_exit_code=True,
log_fail_as_error=True,
extra_ok_codes=None
)
def test__get_filters(self):
self.tc._get_filters(INGRESS_QSIC_ID)
self.execute.assert_called_with(
['ip', 'netns', 'exec', FLOATING_IP_ROUTER_NAMESPACE,
'tc', '-p', '-s', '-d', 'filter', 'show', 'dev',
FLOATING_IP_DEVICE_NAME,
'parent', INGRESS_QSIC_ID, 'prio', 1],
run_as_root=True,
check_exit_code=True,
log_fail_as_error=True,
extra_ok_codes=None
)
def test__get_filterid_for_ip(self):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_filters') as get_filters:
get_filters.return_value = TC_EGRESS_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_no_output(self):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_filters') as get_filters:
get_filters.return_value = ""
self.assertRaises(exceptions.FilterIDForIPNotFound,
self.tc._get_filterid_for_ip,
INGRESS_QSIC_ID, FLOATING_IP_1)
def test__get_filterid_for_ip_duplicated(self):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_filters') as get_filters:
get_filters.return_value = TC_INGRESS_FILTERS_DUP
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):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_filters') as get_filters:
get_filters.return_value = TC_EGRESS_FILTERS
self.assertRaises(exceptions.FilterIDForIPNotFound,
self.tc._get_filterid_for_ip,
INGRESS_QSIC_ID, "1.1.1.1")
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(
['ip', 'netns', 'exec', FLOATING_IP_ROUTER_NAMESPACE,
'tc', 'filter', 'del', 'dev', FLOATING_IP_DEVICE_NAME,
'parent', INGRESS_QSIC_ID,
'prio', 1, 'handle', FLOATING_IP_1, 'u32'],
run_as_root=True,
check_exit_code=True,
log_fail_as_error=True,
extra_ok_codes=None
)
def test__get_qdisc_filters(self):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_filters') as get_filters:
get_filters.return_value = TC_EGRESS_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_no_output(self):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_filters') as get_filters:
get_filters.return_value = ""
f_ids = self.tc._get_qdisc_filters(INGRESS_QSIC_ID)
self.assertEqual(0, len(f_ids))
def test__add_filter(self):
protocol = ['protocol', 'ip']
prio = ['prio', 1]
match = ['u32', 'match', 'ip', 'dst', FLOATING_IP_1]
police = ['police', 'rate', '1kbit', 'burst', '1kbit',
'mtu', '64kb', 'drop', 'flowid', ':1']
args = protocol + prio + match + police
cmd = ['tc', 'filter', 'add', 'dev', FLOATING_IP_DEVICE_NAME,
'parent', INGRESS_QSIC_ID] + args
self.tc._add_filter(INGRESS_QSIC_ID,
constants.INGRESS_DIRECTION,
FLOATING_IP_1, 1, 1)
self.execute.assert_called_once_with(
['ip', 'netns', 'exec', FLOATING_IP_ROUTER_NAMESPACE] + cmd,
run_as_root=True,
check_exit_code=True,
log_fail_as_error=True,
extra_ok_codes=None
)
def test__get_or_create_qdisc(self):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_qdisc_id_for_filter') as get_disc1:
get_disc1.return_value = None
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_add_qdisc'):
with mock.patch.object(
tc_lib.FloatingIPTcCommandBase,
'_get_qdisc_id_for_filter') as get_disc2:
get_disc2.return_value = INGRESS_QSIC_ID
qdisc_id = self.tc._get_or_create_qdisc(
constants.INGRESS_DIRECTION)
self.assertEqual(INGRESS_QSIC_ID, qdisc_id)
def test__get_or_create_qdisc_failed(self):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_qdisc_id_for_filter') as get_disc1:
get_disc1.return_value = None
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_add_qdisc'):
with mock.patch.object(
tc_lib.FloatingIPTcCommandBase,
'_get_qdisc_id_for_filter') as get_disc2:
get_disc2.return_value = None
self.assertRaises(exceptions.FailedToAddQdiscToDevice,
self.tc._get_or_create_qdisc,
constants.INGRESS_DIRECTION)
class TestFloatingIPTcCommand(base.BaseTestCase):
def setUp(self):
super(TestFloatingIPTcCommand, self).setUp()
self.tc = tc_lib.FloatingIPTcCommand(
FLOATING_IP_DEVICE_NAME,
namespace=FLOATING_IP_ROUTER_NAMESPACE)
self.execute = mock.patch('neutron.agent.common.utils.execute').start()
def test_clear_all_filters(self):
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
self.tc.clear_all_filters(constants.EGRESS_DIRECTION)
self.assertEqual(2, self.execute.call_count)
def test_set_ip_rate_limit_filter_existed(self):
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_filterid_for_ip') as get_filter:
get_filter.return_value = FILETER_ID_1
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_del_filter_by_id') as del_filter:
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_add_filter') as add_filter:
ip = "111.111.111.111"
self.tc.set_ip_rate_limit(constants.EGRESS_DIRECTION,
ip, 1, 1)
del_filter.assert_called_once_with(
EGRESS_QDISC_ID, FILETER_ID_1)
add_filter.assert_called_once_with(
EGRESS_QDISC_ID, constants.EGRESS_DIRECTION,
ip, 1, 1)
def test_set_ip_rate_limit_no_qdisc(self):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_qdisc_id_for_filter') as get_disc:
get_disc.return_value = None
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_add_qdisc'):
with mock.patch.object(tc_lib.FloatingIPTcCommandBase,
'_get_filters') as get_filters:
get_filters.return_value = TC_INGRESS_FILTERS
get_disc.return_value = INGRESS_QSIC_ID
ip = "111.111.111.111"
self.tc.set_ip_rate_limit(constants.INGRESS_DIRECTION,
ip, 1, 1)
protocol = ['protocol', 'ip']
prio = ['prio', 1]
_match = 'dst'
match = ['u32', 'match', 'ip', _match, ip]
police = ['police', 'rate', '1kbit', 'burst', '1kbit',
'mtu', '64kb', 'drop', 'flowid', ':1']
args = protocol + prio + match + police
self.execute.assert_called_once_with(
['ip', 'netns', 'exec', FLOATING_IP_ROUTER_NAMESPACE,
'tc', 'filter', 'add', 'dev', FLOATING_IP_DEVICE_NAME,
'parent', INGRESS_QSIC_ID] + args,
run_as_root=True,
check_exit_code=True,
log_fail_as_error=True,
extra_ok_codes=None
)
def test_clear_ip_rate_limit(self):
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_filterid_for_ip') as get_filter_id:
get_filter_id.return_value = FILETER_ID_1
self.tc.clear_ip_rate_limit(constants.EGRESS_DIRECTION,
FLOATING_IP_1)
self.execute.assert_called_once_with(
['ip', 'netns', 'exec', FLOATING_IP_ROUTER_NAMESPACE,
'tc', 'filter', 'del', 'dev', FLOATING_IP_DEVICE_NAME,
'parent', EGRESS_QDISC_ID,
'prio', 1, 'handle', FILETER_ID_1, 'u32'],
run_as_root=True,
check_exit_code=True,
log_fail_as_error=True,
extra_ok_codes=None
)
def test_get_filter_id_for_ip(self):
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_filterid_for_ip') as get_filter_id:
self.tc.get_filter_id_for_ip(constants.EGRESS_DIRECTION,
'8.8.8.8')
get_filter_id.assert_called_once_with(EGRESS_QDISC_ID,
'8.8.8.8')
def test_get_existing_filter_ids(self):
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_qdisc_filters') as get_filter_ids:
self.tc.get_existing_filter_ids(constants.EGRESS_DIRECTION)
get_filter_ids.assert_called_once_with(EGRESS_QDISC_ID)
def test_delete_filter_ids(self):
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,
'_del_filter_by_id') as del_filter_id:
self.tc.delete_filter_ids(constants.EGRESS_DIRECTION,
[FILETER_ID_1, FILETER_ID_2])
del_filter_id.assert_has_calls(
[mock.call(EGRESS_QDISC_ID, FILETER_ID_1),
mock.call(EGRESS_QDISC_ID, FILETER_ID_2)])