Fix ingress bw limit for OVS DPDK ports
For OVS based DPDK ports ingress bandwidth limit is now implemented using egress-policer qos type. Additionally limit values are set in other_config of QoS because there is no queue used in this case. This patch moves also helper methods used to conversion between bytes and bits and between bits and kilobits to neutron.common.utils to be able to use it also in ovs_lib module. Change-Id: I94d1e8dfb82df5c602476db8aaa884ae91fecd7f Closes-Bug: #1724729
This commit is contained in:
parent
ea4c9fae71
commit
1be8574352
|
@ -31,6 +31,8 @@ from neutron._i18n import _
|
|||
from neutron.agent.common import ip_lib
|
||||
from neutron.agent.common import utils
|
||||
from neutron.agent.ovsdb import api as ovsdb_api
|
||||
from neutron.common import constants as common_constants
|
||||
from neutron.common import utils as common_utils
|
||||
from neutron.conf.agent import ovs_conf
|
||||
from neutron.plugins.ml2.drivers.openvswitch.agent.common \
|
||||
import constants
|
||||
|
@ -732,16 +734,27 @@ class OVSBridge(BaseOVS):
|
|||
other_config=qos_other_config))
|
||||
return qos_uuid
|
||||
|
||||
def update_ingress_bw_limit_for_port(self, port_name, max_kbps,
|
||||
max_burst_kbps):
|
||||
max_bw_in_bits = str(max_kbps * 1000)
|
||||
max_burst_in_bits = str(max_burst_kbps * 1000)
|
||||
def _update_bw_limit_profile_dpdk(self, txn, port_name, qos_uuid,
|
||||
other_config):
|
||||
if qos_uuid:
|
||||
txn.add(self.ovsdb.db_set(
|
||||
'QoS', qos_uuid, ('other_config', other_config)))
|
||||
else:
|
||||
external_ids = {'id': port_name}
|
||||
qos_uuid = txn.add(
|
||||
self.ovsdb.db_create(
|
||||
'QoS', external_ids=external_ids, type='egress-policer',
|
||||
other_config=other_config))
|
||||
return qos_uuid
|
||||
|
||||
def _update_ingress_bw_limit_for_port(
|
||||
self, port_name, max_bw_in_bits, max_burst_in_bits):
|
||||
qos_other_config = {
|
||||
'max-rate': max_bw_in_bits
|
||||
'max-rate': str(max_bw_in_bits)
|
||||
}
|
||||
queue_other_config = {
|
||||
'max-rate': max_bw_in_bits,
|
||||
'burst': max_burst_in_bits,
|
||||
'max-rate': str(max_bw_in_bits),
|
||||
'burst': str(max_burst_in_bits),
|
||||
}
|
||||
qos = self.find_qos(port_name)
|
||||
queue = self.find_queue(port_name, QOS_DEFAULT_QUEUE)
|
||||
|
@ -761,6 +774,33 @@ class OVSBridge(BaseOVS):
|
|||
txn.add(self.ovsdb.db_set(
|
||||
'Port', port_name, ('qos', qos_uuid)))
|
||||
|
||||
def _update_ingress_bw_limit_for_dpdk_port(
|
||||
self, port_name, max_bw_in_bits, max_burst_in_bits):
|
||||
# cir and cbs should be set in bytes instead of bits
|
||||
qos_other_config = {
|
||||
'cir': str(max_bw_in_bits / 8),
|
||||
'cbs': str(max_burst_in_bits / 8)
|
||||
}
|
||||
qos = self.find_qos(port_name)
|
||||
qos_uuid = qos['_uuid'] if qos else None
|
||||
with self.ovsdb.transaction(check_error=True) as txn:
|
||||
qos_uuid = self._update_bw_limit_profile_dpdk(
|
||||
txn, port_name, qos_uuid, qos_other_config)
|
||||
txn.add(self.ovsdb.db_set(
|
||||
'Port', port_name, ('qos', qos_uuid)))
|
||||
|
||||
def update_ingress_bw_limit_for_port(self, port_name, max_kbps,
|
||||
max_burst_kbps):
|
||||
max_bw_in_bits = max_kbps * common_constants.SI_BASE
|
||||
max_burst_in_bits = max_burst_kbps * common_constants.SI_BASE
|
||||
port_type = self._get_port_val(port_name, "type")
|
||||
if port_type in constants.OVS_DPDK_PORT_TYPES:
|
||||
self._update_ingress_bw_limit_for_dpdk_port(
|
||||
port_name, max_bw_in_bits, max_burst_in_bits)
|
||||
else:
|
||||
self._update_ingress_bw_limit_for_port(
|
||||
port_name, max_bw_in_bits, max_burst_in_bits)
|
||||
|
||||
def get_ingress_bw_limit_for_port(self, port_name):
|
||||
max_kbps = None
|
||||
qos_max_kbps = None
|
||||
|
@ -772,17 +812,18 @@ class OVSBridge(BaseOVS):
|
|||
other_config = qos_res['other_config']
|
||||
max_bw_in_bits = other_config.get('max-rate')
|
||||
if max_bw_in_bits is not None:
|
||||
qos_max_kbps = int(max_bw_in_bits) / 1000
|
||||
qos_max_kbps = int(max_bw_in_bits) / common_constants.SI_BASE
|
||||
|
||||
queue_res = self.find_queue(port_name, QOS_DEFAULT_QUEUE)
|
||||
if queue_res:
|
||||
other_config = queue_res['other_config']
|
||||
max_bw_in_bits = other_config.get('max-rate')
|
||||
if max_bw_in_bits is not None:
|
||||
queue_max_kbps = int(max_bw_in_bits) / 1000
|
||||
queue_max_kbps = int(max_bw_in_bits) / common_constants.SI_BASE
|
||||
max_burst_in_bits = other_config.get('burst')
|
||||
if max_burst_in_bits is not None:
|
||||
max_burst_kbit = int(max_burst_in_bits) / 1000
|
||||
max_burst_kbit = (
|
||||
int(max_burst_in_bits) / common_constants.SI_BASE)
|
||||
|
||||
if qos_max_kbps == queue_max_kbps:
|
||||
max_kbps = qos_max_kbps
|
||||
|
@ -791,7 +832,24 @@ class OVSBridge(BaseOVS):
|
|||
"queue max-rate %(queue_max_kbps)s",
|
||||
{'qos_max_kbps': qos_max_kbps,
|
||||
'queue_max_kbps': queue_max_kbps})
|
||||
return max_kbps, max_burst_kbit
|
||||
|
||||
def get_ingress_bw_limit_for_dpdk_port(self, port_name):
|
||||
max_kbps = None
|
||||
max_burst_kbit = None
|
||||
res = self.find_qos(port_name)
|
||||
if res:
|
||||
other_config = res['other_config']
|
||||
max_bw_in_bytes = other_config.get("cir")
|
||||
if max_bw_in_bytes is not None:
|
||||
max_kbps = common_utils.bits_to_kilobits(
|
||||
common_utils.bytes_to_bits(int(max_bw_in_bytes)),
|
||||
common_constants.SI_BASE)
|
||||
max_burst_in_bytes = other_config.get("cbs")
|
||||
if max_burst_in_bytes is not None:
|
||||
max_burst_kbit = common_utils.bits_to_kilobits(
|
||||
common_utils.bytes_to_bits(int(max_burst_in_bytes)),
|
||||
common_constants.SI_BASE)
|
||||
return max_kbps, max_burst_kbit
|
||||
|
||||
def delete_ingress_bw_limit_for_port(self, port_name):
|
||||
|
|
|
@ -20,14 +20,13 @@ from neutron_lib.services.qos import constants as qos_consts
|
|||
|
||||
from neutron._i18n import _
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.common import constants
|
||||
from neutron.common import utils
|
||||
|
||||
|
||||
INGRESS_QDISC_ID = "ffff:"
|
||||
MAX_MTU_VALUE = 65535
|
||||
|
||||
SI_BASE = 1000
|
||||
IEC_BASE = 1024
|
||||
|
||||
LATENCY_UNIT = "ms"
|
||||
BW_LIMIT_UNIT = "kbit" # kilobits per second in tc's notation
|
||||
BURST_UNIT = "kbit" # kilobits in tc's notation
|
||||
|
@ -66,10 +65,10 @@ def convert_to_kilobits(value, base):
|
|||
if value.isdigit():
|
||||
value = int(value)
|
||||
if input_in_bits:
|
||||
return bits_to_kilobits(value, base)
|
||||
return utils.bits_to_kilobits(value, base)
|
||||
else:
|
||||
bits_value = bytes_to_bits(value)
|
||||
return bits_to_kilobits(bits_value, base)
|
||||
bits_value = utils.bytes_to_bits(value)
|
||||
return utils.bits_to_kilobits(bits_value, base)
|
||||
unit = value[-1:]
|
||||
if unit not in UNITS.keys():
|
||||
raise InvalidUnit(unit=unit)
|
||||
|
@ -77,17 +76,8 @@ def convert_to_kilobits(value, base):
|
|||
if input_in_bits:
|
||||
bits_value = val * (base ** UNITS[unit])
|
||||
else:
|
||||
bits_value = bytes_to_bits(val * (base ** UNITS[unit]))
|
||||
return bits_to_kilobits(bits_value, base)
|
||||
|
||||
|
||||
def bytes_to_bits(value):
|
||||
return value * 8
|
||||
|
||||
|
||||
def bits_to_kilobits(value, base):
|
||||
#NOTE(slaweq): round up that even 1 bit will give 1 kbit as a result
|
||||
return int((value + (base - 1)) / base)
|
||||
bits_value = utils.bytes_to_bits(val * (base ** UNITS[unit]))
|
||||
return utils.bits_to_kilobits(bits_value, base)
|
||||
|
||||
|
||||
class TcCommand(ip_lib.IPDevice):
|
||||
|
@ -124,10 +114,11 @@ class TcCommand(ip_lib.IPDevice):
|
|||
if m:
|
||||
#NOTE(slaweq): because tc is giving bw limit in SI units
|
||||
# we need to calculate it as 1000bit = 1kbit:
|
||||
bw_limit = convert_to_kilobits(m.group(1), SI_BASE)
|
||||
bw_limit = convert_to_kilobits(m.group(1), constants.SI_BASE)
|
||||
#NOTE(slaweq): because tc is giving burst limit in IEC units
|
||||
# we need to calculate it as 1024bit = 1kbit:
|
||||
burst_limit = convert_to_kilobits(m.group(2), IEC_BASE)
|
||||
burst_limit = convert_to_kilobits(
|
||||
m.group(2), constants.IEC_BASE)
|
||||
return bw_limit, burst_limit
|
||||
return None, None
|
||||
|
||||
|
@ -144,10 +135,10 @@ class TcCommand(ip_lib.IPDevice):
|
|||
return None, None
|
||||
#NOTE(slaweq): because tc is giving bw limit in SI units
|
||||
# we need to calculate it as 1000bit = 1kbit:
|
||||
bw_limit = convert_to_kilobits(m.group(2), SI_BASE)
|
||||
bw_limit = convert_to_kilobits(m.group(2), constants.SI_BASE)
|
||||
#NOTE(slaweq): because tc is giving burst limit in IEC units
|
||||
# we need to calculate it as 1024bit = 1kbit:
|
||||
burst_limit = convert_to_kilobits(m.group(3), IEC_BASE)
|
||||
burst_limit = convert_to_kilobits(m.group(3), constants.IEC_BASE)
|
||||
return bw_limit, burst_limit
|
||||
|
||||
def set_filters_bw_limit(self, bw_limit, burst_limit):
|
||||
|
|
|
@ -222,3 +222,7 @@ FLOATING_IP_HOST_NEEDS_BINDING = "FLOATING_IP_HOST_NEEDS_BINDING"
|
|||
# Possible types of values (e.g. in QoS rule types)
|
||||
VALUES_TYPE_CHOICES = "choices"
|
||||
VALUES_TYPE_RANGE = "range"
|
||||
|
||||
# Units base
|
||||
SI_BASE = 1000
|
||||
IEC_BASE = 1024
|
||||
|
|
|
@ -801,3 +801,12 @@ def resolve_ref(ref):
|
|||
if isinstance(ref, weakref.ref):
|
||||
ref = ref()
|
||||
return ref
|
||||
|
||||
|
||||
def bytes_to_bits(value):
|
||||
return value * 8
|
||||
|
||||
|
||||
def bits_to_kilobits(value, base):
|
||||
#NOTE(slaweq): round up that even 1 bit will give 1 kbit as a result
|
||||
return int((value + (base - 1)) / base)
|
||||
|
|
|
@ -137,6 +137,8 @@ OVS_DATAPATH_NETDEV = 'netdev'
|
|||
OVS_DPDK_VHOST_USER = 'dpdkvhostuser'
|
||||
OVS_DPDK_VHOST_USER_CLIENT = 'dpdkvhostuserclient'
|
||||
|
||||
OVS_DPDK_PORT_TYPES = [OVS_DPDK_VHOST_USER, OVS_DPDK_VHOST_USER_CLIENT]
|
||||
|
||||
# default ovs vhost-user socket location
|
||||
VHOST_USER_SOCKET_DIR = '/var/run/openvswitch'
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@ from neutron.agent.common import ovs_lib
|
|||
from neutron.agent.linux import ip_lib
|
||||
from neutron.agent.ovsdb.native import idlutils
|
||||
from neutron.common import utils
|
||||
from neutron.plugins.ml2.drivers.openvswitch.agent.common import (
|
||||
constants as agent_const)
|
||||
from neutron.tests.common.exclusive_resources import port
|
||||
from neutron.tests.common import net_helpers
|
||||
from neutron.tests.functional.agent.linux import base
|
||||
|
@ -392,6 +394,27 @@ class OVSBridgeTestCase(OVSBridgeTestBase):
|
|||
self.assertIsNone(max_rate)
|
||||
self.assertIsNone(burst)
|
||||
|
||||
def test_ingress_bw_limit_dpdk_port(self):
|
||||
port_name, _ = self.create_ovs_port(
|
||||
('type', agent_const.OVS_DPDK_VHOST_USER))
|
||||
self.br.update_ingress_bw_limit_for_port(port_name, 700, 70)
|
||||
max_rate, burst = self.br.get_ingress_bw_limit_for_dpdk_port(
|
||||
port_name)
|
||||
self.assertEqual(700, max_rate)
|
||||
self.assertEqual(70, burst)
|
||||
|
||||
self.br.update_ingress_bw_limit_for_port(port_name, 750, 100)
|
||||
max_rate, burst = self.br.get_ingress_bw_limit_for_dpdk_port(
|
||||
port_name)
|
||||
self.assertEqual(750, max_rate)
|
||||
self.assertEqual(100, burst)
|
||||
|
||||
self.br.delete_ingress_bw_limit_for_port(port_name)
|
||||
max_rate, burst = self.br.get_ingress_bw_limit_for_dpdk_port(
|
||||
port_name)
|
||||
self.assertIsNone(max_rate)
|
||||
self.assertIsNone(burst)
|
||||
|
||||
def test_db_create_references(self):
|
||||
with self.ovs.ovsdb.transaction(check_error=True) as txn:
|
||||
queue = txn.add(self.ovs.ovsdb.db_create("Queue",
|
||||
|
|
|
@ -17,6 +17,8 @@ import mock
|
|||
from neutron_lib.services.qos import constants as qos_consts
|
||||
|
||||
from neutron.agent.linux import tc_lib
|
||||
from neutron.common import constants
|
||||
from neutron.common import utils
|
||||
from neutron.tests import base
|
||||
|
||||
DEVICE_NAME = "tap_device"
|
||||
|
@ -58,7 +60,7 @@ class BaseUnitConversionTest(object):
|
|||
|
||||
def test_convert_to_kilobits_bits_value(self):
|
||||
value = "1000bit"
|
||||
expected_value = tc_lib.bits_to_kilobits(1000, self.base_unit)
|
||||
expected_value = utils.bits_to_kilobits(1000, self.base_unit)
|
||||
self.assertEqual(
|
||||
expected_value,
|
||||
tc_lib.convert_to_kilobits(value, self.base_unit)
|
||||
|
@ -66,7 +68,7 @@ class BaseUnitConversionTest(object):
|
|||
|
||||
def test_convert_to_kilobits_megabytes_value(self):
|
||||
value = "1m"
|
||||
expected_value = tc_lib.bits_to_kilobits(
|
||||
expected_value = utils.bits_to_kilobits(
|
||||
self.base_unit ** 2 * 8, self.base_unit)
|
||||
self.assertEqual(
|
||||
expected_value,
|
||||
|
@ -75,7 +77,7 @@ class BaseUnitConversionTest(object):
|
|||
|
||||
def test_convert_to_kilobits_megabits_value(self):
|
||||
value = "1mbit"
|
||||
expected_value = tc_lib.bits_to_kilobits(
|
||||
expected_value = utils.bits_to_kilobits(
|
||||
self.base_unit ** 2, self.base_unit)
|
||||
self.assertEqual(
|
||||
expected_value,
|
||||
|
@ -89,53 +91,15 @@ class BaseUnitConversionTest(object):
|
|||
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)
|
||||
)
|
||||
base_unit = constants.SI_BASE
|
||||
|
||||
|
||||
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)
|
||||
)
|
||||
base_unit = constants.IEC_BASE
|
||||
|
||||
|
||||
class TestTcCommand(base.BaseTestCase):
|
||||
|
|
|
@ -28,6 +28,7 @@ import six
|
|||
import testscenarios
|
||||
import testtools
|
||||
|
||||
from neutron.common import constants as common_constants
|
||||
from neutron.common import exceptions as n_exc
|
||||
from neutron.common import utils
|
||||
from neutron.plugins.common import utils as plugin_utils
|
||||
|
@ -775,3 +776,54 @@ class TestThrottler(base.BaseTestCase):
|
|||
|
||||
obj = Klass()
|
||||
obj.method()
|
||||
|
||||
|
||||
class BaseUnitConversionTest(object):
|
||||
|
||||
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, utils.bytes_to_bits(input_bytes)
|
||||
)
|
||||
|
||||
|
||||
class TestSIUnitConversions(BaseUnitConversionTest, base.BaseTestCase):
|
||||
|
||||
base_unit = common_constants.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,
|
||||
utils.bits_to_kilobits(input_bits, self.base_unit)
|
||||
)
|
||||
|
||||
|
||||
class TestIECUnitConversions(BaseUnitConversionTest, base.BaseTestCase):
|
||||
|
||||
base_unit = common_constants.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,
|
||||
utils.bits_to_kilobits(input_bits, self.base_unit)
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue