[SR-IOV] Fix QoS extension to set min/max values
"ip link" commands allow to define VF rates independently. That means, first "rate" (max BW) can be set and in a second command "min" (min BW) (check LP bug description). However Pyroute2 command to set the VF rates requires to set both. If one value is missing ("min_tx_rate", "max_tx_rate"), the library sets this value to 0; in other words, the value is deleted. The Pyroute2 structures are built depending on the parameter names. In this case, {'vf': {'rate': ...}} will create a "vf_rate" [1] nla structure, that requires "min_tx_rate" and "max_tx_rate". This is part of the full structure passed to the "iproute" library [2]. This is an example of code that only sets the "max_tx_rate" for the 15th VF of "enp196s0f0": $ from neutron.plugins.ml2.drivers.mech_sriov.agent import pci_lib pci = pci_lib.PciDeviceIPWrapper("enp196s0f0") pci.set_vf_rate(15, {'max_tx_rate': 10}) The "msg" [3] (structure passed to "iproute" library) is this: https://paste.opendev.org/show/b2FZBOebGOCHMrYhPr6X/. The "min_tx_rate" is set to the default value 0. This patch reads first the existing rates ("min_tx_rate", "max_tx_rate") and populates the command parameters accordingly. [1]a9564dff8e/pyroute2.core/pr2modules/netlink/rtnl/ifinfmsg/__init__.py (L712-L717)
[2]c8d9d92544/ip/ipaddress.c (L454-L470)
[3]a9564dff8e/pyroute2.core/pr2modules/iproute/linux.py (L1499)
Closes-Bug: #1962844 Change-Id: Ibbb6d938355440c42850812e368224b76b1fce19
This commit is contained in:
parent
b072cbf05f
commit
cdff281f64
|
@ -33,6 +33,8 @@ IP_LINK_CAPABILITY_STATE = 'state'
|
|||
IP_LINK_CAPABILITY_VLAN = 'vlan'
|
||||
IP_LINK_CAPABILITY_RATE = 'max_tx_rate'
|
||||
IP_LINK_CAPABILITY_MIN_TX_RATE = 'min_tx_rate'
|
||||
IP_LINK_CAPABILITY_RATES = (IP_LINK_CAPABILITY_RATE,
|
||||
IP_LINK_CAPABILITY_MIN_TX_RATE)
|
||||
IP_LINK_CAPABILITY_SPOOFCHK = 'spoofchk'
|
||||
IP_LINK_SUB_CAPABILITY_QOS = 'qos'
|
||||
|
||||
|
@ -172,6 +174,9 @@ class EmbSwitch(object):
|
|||
if pci_slot not in exclude_devices:
|
||||
self.pci_slot_map[pci_slot] = vf_index
|
||||
|
||||
def _get_vfs(self):
|
||||
return self.pci_dev_wrapper.device(self.dev_name).link.get_vfs()
|
||||
|
||||
def get_pci_slot_list(self):
|
||||
"""Get list of VF addresses."""
|
||||
return self.pci_slot_map.keys()
|
||||
|
@ -208,40 +213,45 @@ class EmbSwitch(object):
|
|||
return self.pci_dev_wrapper.set_vf_state(vf_index, state,
|
||||
auto=propagate_uplink_state)
|
||||
|
||||
def set_device_rate(self, pci_slot, rate_type, rate_kbps):
|
||||
def set_device_rate(self, pci_slot, rates):
|
||||
"""Set device rate: max_tx_rate, min_tx_rate
|
||||
|
||||
@param pci_slot: Virtual Function address
|
||||
@param rate_type: device rate name type. Could be 'max_tx_rate' and
|
||||
'min_tx_rate' ('rate' is not supported anymore).
|
||||
@param rate_kbps: device rate in kbps
|
||||
@param rates: dictionary with rate type (str) and the value (int)
|
||||
in Kbps. Example:
|
||||
{'max_tx_rate': 20000, 'min_tx_rate': 10000}
|
||||
{'max_tx_rate': 30000}
|
||||
{'min_tx_rate': 5000}
|
||||
|
||||
"""
|
||||
vf_index = self._get_vf_index(pci_slot)
|
||||
# NOTE(ralonsoh): ip link sets rate in Mbps therefore we need to
|
||||
# convert the rate_kbps value from kbps to Mbps.
|
||||
# Zero means to disable the rate so the lowest rate available is 1Mbps.
|
||||
# Floating numbers are not allowed
|
||||
if 0 < rate_kbps < 1000:
|
||||
rate_mbps = 1
|
||||
else:
|
||||
rate_mbps = helpers.round_val(rate_kbps / 1000.0)
|
||||
rates_mbps = {}
|
||||
for rate_type, rate_kbps in rates.items():
|
||||
if 0 < rate_kbps < 1000:
|
||||
rate_mbps = 1
|
||||
else:
|
||||
rate_mbps = helpers.round_val(rate_kbps / 1000.0)
|
||||
|
||||
log_dict = {
|
||||
'rate_mbps': rate_mbps,
|
||||
'rate_kbps': rate_kbps,
|
||||
'vf_index': vf_index,
|
||||
'rate_type': rate_type
|
||||
}
|
||||
if rate_kbps % 1000 != 0:
|
||||
LOG.debug("'%(rate_type)s' for SR-IOV ports is counted in Mbps; "
|
||||
"setting %(rate_mbps)s Mbps limit for port %(vf_index)s "
|
||||
"instead of %(rate_kbps)s kbps",
|
||||
log_dict)
|
||||
else:
|
||||
LOG.debug("Setting %(rate_mbps)s Mbps limit for port %(vf_index)s",
|
||||
log_dict)
|
||||
rates_mbps[rate_type] = rate_mbps
|
||||
|
||||
return self.pci_dev_wrapper.set_vf_rate(vf_index, rate_type, rate_mbps)
|
||||
missing_rate_types = set(IP_LINK_CAPABILITY_RATES) - rates.keys()
|
||||
if missing_rate_types:
|
||||
# A key is missing. As explained in LP#1962844, in order not to
|
||||
# delete an existing rate ('max_tx_rate', 'min_tx_rate'), it is
|
||||
# needed to read the current VF rates and set the same value again.
|
||||
vf = self._get_vfs()[int(vf_index)]
|
||||
# Devices without 'min_tx_rate' support will return None in this
|
||||
# value. If current value is 0, there is no need to set it again.
|
||||
for _type in (_type for _type in missing_rate_types if vf[_type]):
|
||||
rates_mbps[_type] = vf[_type]
|
||||
|
||||
LOG.debug('Setting %s limits (in Mbps) for port VF %s',
|
||||
rates_mbps, vf_index)
|
||||
return self.pci_dev_wrapper.set_vf_rate(vf_index, rates_mbps)
|
||||
|
||||
def _get_vf_index(self, pci_slot):
|
||||
vf_index = self.pci_slot_map.get(pci_slot)
|
||||
|
|
|
@ -91,20 +91,22 @@ class PciDeviceIPWrapper(ip_lib.IPWrapper):
|
|||
vf_config = {'vf': vf_index, 'spoofchk': int(enabled)}
|
||||
ip.link.set_vf_feature(vf_config)
|
||||
|
||||
def set_vf_rate(self, vf_index, rate_type, rate_value):
|
||||
"""sets vf rate.
|
||||
|
||||
def set_vf_rate(self, vf_index, rates):
|
||||
"""sets vf rates.
|
||||
@param vf_index: vf index
|
||||
@param rate_type: vf rate type ('max_tx_rate', 'min_tx_rate')
|
||||
@param rate_value: vf rate in Mbps
|
||||
@param rates: dictionary with rate type (str) and the value (int)
|
||||
in Mbps. Example:
|
||||
{'max_tx_rate': 20, 'min_tx_rate': 10}
|
||||
{'max_tx_rate': 30}
|
||||
{'min_tx_rate': 5}
|
||||
"""
|
||||
ip = self.device(self.dev_name)
|
||||
vf_config = {'vf': vf_index, 'rate': {rate_type: int(rate_value)}}
|
||||
vf_config = {'vf': vf_index, 'rate': rates}
|
||||
try:
|
||||
ip.link.set_vf_feature(vf_config)
|
||||
except ip_lib.InvalidArgument:
|
||||
# NOTE(ralonsoh): some NICs do not support "min_tx_rate" parameter.
|
||||
# https://bugs.launchpad.net/neutron/+bug/1918464
|
||||
LOG.error('Device %(device)s does not support ip-link vf '
|
||||
'"%(rate_type)s" parameter.',
|
||||
{'device': self.dev_name, 'rate_type': rate_type})
|
||||
'"min_tx_rate" parameter. Rates: %(rates)s',
|
||||
{'device': self.dev_name, 'rates': rates})
|
||||
|
|
|
@ -404,8 +404,12 @@ def get_link_vfs(device, namespace):
|
|||
for vinfo in vfinfo_list.get_attrs('IFLA_VF_INFO'):
|
||||
mac = vinfo.get_attr('IFLA_VF_MAC')
|
||||
link_state = vinfo.get_attr('IFLA_VF_LINK_STATE')
|
||||
rate = vinfo.get_attr('IFLA_VF_RATE', default={})
|
||||
vfs[mac['vf']] = {'mac': mac['mac'],
|
||||
'link_state': link_state['link_state']}
|
||||
'link_state': link_state['link_state'],
|
||||
'max_tx_rate': rate.get('max_tx_rate'),
|
||||
'min_tx_rate': rate.get('min_tx_rate'),
|
||||
}
|
||||
|
||||
return vfs
|
||||
|
||||
|
|
|
@ -430,6 +430,11 @@ class TestEmbSwitch(base.BaseTestCase):
|
|||
"eswitch_manager.PciOsWrapper.scan_vf_devices",
|
||||
return_value=self.SCANNED_DEVICES):
|
||||
self.emb_switch = esm.EmbSwitch(self.DEV_NAME, exclude_devices)
|
||||
self.mock_get_vfs = mock.patch.object(esm.EmbSwitch,
|
||||
'_get_vfs').start()
|
||||
self.vf_rates = [{esm.IP_LINK_CAPABILITY_RATE: 500,
|
||||
esm.IP_LINK_CAPABILITY_MIN_TX_RATE: 250}]
|
||||
self.mock_get_vfs.return_value = self.vf_rates
|
||||
|
||||
def test_get_assigned_devices_info(self):
|
||||
with mock.patch.object(pci_lib.PciDeviceIPWrapper, 'get_assigned_macs',
|
||||
|
@ -508,61 +513,64 @@ class TestEmbSwitch(base.BaseTestCase):
|
|||
self.emb_switch.set_device_spoofcheck,
|
||||
self.WRONG_PCI_SLOT, True)
|
||||
|
||||
def test_set_device_rate_ok(self):
|
||||
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib."
|
||||
"PciDeviceIPWrapper.set_vf_rate") as pci_lib_mock:
|
||||
self.emb_switch.set_device_rate(
|
||||
self.PCI_SLOT, esm.IP_LINK_CAPABILITY_RATE, 2000)
|
||||
pci_lib_mock.assert_called_with(
|
||||
0, esm.IP_LINK_CAPABILITY_RATE, 2)
|
||||
@mock.patch.object(pci_lib.PciDeviceIPWrapper, 'set_vf_rate')
|
||||
def test_set_device_rate_ok(self, mock_set_vf_rate):
|
||||
self.emb_switch.set_device_rate(
|
||||
self.PCI_SLOT, {esm.IP_LINK_CAPABILITY_RATE: 2000})
|
||||
self.vf_rates[0][esm.IP_LINK_CAPABILITY_RATE] = 2
|
||||
mock_set_vf_rate.assert_called_with(0, self.vf_rates[0])
|
||||
|
||||
def test_set_device_max_rate_ok2(self):
|
||||
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib."
|
||||
"PciDeviceIPWrapper.set_vf_rate") as pci_lib_mock:
|
||||
self.emb_switch.set_device_rate(
|
||||
self.PCI_SLOT, esm.IP_LINK_CAPABILITY_RATE, 99)
|
||||
pci_lib_mock.assert_called_with(
|
||||
0, esm.IP_LINK_CAPABILITY_RATE, 1)
|
||||
# No 'min_tx_rate' support
|
||||
vf_rates = [{esm.IP_LINK_CAPABILITY_RATE: 500,
|
||||
esm.IP_LINK_CAPABILITY_MIN_TX_RATE: None}]
|
||||
self.mock_get_vfs.return_value = vf_rates
|
||||
self.emb_switch.set_device_rate(
|
||||
self.PCI_SLOT, {esm.IP_LINK_CAPABILITY_RATE: 2000})
|
||||
vf_rates[0] = {esm.IP_LINK_CAPABILITY_RATE: 2}
|
||||
mock_set_vf_rate.assert_called_with(0, vf_rates[0])
|
||||
|
||||
def test_set_device_max_rate_rounded_ok(self):
|
||||
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib."
|
||||
"PciDeviceIPWrapper.set_vf_rate") as pci_lib_mock:
|
||||
self.emb_switch.set_device_rate(
|
||||
self.PCI_SLOT, esm.IP_LINK_CAPABILITY_RATE, 2001)
|
||||
pci_lib_mock.assert_called_with(0, esm.IP_LINK_CAPABILITY_RATE, 2)
|
||||
@mock.patch.object(pci_lib.PciDeviceIPWrapper, 'set_vf_rate')
|
||||
def test_set_device_max_rate_ok2(self, mock_set_vf_rate):
|
||||
self.emb_switch.set_device_rate(
|
||||
self.PCI_SLOT, {esm.IP_LINK_CAPABILITY_RATE: 99})
|
||||
self.vf_rates[0][esm.IP_LINK_CAPABILITY_RATE] = 1
|
||||
mock_set_vf_rate.assert_called_with(0, self.vf_rates[0])
|
||||
|
||||
def test_set_device_max_rate_rounded_ok2(self):
|
||||
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib."
|
||||
"PciDeviceIPWrapper.set_vf_rate") as pci_lib_mock:
|
||||
self.emb_switch.set_device_rate(
|
||||
self.PCI_SLOT, esm.IP_LINK_CAPABILITY_RATE, 2499)
|
||||
pci_lib_mock.assert_called_with(
|
||||
0, esm.IP_LINK_CAPABILITY_RATE, 2)
|
||||
@mock.patch.object(pci_lib.PciDeviceIPWrapper, 'set_vf_rate')
|
||||
def test_set_device_max_rate_rounded_ok(self, mock_set_vf_rate):
|
||||
self.emb_switch.set_device_rate(
|
||||
self.PCI_SLOT, {esm.IP_LINK_CAPABILITY_RATE: 2001})
|
||||
self.vf_rates[0][esm.IP_LINK_CAPABILITY_RATE] = 2
|
||||
mock_set_vf_rate.assert_called_with(0, self.vf_rates[0])
|
||||
|
||||
def test_set_device_max_rate_rounded_ok3(self):
|
||||
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib."
|
||||
"PciDeviceIPWrapper.set_vf_rate") as pci_lib_mock:
|
||||
self.emb_switch.set_device_rate(
|
||||
self.PCI_SLOT, esm.IP_LINK_CAPABILITY_RATE, 2500)
|
||||
pci_lib_mock.assert_called_with(
|
||||
0, esm.IP_LINK_CAPABILITY_RATE, 3)
|
||||
@mock.patch.object(pci_lib.PciDeviceIPWrapper, 'set_vf_rate')
|
||||
def test_set_device_max_rate_rounded_ok2(self, mock_set_vf_rate):
|
||||
self.emb_switch.set_device_rate(
|
||||
self.PCI_SLOT, {esm.IP_LINK_CAPABILITY_RATE: 2499})
|
||||
self.vf_rates[0][esm.IP_LINK_CAPABILITY_RATE] = 2
|
||||
mock_set_vf_rate.assert_called_with(0, self.vf_rates[0])
|
||||
|
||||
def test_set_device_max_rate_disable(self):
|
||||
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib."
|
||||
"PciDeviceIPWrapper.set_vf_rate") as pci_lib_mock:
|
||||
self.emb_switch.set_device_rate(
|
||||
self.PCI_SLOT, esm.IP_LINK_CAPABILITY_RATE, 0)
|
||||
pci_lib_mock.assert_called_with(
|
||||
0, esm.IP_LINK_CAPABILITY_RATE, 0)
|
||||
@mock.patch.object(pci_lib.PciDeviceIPWrapper, 'set_vf_rate')
|
||||
def test_set_device_max_rate_rounded_ok3(self, mock_set_vf_rate):
|
||||
self.emb_switch.set_device_rate(
|
||||
self.PCI_SLOT, {esm.IP_LINK_CAPABILITY_RATE: 2500})
|
||||
self.vf_rates[0][esm.IP_LINK_CAPABILITY_RATE] = 3
|
||||
mock_set_vf_rate.assert_called_with(0, self.vf_rates[0])
|
||||
|
||||
def test_set_device_max_rate_fail(self):
|
||||
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib."
|
||||
"PciDeviceIPWrapper.set_vf_rate"):
|
||||
self.assertRaises(
|
||||
exc.InvalidPciSlotError,
|
||||
self.emb_switch.set_device_rate,
|
||||
self.WRONG_PCI_SLOT,
|
||||
esm.IP_LINK_CAPABILITY_RATE, 1000)
|
||||
@mock.patch.object(pci_lib.PciDeviceIPWrapper, 'set_vf_rate')
|
||||
def test_set_device_max_rate_disable(self, mock_set_vf_rate):
|
||||
self.emb_switch.set_device_rate(
|
||||
self.PCI_SLOT, {esm.IP_LINK_CAPABILITY_RATE: 0})
|
||||
self.vf_rates[0][esm.IP_LINK_CAPABILITY_RATE] = 0
|
||||
mock_set_vf_rate.assert_called_with(0, self.vf_rates[0])
|
||||
|
||||
@mock.patch.object(pci_lib.PciDeviceIPWrapper, 'set_vf_rate')
|
||||
def test_set_device_max_rate_fail(self, *args):
|
||||
self.assertRaises(
|
||||
exc.InvalidPciSlotError,
|
||||
self.emb_switch.set_device_rate,
|
||||
self.WRONG_PCI_SLOT,
|
||||
{esm.IP_LINK_CAPABILITY_RATE: 1000})
|
||||
|
||||
def test_get_pci_device(self):
|
||||
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib."
|
||||
|
|
|
@ -92,12 +92,12 @@ class TestPciLib(base.BaseTestCase):
|
|||
self.mock_ip_device.link.set_vf_feature.assert_called_once_with(vf)
|
||||
|
||||
def test_set_vf_rate(self):
|
||||
self.pci_wrapper.set_vf_rate(self.VF_INDEX, 'max_tx_rate', 20)
|
||||
self.pci_wrapper.set_vf_rate(self.VF_INDEX, {'max_tx_rate': 20})
|
||||
vf = {'vf': self.VF_INDEX, 'rate': {'max_tx_rate': 20}}
|
||||
self.mock_ip_device.link.set_vf_feature.assert_called_once_with(vf)
|
||||
|
||||
self.mock_ip_device.link.set_vf_feature.reset_mock()
|
||||
self.pci_wrapper.set_vf_rate(self.VF_INDEX, 'min_tx_rate', 10)
|
||||
self.pci_wrapper.set_vf_rate(self.VF_INDEX, {'min_tx_rate': 10})
|
||||
vf = {'vf': self.VF_INDEX, 'rate': {'min_tx_rate': 10}}
|
||||
self.mock_ip_device.link.set_vf_feature.assert_called_once_with(vf)
|
||||
|
||||
|
@ -105,8 +105,9 @@ class TestPciLib(base.BaseTestCase):
|
|||
def test_set_vf_rate_exception(self, mock_log):
|
||||
self.mock_ip_device.link.set_vf_feature.side_effect = (
|
||||
ip_lib.InvalidArgument)
|
||||
self.pci_wrapper.set_vf_rate(self.VF_INDEX, 'min_tx_rate', 10)
|
||||
self.pci_wrapper.set_vf_rate(self.VF_INDEX, {'min_tx_rate': 10})
|
||||
mock_log.error.assert_called_once_with(
|
||||
'Device %(device)s does not support ip-link vf "%(rate_type)s" '
|
||||
'parameter.', {'device': self.DEV_NAME, 'rate_type': 'min_tx_rate'}
|
||||
'Device %(device)s does not support ip-link vf "min_tx_rate" '
|
||||
'parameter. Rates: %(rates)s',
|
||||
{'device': self.DEV_NAME, 'rates': {'min_tx_rate': 10}}
|
||||
)
|
||||
|
|
|
@ -238,9 +238,11 @@ class IpLibTestCase(base.BaseTestCase):
|
|||
vf_info.append(pyroute2.netlink.nlmsg_base())
|
||||
mac_info = {'mac': 'mac_%s' % idx, 'vf': idx}
|
||||
link_state = {'link_state': idx} # see SR-IOV pci_lib.LinkState
|
||||
rates = {'max_tx_rate': idx * 1000, 'min_tx_rate': idx * 500}
|
||||
vf_info[idx].setvalue(
|
||||
{'attrs': [('IFLA_VF_MAC', mac_info),
|
||||
('IFLA_VF_LINK_STATE', link_state)]})
|
||||
('IFLA_VF_LINK_STATE', link_state),
|
||||
('IFLA_VF_RATE', rates)]})
|
||||
vfinfo_list = pyroute2.netlink.nlmsg_base()
|
||||
vfinfo_list.setvalue({'attrs': [('IFLA_VF_INFO', vf_info[0]),
|
||||
('IFLA_VF_INFO', vf_info[1]),
|
||||
|
@ -254,10 +256,13 @@ class IpLibTestCase(base.BaseTestCase):
|
|||
with mock.patch.object(priv_lib, '_run_iproute_link') as mock_iplink:
|
||||
mock_iplink.return_value = [value]
|
||||
result = priv_lib.get_link_vfs('device', 'namespace')
|
||||
self.assertEqual({0: {'mac': 'mac_0', 'link_state': 0},
|
||||
1: {'mac': 'mac_1', 'link_state': 1},
|
||||
2: {'mac': 'mac_2', 'link_state': 2}},
|
||||
result)
|
||||
exp = {0: {'mac': 'mac_0', 'link_state': 0,
|
||||
'max_tx_rate': 0, 'min_tx_rate': 0},
|
||||
1: {'mac': 'mac_1', 'link_state': 1,
|
||||
'max_tx_rate': 1000, 'min_tx_rate': 500},
|
||||
2: {'mac': 'mac_2', 'link_state': 2,
|
||||
'max_tx_rate': 2000, 'min_tx_rate': 1000}}
|
||||
self.assertEqual(exp, result)
|
||||
|
||||
|
||||
class MakeSerializableTestCase(base.BaseTestCase):
|
||||
|
|
Loading…
Reference in New Issue