You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
305 lines
11 KiB
305 lines
11 KiB
# Copyright 2016 OVH SAS
|
|
# All Rights Reserved.
|
|
#
|
|
# 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.agent.linux import tc_lib
|
|
from neutron.services.qos import qos_consts
|
|
from neutron.tests import base
|
|
|
|
DEVICE_NAME = "tap_device"
|
|
KERNEL_HZ_VALUE = 1000
|
|
BW_LIMIT = 2000 # [kbps]
|
|
BURST = 100 # [kbit]
|
|
LATENCY = 50 # [ms]
|
|
|
|
TC_QDISC_OUTPUT = (
|
|
'qdisc tbf 8011: root refcnt 2 rate %(bw)skbit burst %(burst)skbit '
|
|
'lat 50.0ms \n') % {'bw': BW_LIMIT, 'burst': BURST}
|
|
|
|
TC_FILTERS_OUTPUT = (
|
|
'filter protocol all pref 49152 u32 \nfilter protocol all pref '
|
|
'49152 u32 fh 800: ht divisor 1 \nfilter protocol all pref 49152 u32 fh '
|
|
'800::800 order 2048 key ht 800 \n match 00000000/00000000 at 0\n '
|
|
'police 0x1e rate %(bw)skbit burst %(burst)skbit mtu 2Kb action \n'
|
|
'drop overhead 0b \n ref 1 bind 1'
|
|
) % {'bw': BW_LIMIT, 'burst': BURST}
|
|
|
|
|
|
class BaseUnitConversionTest(object):
|
|
|
|
def test_convert_to_kilobits_bare_value(self):
|
|
value = "1000"
|
|
expected_value = 8 # kbit
|
|
self.assertEqual(
|
|
expected_value,
|
|
tc_lib.convert_to_kilobits(value, self.base_unit)
|
|
)
|
|
|
|
def test_convert_to_kilobits_bytes_value(self):
|
|
value = "1000b"
|
|
expected_value = 8 # kbit
|
|
self.assertEqual(
|
|
expected_value,
|
|
tc_lib.convert_to_kilobits(value, self.base_unit)
|
|
)
|
|
|
|
def test_convert_to_kilobits_bits_value(self):
|
|
value = "1000bit"
|
|
expected_value = tc_lib.bits_to_kilobits(1000, self.base_unit)
|
|
self.assertEqual(
|
|
expected_value,
|
|
tc_lib.convert_to_kilobits(value, self.base_unit)
|
|
)
|
|
|
|
def test_convert_to_kilobits_megabytes_value(self):
|
|
value = "1m"
|
|
expected_value = tc_lib.bits_to_kilobits(
|
|
self.base_unit ** 2 * 8, self.base_unit)
|
|
self.assertEqual(
|
|
expected_value,
|
|
tc_lib.convert_to_kilobits(value, self.base_unit)
|
|
)
|
|
|
|
def test_convert_to_kilobits_megabits_value(self):
|
|
value = "1mbit"
|
|
expected_value = tc_lib.bits_to_kilobits(
|
|
self.base_unit ** 2, self.base_unit)
|
|
self.assertEqual(
|
|
expected_value,
|
|
tc_lib.convert_to_kilobits(value, self.base_unit)
|
|
)
|
|
|
|
def test_convert_to_bytes_wrong_unit(self):
|
|
value = "1Zbit"
|
|
self.assertRaises(
|
|
tc_lib.InvalidUnit,
|
|
tc_lib.convert_to_kilobits, value, self.base_unit
|
|
)
|
|
|
|
def test_bytes_to_bits(self):
|
|
test_values = [
|
|
(0, 0), # 0 bytes should be 0 bits
|
|
(1, 8) # 1 byte should be 8 bits
|
|
]
|
|
for input_bytes, expected_bits in test_values:
|
|
self.assertEqual(
|
|
expected_bits, tc_lib.bytes_to_bits(input_bytes)
|
|
)
|
|
|
|
|
|
class TestSIUnitConversions(BaseUnitConversionTest, base.BaseTestCase):
|
|
|
|
base_unit = tc_lib.SI_BASE
|
|
|
|
def test_bits_to_kilobits(self):
|
|
test_values = [
|
|
(0, 0), # 0 bites should be 0 kilobites
|
|
(1, 1), # 1 bit should be 1 kilobit
|
|
(999, 1), # 999 bits should be 1 kilobit
|
|
(1000, 1), # 1000 bits should be 1 kilobit
|
|
(1001, 2) # 1001 bits should be 2 kilobits
|
|
]
|
|
for input_bits, expected_kilobits in test_values:
|
|
self.assertEqual(
|
|
expected_kilobits,
|
|
tc_lib.bits_to_kilobits(input_bits, self.base_unit)
|
|
)
|
|
|
|
|
|
class TestIECUnitConversions(BaseUnitConversionTest, base.BaseTestCase):
|
|
|
|
base_unit = tc_lib.IEC_BASE
|
|
|
|
def test_bits_to_kilobits(self):
|
|
test_values = [
|
|
(0, 0), # 0 bites should be 0 kilobites
|
|
(1, 1), # 1 bit should be 1 kilobit
|
|
(1023, 1), # 1023 bits should be 1 kilobit
|
|
(1024, 1), # 1024 bits should be 1 kilobit
|
|
(1025, 2) # 1025 bits should be 2 kilobits
|
|
]
|
|
for input_bits, expected_kilobits in test_values:
|
|
self.assertEqual(
|
|
expected_kilobits,
|
|
tc_lib.bits_to_kilobits(input_bits, self.base_unit)
|
|
)
|
|
|
|
|
|
class TestTcCommand(base.BaseTestCase):
|
|
def setUp(self):
|
|
super(TestTcCommand, self).setUp()
|
|
self.tc = tc_lib.TcCommand(DEVICE_NAME, KERNEL_HZ_VALUE)
|
|
self.bw_limit = "%s%s" % (BW_LIMIT, tc_lib.BW_LIMIT_UNIT)
|
|
self.burst = "%s%s" % (BURST, tc_lib.BURST_UNIT)
|
|
self.latency = "%s%s" % (LATENCY, tc_lib.LATENCY_UNIT)
|
|
self.execute = mock.patch('neutron.agent.common.utils.execute').start()
|
|
|
|
def test_check_kernel_hz_lower_then_zero(self):
|
|
self.assertRaises(
|
|
tc_lib.InvalidKernelHzValue,
|
|
tc_lib.TcCommand, DEVICE_NAME, 0
|
|
)
|
|
self.assertRaises(
|
|
tc_lib.InvalidKernelHzValue,
|
|
tc_lib.TcCommand, DEVICE_NAME, -100
|
|
)
|
|
|
|
def test_get_filters_bw_limits(self):
|
|
self.execute.return_value = TC_FILTERS_OUTPUT
|
|
bw_limit, burst_limit = self.tc.get_filters_bw_limits()
|
|
self.assertEqual(BW_LIMIT, bw_limit)
|
|
self.assertEqual(BURST, burst_limit)
|
|
|
|
def test_get_filters_bw_limits_when_output_not_match(self):
|
|
output = (
|
|
"Some different "
|
|
"output from command:"
|
|
"tc filters show dev XXX parent ffff:"
|
|
)
|
|
self.execute.return_value = output
|
|
bw_limit, burst_limit = self.tc.get_filters_bw_limits()
|
|
self.assertIsNone(bw_limit)
|
|
self.assertIsNone(burst_limit)
|
|
|
|
def test_get_filters_bw_limits_when_wrong_units(self):
|
|
output = TC_FILTERS_OUTPUT.replace("kbit", "Xbit")
|
|
self.execute.return_value = output
|
|
self.assertRaises(tc_lib.InvalidUnit, self.tc.get_filters_bw_limits)
|
|
|
|
def test_get_tbf_bw_limits(self):
|
|
self.execute.return_value = TC_QDISC_OUTPUT
|
|
bw_limit, burst_limit = self.tc.get_tbf_bw_limits()
|
|
self.assertEqual(BW_LIMIT, bw_limit)
|
|
self.assertEqual(BURST, burst_limit)
|
|
|
|
def test_get_tbf_bw_limits_when_wrong_qdisc(self):
|
|
output = TC_QDISC_OUTPUT.replace("tbf", "different_qdisc")
|
|
self.execute.return_value = output
|
|
bw_limit, burst_limit = self.tc.get_tbf_bw_limits()
|
|
self.assertIsNone(bw_limit)
|
|
self.assertIsNone(burst_limit)
|
|
|
|
def test_get_tbf_bw_limits_when_wrong_units(self):
|
|
output = TC_QDISC_OUTPUT.replace("kbit", "Xbit")
|
|
self.execute.return_value = output
|
|
self.assertRaises(tc_lib.InvalidUnit, self.tc.get_tbf_bw_limits)
|
|
|
|
def test_set_tbf_bw_limit(self):
|
|
self.tc.set_tbf_bw_limit(BW_LIMIT, BURST, LATENCY)
|
|
self.execute.assert_called_once_with(
|
|
["tc", "qdisc", "replace", "dev", DEVICE_NAME,
|
|
"root", "tbf", "rate", self.bw_limit,
|
|
"latency", self.latency,
|
|
"burst", self.burst],
|
|
run_as_root=True,
|
|
check_exit_code=True,
|
|
log_fail_as_error=True,
|
|
extra_ok_codes=None
|
|
)
|
|
|
|
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', 'qdisc', 'add', 'dev', DEVICE_NAME, "ingress",
|
|
"handle", tc_lib.INGRESS_QDISC_ID],
|
|
run_as_root=True,
|
|
check_exit_code=True,
|
|
log_fail_as_error=True,
|
|
extra_ok_codes=None
|
|
),
|
|
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
|
|
)]
|
|
)
|
|
|
|
def test_update_tbf_bw_limit(self):
|
|
self.tc.update_tbf_bw_limit(BW_LIMIT, BURST, LATENCY)
|
|
self.execute.assert_called_once_with(
|
|
["tc", "qdisc", "replace", "dev", DEVICE_NAME,
|
|
"root", "tbf", "rate", self.bw_limit,
|
|
"latency", self.latency,
|
|
"burst", self.burst],
|
|
run_as_root=True,
|
|
check_exit_code=True,
|
|
log_fail_as_error=True,
|
|
extra_ok_codes=None
|
|
)
|
|
|
|
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]
|
|
)
|
|
|
|
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]
|
|
)
|
|
|
|
def test_get_ingress_qdisc_burst_value_burst_not_none(self):
|
|
self.assertEqual(
|
|
BURST, self.tc.get_ingress_qdisc_burst_value(BW_LIMIT, BURST)
|
|
)
|
|
|
|
def test_get_ingress_qdisc_burst_no_burst_value_given(self):
|
|
expected_burst = BW_LIMIT * qos_consts.DEFAULT_BURST_RATE
|
|
self.assertEqual(
|
|
expected_burst,
|
|
self.tc.get_ingress_qdisc_burst_value(BW_LIMIT, None)
|
|
)
|
|
|
|
def test_get_ingress_qdisc_burst_burst_value_zero(self):
|
|
expected_burst = BW_LIMIT * qos_consts.DEFAULT_BURST_RATE
|
|
self.assertEqual(
|
|
expected_burst,
|
|
self.tc.get_ingress_qdisc_burst_value(BW_LIMIT, 0)
|
|
)
|
|
|
|
def test__get_tbf_burst_value_when_burst_bigger_then_minimal(self):
|
|
result = self.tc._get_tbf_burst_value(BW_LIMIT, BURST)
|
|
self.assertEqual(BURST, result)
|
|
|
|
def test__get_tbf_burst_value_when_burst_smaller_then_minimal(self):
|
|
result = self.tc._get_tbf_burst_value(BW_LIMIT, 0)
|
|
self.assertEqual(2, result)
|