VF rate limiting support
This feature is part of 'Single NIC support'. This commit implements the rate limit on VFs in terms of max_tx_rate leveraging hardware NIC driver capability. And adjust sriov-device-plugin config.json to make it easy to allocate rate limited VFs to the target Pods. Story: 2008470 Task: 41508 Depends-On: https://review.opendev.org/c/starlingx/config/+/770132 Signed-off-by: Litao Gao <litao.gao@windriver.com> Change-Id: I3b609296b6b5b872f0bb0bd9733740b01e9d421c
This commit is contained in:
parent
c0fadef2c1
commit
69664edc58
|
@ -17,7 +17,7 @@ CREATION_ATTRIBUTES = ['ifname', 'iftype', 'ihost_uuid', 'imtu', 'ifclass',
|
|||
'providernetworks', 'datanetworks', 'ifcapabilities', 'ports', 'imac',
|
||||
'vlan_id', 'uses', 'used_by',
|
||||
'ipv4_mode', 'ipv6_mode', 'ipv4_pool', 'ipv6_pool',
|
||||
'sriov_numvfs', 'sriov_vf_driver', 'ptp_role']
|
||||
'sriov_numvfs', 'sriov_vf_driver', 'ptp_role', 'max_tx_rate']
|
||||
|
||||
|
||||
class iinterface(base.Resource):
|
||||
|
|
|
@ -20,7 +20,8 @@ def _print_iinterface_show(cc, iinterface):
|
|||
'aemode', 'schedpolicy', 'txhashpolicy',
|
||||
'uuid', 'ihost_uuid',
|
||||
'vlan_id', 'uses', 'used_by',
|
||||
'created_at', 'updated_at', 'sriov_numvfs', 'sriov_vf_driver']
|
||||
'created_at', 'updated_at', 'sriov_numvfs',
|
||||
'sriov_vf_driver', 'max_tx_rate']
|
||||
optional_fields = ['ipv4_mode', 'ipv6_mode', 'ipv4_pool', 'ipv6_pool']
|
||||
rename_fields = [{'field': 'dpdksupport', 'label': 'accelerated'}]
|
||||
data = [(f, getattr(iinterface, f, '')) for f in fields]
|
||||
|
@ -84,6 +85,8 @@ def do_host_if_list(cc, args):
|
|||
attr_str = "%s,accelerated=False" % attr_str
|
||||
else:
|
||||
attr_str = "%s,accelerated=True" % attr_str
|
||||
if i.max_tx_rate:
|
||||
attr_str = "%s,max_tx_rate=%s" % (attr_str, i.max_tx_rate)
|
||||
setattr(i, 'attrs', attr_str)
|
||||
|
||||
field_labels = ['uuid', 'name', 'class', 'type', 'vlan id', 'ports',
|
||||
|
@ -169,13 +172,17 @@ def do_host_if_delete(cc, args):
|
|||
metavar='<ptp role>',
|
||||
choices=['master', 'slave', 'none'],
|
||||
help='The PTP role for this interface')
|
||||
@utils.arg('-r', '--max-tx-rate',
|
||||
dest='max_tx_rate',
|
||||
metavar='<max_tx_rate>',
|
||||
help='The max tx rate (Mb/s) of the SR-IOV VF interface')
|
||||
def do_host_if_add(cc, args):
|
||||
"""Add an interface."""
|
||||
|
||||
field_list = ['ifname', 'iftype', 'imtu', 'ifclass', 'aemode',
|
||||
'txhashpolicy', 'vlan_id', 'ptp_role',
|
||||
'ipv4_mode', 'ipv6_mode', 'ipv4_pool', 'ipv6_pool',
|
||||
'sriov_numvfs', 'sriov_vf_driver']
|
||||
'sriov_numvfs', 'sriov_vf_driver', 'max_tx_rate']
|
||||
|
||||
ihost = ihost_utils._find_ihost(cc, args.hostnameorid)
|
||||
|
||||
|
@ -267,13 +274,17 @@ def do_host_if_add(cc, args):
|
|||
metavar='<ptp role>',
|
||||
choices=['master', 'slave', 'none'],
|
||||
help='The PTP role for this interface')
|
||||
@utils.arg('-r', '--max-tx-rate',
|
||||
dest='max_tx_rate',
|
||||
metavar='<max_tx_rate>',
|
||||
help='The max tx rate (Mb/s) of the VF interface')
|
||||
def do_host_if_modify(cc, args):
|
||||
"""Modify interface attributes."""
|
||||
|
||||
rwfields = ['iftype', 'ifname', 'imtu', 'aemode', 'txhashpolicy',
|
||||
'ports', 'ifclass', 'ptp_role',
|
||||
'ipv4_mode', 'ipv6_mode', 'ipv4_pool', 'ipv6_pool',
|
||||
'sriov_numvfs', 'sriov_vf_driver']
|
||||
'sriov_numvfs', 'sriov_vf_driver', 'max_tx_rate']
|
||||
|
||||
ihost = ihost_utils._find_ihost(cc, args.hostnameorid)
|
||||
|
||||
|
|
|
@ -193,6 +193,9 @@ class Interface(base.APIBase):
|
|||
ptp_role = wtypes.text
|
||||
"The PTP role for this interface"
|
||||
|
||||
max_tx_rate = int
|
||||
"The value of configured max tx rate of VF, Mbps"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.fields = list(objects.interface.fields.keys())
|
||||
for k in self.fields:
|
||||
|
@ -217,7 +220,8 @@ class Interface(base.APIBase):
|
|||
'aemode', 'schedpolicy', 'txhashpolicy',
|
||||
'vlan_id', 'uses', 'usesmodify', 'used_by',
|
||||
'ipv4_mode', 'ipv6_mode', 'ipv4_pool', 'ipv6_pool',
|
||||
'sriov_numvfs', 'sriov_vf_driver', 'ptp_role'])
|
||||
'sriov_numvfs', 'sriov_vf_driver', 'ptp_role',
|
||||
'max_tx_rate'])
|
||||
|
||||
# never expose the ihost_id attribute
|
||||
interface.ihost_id = wtypes.Unset
|
||||
|
@ -672,7 +676,8 @@ def _set_defaults(interface):
|
|||
'vlan_id': None,
|
||||
'sriov_numvfs': 0,
|
||||
'sriov_vf_driver': None,
|
||||
'ptp_role': constants.INTERFACE_PTP_ROLE_NONE}
|
||||
'ptp_role': constants.INTERFACE_PTP_ROLE_NONE,
|
||||
'max_tx_rate': None}
|
||||
|
||||
if interface['ifclass'] == constants.INTERFACE_CLASS_DATA:
|
||||
defaults['ipv4_mode'] = constants.IPV4_DISABLED
|
||||
|
@ -1016,6 +1021,43 @@ def _check_network_type_and_port(interface, ihost,
|
|||
raise wsme.exc.ClientSideError(msg)
|
||||
|
||||
|
||||
def _check_interface_ratelimit(interface):
|
||||
# Ensure rate limit is valid for VF interfaces
|
||||
if interface['max_tx_rate'] is not None:
|
||||
if not str(interface['max_tx_rate']).isdigit():
|
||||
msg = _("max_tx_rate must be an integer value.")
|
||||
raise wsme.exc.ClientSideError(msg)
|
||||
if interface['iftype'] != constants.INTERFACE_TYPE_VF:
|
||||
msg = _("max_tx_rate is only allowed to be configured for VF interfaces")
|
||||
raise wsme.exc.ClientSideError(msg)
|
||||
|
||||
# check if an overcommitted config
|
||||
max_tx_rate = interface['max_tx_rate']
|
||||
|
||||
ihost_uuid = interface['ihost_uuid']
|
||||
lower_ifname = interface['uses'][0]
|
||||
lower_iface = (
|
||||
pecan.request.dbapi.iinterface_get(lower_ifname, ihost_uuid))
|
||||
|
||||
ports = pecan.request.dbapi.ethernet_port_get_by_interface(
|
||||
lower_iface['uuid'])
|
||||
if len(ports) > 0 and ports[0]['speed'] is not None:
|
||||
# keep 10% of the bandwidth for PF traffic
|
||||
total_rate_for_vf = int(ports[0]['speed'] * constants.VF_TOTAL_RATE_RATIO)
|
||||
total_rate_used = 0
|
||||
interface_list = pecan.request.dbapi.iinterface_get_all(
|
||||
forihostid=ihost_uuid)
|
||||
for i in interface_list:
|
||||
if (i['iftype'] == constants.INTERFACE_TYPE_VF and
|
||||
lower_ifname == i['uses'][0]):
|
||||
if i['max_tx_rate'] is not None:
|
||||
total_rate_used += i['max_tx_rate']
|
||||
|
||||
if total_rate_used + max_tx_rate > total_rate_for_vf:
|
||||
msg = _("Configured max_tx_rate exceeds total link speed")
|
||||
raise wsme.exc.ClientSideError(msg)
|
||||
|
||||
|
||||
def _check_interface_ptp(interface):
|
||||
# Ensure PTP settings are valid for this interface
|
||||
# Validate PTP role value
|
||||
|
@ -1302,6 +1344,7 @@ def _check_interface_data(op, interface, ihost, existing_interface,
|
|||
(interface['sriov_numvfs'], avail_vfs, lower_iface['ifname']))
|
||||
raise wsme.exc.ClientSideError(msg)
|
||||
_check_interface_ptp(interface)
|
||||
_check_interface_ratelimit(interface)
|
||||
|
||||
return interface
|
||||
|
||||
|
|
|
@ -748,6 +748,9 @@ LINK_SPEED_1G = 1000
|
|||
LINK_SPEED_10G = 10000
|
||||
LINK_SPEED_25G = 25000
|
||||
|
||||
# VF rate limit
|
||||
VF_TOTAL_RATE_RATIO = 0.9
|
||||
|
||||
# DRBD engineering limits.
|
||||
# Link Util values are in Percentage.
|
||||
DRBD_LINK_UTIL_MIN = 5
|
||||
|
|
|
@ -1142,6 +1142,10 @@ class HostLabelInvalid(Invalid):
|
|||
message = _("Host label is invalid. Reason: %(reason)s")
|
||||
|
||||
|
||||
class LinkSpeedInvalid(Invalid):
|
||||
message = _("Link speed is invalid. Reason: %(reason)s")
|
||||
|
||||
|
||||
class PickleableException(Exception):
|
||||
"""
|
||||
Pickleable Exception
|
||||
|
|
|
@ -1209,6 +1209,18 @@ def get_interface_os_ifname(interface, interfaces, ports):
|
|||
return interface['ifname']
|
||||
|
||||
|
||||
def get_sriov_vf_index(addr, addrs):
|
||||
"""
|
||||
Returns vf index of specified pci addr of the vf
|
||||
Returns None if not found
|
||||
"""
|
||||
try:
|
||||
return addrs.index(addr)
|
||||
except ValueError:
|
||||
LOG.error("Index not found for this addr %s." % addr)
|
||||
return None
|
||||
|
||||
|
||||
def get_dhcp_cid(hostname, network_type, mac):
|
||||
"""Create the CID for use with dnsmasq. We use a unique identifier for a
|
||||
client since different networks can operate over the same device (and hence
|
||||
|
|
|
@ -2328,6 +2328,7 @@ class Connection(api.Connection):
|
|||
|
||||
@objects.objectify(objects.interface)
|
||||
def iinterface_update(self, iinterface_id, values):
|
||||
self._interface_ratelimit_encode(values)
|
||||
with _session_for_write() as session:
|
||||
query = model_query(models.Interfaces, read_deleted="no",
|
||||
session=session)
|
||||
|
@ -2353,11 +2354,22 @@ class Connection(api.Connection):
|
|||
def iinterface_destroy(self, iinterface_id):
|
||||
return self._interface_destroy(models.Interfaces, iinterface_id)
|
||||
|
||||
def _interface_ratelimit_encode(self, values):
|
||||
# we need to use 'ifcapabilities' dict to save ratelimit info
|
||||
if values.get('max_tx_rate') is not None:
|
||||
capabilities = {'max_tx_rate': values['max_tx_rate']}
|
||||
if values.get('ifcapabilities') is not None:
|
||||
values['ifcapabilities'].update(capabilities)
|
||||
else:
|
||||
values['ifcapabilities'] = capabilities
|
||||
|
||||
def _interface_create(self, obj, forihostid, values):
|
||||
if not values.get('uuid'):
|
||||
values['uuid'] = uuidutils.generate_uuid()
|
||||
values['forihostid'] = int(forihostid)
|
||||
|
||||
self._interface_ratelimit_encode(values)
|
||||
|
||||
is_profile = values.get('interface_profile', False)
|
||||
with _session_for_write() as session:
|
||||
|
||||
|
|
|
@ -146,7 +146,8 @@ class Interface(base.SysinvObject):
|
|||
'ipv6_pool': utils.uuid_or_none,
|
||||
'sriov_numvfs': utils.int_or_none,
|
||||
'sriov_vf_driver': utils.str_or_none,
|
||||
'ptp_role': utils.str_or_none
|
||||
'ptp_role': utils.str_or_none,
|
||||
'max_tx_rate': utils.int_or_none,
|
||||
}
|
||||
|
||||
_foreign_fields = {'uses': _get_interface_name_list,
|
||||
|
@ -169,3 +170,17 @@ class Interface(base.SysinvObject):
|
|||
def save_changes(self, context, updates):
|
||||
self.dbapi.iinterface_update(self.uuid, # pylint: disable=no-member
|
||||
updates)
|
||||
|
||||
@classmethod
|
||||
def from_db_object(cls, db_obj):
|
||||
cls._interface_ratelimit_decode(db_obj)
|
||||
return cls._from_db_object(cls(), db_obj)
|
||||
|
||||
@classmethod
|
||||
def _interface_ratelimit_decode(cls, db_obj):
|
||||
if not isinstance(db_obj, list):
|
||||
try:
|
||||
capabilities = db_obj['ifcapabilities']
|
||||
db_obj['max_tx_rate'] = capabilities['max_tx_rate']
|
||||
except (ValueError, TypeError):
|
||||
db_obj['max_tx_rate'] = None
|
||||
|
|
|
@ -1031,18 +1031,17 @@ def get_sriov_vf_config(context, iface, port, vf_config):
|
|||
|
||||
# Calculate the VF addresses to assign to a logical VF interface,
|
||||
# taking into account any upper or lower interfaces.
|
||||
vf_addr_list = ''
|
||||
vf_addrs = port.get('sriov_vfs_pci_address', None)
|
||||
if vf_addrs:
|
||||
vf_addr_list = vf_addrs.split(',')
|
||||
vf_addr_list = []
|
||||
all_vf_addr_list = []
|
||||
all_vf_addrs = port.get('sriov_vfs_pci_address', None)
|
||||
if all_vf_addrs:
|
||||
all_vf_addr_list = all_vf_addrs.split(',')
|
||||
vf_addr_list = interface.get_sriov_interface_vf_addrs(
|
||||
context, iface, vf_addr_list)
|
||||
vf_addr_list = ",".join(vf_addr_list)
|
||||
context, iface, all_vf_addr_list)
|
||||
|
||||
# Format the vf addresses as quoted strings in order to prevent
|
||||
# puppet from treating the address as a time/date value
|
||||
vf_addrs = [quoted_str(addr.strip())
|
||||
for addr in vf_addr_list.split(",") if addr]
|
||||
vf_addrs = [quoted_str(addr.strip()) for addr in vf_addr_list if addr]
|
||||
|
||||
# Get the user specified VF driver, if any. If the driver is
|
||||
# None, the driver will be determined by the kernel. That is,
|
||||
|
@ -1055,12 +1054,24 @@ def get_sriov_vf_config(context, iface, port, vf_config):
|
|||
vf_driver = port.get('sriov_vf_driver', None)
|
||||
|
||||
for addr in vf_addrs:
|
||||
vf_config.update({
|
||||
addr: {
|
||||
'addr': addr,
|
||||
'driver': vf_driver
|
||||
}
|
||||
})
|
||||
rate = iface.get('max_tx_rate', None)
|
||||
if rate:
|
||||
vfnum = utils.get_sriov_vf_index(addr, all_vf_addr_list)
|
||||
vf_config.update({
|
||||
addr: {
|
||||
'addr': addr,
|
||||
'driver': vf_driver,
|
||||
'vfnumber': vfnum,
|
||||
'max_tx_rate': rate
|
||||
}
|
||||
})
|
||||
else:
|
||||
vf_config.update({
|
||||
addr: {
|
||||
'addr': addr,
|
||||
'driver': vf_driver
|
||||
}
|
||||
})
|
||||
|
||||
if iface.get('used_by', None):
|
||||
upper_ifaces = iface['used_by']
|
||||
|
|
|
@ -513,8 +513,32 @@ class KubernetesPuppet(base.BasePuppet):
|
|||
driver_list.append(driver)
|
||||
|
||||
pf_name_list = resource['selectors']['pfNames']
|
||||
if port['name'] not in pf_name_list:
|
||||
pf_name_list.append(port['name'])
|
||||
if ifclass == constants.INTERFACE_CLASS_PCI_SRIOV:
|
||||
# In sriov case, we need specify each VF for resource pool
|
||||
# Get VF addresses assigned to this logical VF interface
|
||||
vf_addr_list = []
|
||||
all_vf_addr_list = []
|
||||
vf_addrs = port.get('sriov_vfs_pci_address', None)
|
||||
if vf_addrs:
|
||||
all_vf_addr_list = vf_addrs.split(',')
|
||||
vf_addr_list = interface.get_sriov_interface_vf_addrs(
|
||||
self.context, iface, all_vf_addr_list)
|
||||
|
||||
vfnolst = [utils.get_sriov_vf_index(addr, all_vf_addr_list)
|
||||
for addr in vf_addr_list]
|
||||
vfnolst = [str(vfno) for vfno in vfnolst]
|
||||
vfnolist_str = ",".join(vfnolst)
|
||||
if vfnolist_str:
|
||||
# concat into the form of 'ens785f0#0,2,7,9'
|
||||
pfname_with_vfs = "%s#%s" % (port['name'], vfnolist_str)
|
||||
pf_name_list.append(pfname_with_vfs)
|
||||
else:
|
||||
# error case, cannot find the vf numbers in sriov case
|
||||
LOG.error("Failed to get vf numbers for pci device %s", port['name'])
|
||||
continue
|
||||
else:
|
||||
if port['name'] not in pf_name_list:
|
||||
pf_name_list.append(port['name'])
|
||||
|
||||
if interface.is_a_mellanox_device(self.context, iface):
|
||||
resource['selectors']['isRdma'] = True
|
||||
|
|
|
@ -205,31 +205,40 @@ class InterfaceTestCase(base.FunctionalTest, dbbase.BaseHostTestCase):
|
|||
dbutils.create_test_datanetwork(**dn_values)
|
||||
|
||||
def _create_ethernet(self, ifname=None, networktype=None, ifclass=None,
|
||||
datanetworks=None, host=None, expect_errors=False):
|
||||
datanetworks=None, host=None, expect_errors=False,
|
||||
lower_iface=None):
|
||||
interface_id = len(self.profile['interfaces']) + 1
|
||||
port = None
|
||||
if not ifname:
|
||||
ifname = (networktype or 'eth') + str(interface_id)
|
||||
if not host:
|
||||
host = self.controller
|
||||
if not ifclass and networktype in constants.PLATFORM_NETWORK_TYPES:
|
||||
ifclass = constants.INTERFACE_CLASS_PLATFORM
|
||||
port_id = len(self.profile['ports'])
|
||||
port = dbutils.create_test_ethernet_port(
|
||||
id=port_id,
|
||||
name='eth' + str(port_id),
|
||||
host_id=host.id,
|
||||
interface_id=interface_id,
|
||||
pciaddr='0000:00:00.' + str(port_id + 1),
|
||||
dev_id=0)
|
||||
if not lower_iface:
|
||||
port_id = len(self.profile['ports'])
|
||||
port = dbutils.create_test_ethernet_port(
|
||||
id=port_id,
|
||||
name='eth' + str(port_id),
|
||||
host_id=host.id,
|
||||
interface_id=interface_id,
|
||||
pciaddr='0000:00:00.' + str(port_id + 1),
|
||||
dev_id=0)
|
||||
self.profile['ports'].append(port)
|
||||
|
||||
if not networktype:
|
||||
interface = dbutils.create_test_interface(ifname=ifname,
|
||||
forihostid=host.id,
|
||||
ihost_uuid=host.uuid)
|
||||
else:
|
||||
if lower_iface:
|
||||
uses = [lower_iface['ifname']]
|
||||
else:
|
||||
uses = None
|
||||
interface = self._post_get_test_interface(
|
||||
ifname=ifname,
|
||||
ifclass=ifclass,
|
||||
uses=uses,
|
||||
forihostid=host.id, ihost_uuid=host.uuid)
|
||||
response = self._post_and_check(interface, expect_errors)
|
||||
if expect_errors is False:
|
||||
|
@ -252,7 +261,6 @@ class InterfaceTestCase(base.FunctionalTest, dbbase.BaseHostTestCase):
|
|||
datanetwork_id=dn.id)
|
||||
|
||||
self.profile['interfaces'].append(interface)
|
||||
self.profile['ports'].append(port)
|
||||
|
||||
return port, interface
|
||||
|
||||
|
@ -380,7 +388,8 @@ class InterfaceTestCase(base.FunctionalTest, dbbase.BaseHostTestCase):
|
|||
sriov_totalvfs=sriov_totalvfs,
|
||||
sriov_numvfs=sriov_numvfs,
|
||||
driver='i40e',
|
||||
sriov_vf_driver='i40evf')
|
||||
sriov_vf_driver='i40evf',
|
||||
speed=10000)
|
||||
|
||||
ifclass = constants.INTERFACE_CLASS_PCI_SRIOV
|
||||
interface = self._post_get_test_interface(
|
||||
|
@ -409,7 +418,7 @@ class InterfaceTestCase(base.FunctionalTest, dbbase.BaseHostTestCase):
|
|||
def _create_vf(self, ifname, ifclass=None,
|
||||
lower_iface=None, sriov_numvfs=None,
|
||||
sriov_vf_driver=None, datanetworks=None, host=None,
|
||||
expect_errors=False):
|
||||
expect_errors=False, max_tx_rate=None):
|
||||
if not host:
|
||||
host = self.controller
|
||||
if not lower_iface:
|
||||
|
@ -429,7 +438,8 @@ class InterfaceTestCase(base.FunctionalTest, dbbase.BaseHostTestCase):
|
|||
uses=[lower_iface['ifname']],
|
||||
forihostid=host.id, ihost_uuid=host.uuid,
|
||||
sriov_numvfs=sriov_numvfs,
|
||||
sriov_vf_driver=sriov_vf_driver)
|
||||
sriov_vf_driver=sriov_vf_driver,
|
||||
max_tx_rate=max_tx_rate)
|
||||
response = self._post_and_check(interface, expect_errors)
|
||||
if expect_errors is False:
|
||||
interface['uuid'] = response.json['uuid']
|
||||
|
@ -618,6 +628,36 @@ class InterfaceControllerVlanOverEthernet(InterfaceTestCase):
|
|||
self._create_and_apply_profile(self.controller)
|
||||
|
||||
|
||||
class InterfaceEthernetOverSriov(InterfaceTestCase):
|
||||
|
||||
def _setup_configuration(self):
|
||||
# Setup a sample configuration where the personality is set to a
|
||||
# controller with a worker subfunction and all interfaces are
|
||||
# ethernet aside from a VF over SR-IOV interface.
|
||||
self._create_host(constants.CONTROLLER, constants.WORKER,
|
||||
admin=constants.ADMIN_LOCKED)
|
||||
self._create_datanetworks()
|
||||
|
||||
lower_port, lower_iface = self._create_sriov(
|
||||
'sriov1', sriov_numvfs=2)
|
||||
self._create_vf('vf1', lower_iface=lower_iface, sriov_numvfs=1,
|
||||
sriov_vf_driver='vfio', datanetworks='group0-data1')
|
||||
port, iface = self._create_ethernet(
|
||||
'pxeboot', constants.NETWORK_TYPE_PXEBOOT, lower_iface=lower_iface)
|
||||
self._create_vlan('oam', constants.NETWORK_TYPE_OAM,
|
||||
constants.INTERFACE_CLASS_PLATFORM, 1, lower_iface)
|
||||
self._create_vlan('mgmt', constants.NETWORK_TYPE_MGMT,
|
||||
constants.INTERFACE_CLASS_PLATFORM, 2, lower_iface)
|
||||
self._create_vlan('cluster', constants.NETWORK_TYPE_CLUSTER_HOST,
|
||||
constants.INTERFACE_CLASS_PLATFORM, 3, lower_iface)
|
||||
|
||||
def setUp(self):
|
||||
super(InterfaceEthernetOverSriov, self).setUp()
|
||||
|
||||
def test_ethernet_over_sriov_profile(self):
|
||||
self._create_and_apply_profile(self.controller)
|
||||
|
||||
|
||||
class InterfaceWorkerEthernet(InterfaceTestCase):
|
||||
|
||||
def _setup_configuration(self):
|
||||
|
@ -989,6 +1029,39 @@ class InterfaceAIOVfOverSriov(InterfaceTestCase):
|
|||
self._create_and_apply_profile(self.controller)
|
||||
|
||||
|
||||
class InterfaceAIOVfWithRatelimitOverSriov(InterfaceTestCase):
|
||||
|
||||
def _setup_configuration(self):
|
||||
# Setup a sample configuration where the personality is set to a
|
||||
# controller with a worker subfunction and all interfaces are
|
||||
# ethernet aside from a VF over SR-IOV interface.
|
||||
self._create_host(constants.CONTROLLER, constants.WORKER,
|
||||
admin=constants.ADMIN_LOCKED)
|
||||
self._create_datanetworks()
|
||||
self._create_ethernet('oam', constants.NETWORK_TYPE_OAM)
|
||||
self._create_ethernet('mgmt', constants.NETWORK_TYPE_MGMT)
|
||||
self._create_ethernet('cluster', constants.NETWORK_TYPE_CLUSTER_HOST)
|
||||
self._create_ethernet('data', constants.NETWORK_TYPE_DATA,
|
||||
constants.INTERFACE_CLASS_DATA,
|
||||
'group0-data0')
|
||||
self._create_ethernet('pthru', constants.NETWORK_TYPE_PCI_PASSTHROUGH,
|
||||
constants.INTERFACE_CLASS_PCI_PASSTHROUGH,
|
||||
'group0-ext0')
|
||||
lower_port, lower_iface = self._create_sriov(
|
||||
'sriov1', sriov_numvfs=3, datanetworks='group0-data0')
|
||||
self._create_vf('vf1', lower_iface=lower_iface, sriov_numvfs=1,
|
||||
sriov_vf_driver='vfio', datanetworks='group0-data1')
|
||||
self._create_vf('vf2', lower_iface=lower_iface, sriov_numvfs=1,
|
||||
sriov_vf_driver='vfio', datanetworks='group0-data1',
|
||||
max_tx_rate=100)
|
||||
|
||||
def setUp(self):
|
||||
super(InterfaceAIOVfWithRatelimitOverSriov, self).setUp()
|
||||
|
||||
def test_AIO_vf_with_ratelimit_over_sriov_profile(self):
|
||||
self._create_and_apply_profile(self.controller)
|
||||
|
||||
|
||||
# Test that the unsupported config is rejected
|
||||
class InterfaceAIOVlanOverDataEthernet(InterfaceTestCase):
|
||||
|
||||
|
@ -1952,6 +2025,86 @@ class TestPostMixin(object):
|
|||
self.assertIn('VF interfaces can only use one lower',
|
||||
response.json['error_message'])
|
||||
|
||||
def test_create_vf_interface_with_valid_ratelimit(self):
|
||||
self._create_ethernet('mgmt', constants.NETWORK_TYPE_MGMT,
|
||||
host=self.worker)
|
||||
port, lower_iface = self._create_sriov(
|
||||
'sriov', host=self.worker, sriov_numvfs=4)
|
||||
# default speed is 10Gbps
|
||||
self._create_vf('vf1', lower_iface=lower_iface,
|
||||
host=self.worker, sriov_numvfs=1,
|
||||
expect_errors=False, max_tx_rate=1000)
|
||||
self._create_vf('vf2', lower_iface=lower_iface,
|
||||
host=self.worker, sriov_numvfs=1,
|
||||
expect_errors=False, max_tx_rate=8000)
|
||||
|
||||
def test_create_vf_interface_with_invalid_ratelimit(self):
|
||||
self._create_ethernet('mgmt', constants.NETWORK_TYPE_MGMT,
|
||||
host=self.worker)
|
||||
port, lower_iface = self._create_sriov(
|
||||
'sriov', host=self.worker, sriov_numvfs=4)
|
||||
self._create_vf('vf1', lower_iface=lower_iface,
|
||||
host=self.worker, sriov_numvfs=1,
|
||||
expect_errors=False, max_tx_rate=1000)
|
||||
# exceeds total bandwidth
|
||||
self._create_vf('vf2', lower_iface=lower_iface,
|
||||
host=self.worker, sriov_numvfs=1,
|
||||
expect_errors=True, max_tx_rate=9000)
|
||||
# rate is not a numeric value
|
||||
self._create_vf('vf3', lower_iface=lower_iface,
|
||||
host=self.worker, sriov_numvfs=1,
|
||||
expect_errors=True, max_tx_rate='ratelimit')
|
||||
|
||||
def test_interface_vf_ratelimit_modify_add_ratelimit(self):
|
||||
self._create_ethernet('mgmt', constants.NETWORK_TYPE_MGMT,
|
||||
host=self.worker)
|
||||
port, lower_iface = self._create_sriov(
|
||||
'sriov', host=self.worker, sriov_numvfs=4)
|
||||
vf = self._create_vf('vf1', lower_iface=lower_iface,
|
||||
host=self.worker, sriov_numvfs=3, expect_errors=False)
|
||||
|
||||
patch_result = self.patch_dict_json(
|
||||
'%s' % self._get_path(vf['uuid']),
|
||||
max_tx_rate=2000)
|
||||
|
||||
self.assertEqual('application/json', patch_result.content_type)
|
||||
self.assertEqual(http_client.OK, patch_result.status_code)
|
||||
self.assertEqual(2000, patch_result.json['max_tx_rate'])
|
||||
|
||||
def test_interface_vf_ratelimit_modify_adjust_ratelimit(self):
|
||||
self._create_ethernet('mgmt', constants.NETWORK_TYPE_MGMT,
|
||||
host=self.worker)
|
||||
port, lower_iface = self._create_sriov(
|
||||
'sriov', host=self.worker, sriov_numvfs=4)
|
||||
vf = self._create_vf('vf1', lower_iface=lower_iface,
|
||||
host=self.worker, sriov_numvfs=3,
|
||||
expect_errors=False, max_tx_rate=1000)
|
||||
|
||||
patch_result = self.patch_dict_json(
|
||||
'%s' % self._get_path(vf['uuid']),
|
||||
max_tx_rate=2000)
|
||||
|
||||
self.assertEqual('application/json', patch_result.content_type)
|
||||
self.assertEqual(http_client.OK, patch_result.status_code)
|
||||
self.assertEqual(2000, patch_result.json['max_tx_rate'])
|
||||
|
||||
def test_interface_vf_ratelimit_modify_clear_ratelimit(self):
|
||||
self._create_ethernet('mgmt', constants.NETWORK_TYPE_MGMT,
|
||||
host=self.worker)
|
||||
port, lower_iface = self._create_sriov(
|
||||
'sriov', host=self.worker, sriov_numvfs=4)
|
||||
vf = self._create_vf('vf1', lower_iface=lower_iface,
|
||||
host=self.worker, sriov_numvfs=3,
|
||||
expect_errors=False, max_tx_rate=1000)
|
||||
|
||||
patch_result = self.patch_dict_json(
|
||||
'%s' % self._get_path(vf['uuid']),
|
||||
max_tx_rate=0)
|
||||
|
||||
self.assertEqual('application/json', patch_result.content_type)
|
||||
self.assertEqual(http_client.OK, patch_result.status_code)
|
||||
self.assertEqual(0, patch_result.json['max_tx_rate'])
|
||||
|
||||
|
||||
class TestAIOPost(InterfaceTestCase):
|
||||
def setUp(self):
|
||||
|
|
|
@ -1046,7 +1046,8 @@ def get_test_interface(**kw):
|
|||
'sriov_numvfs': kw.get('sriov_numvfs', None),
|
||||
'sriov_vf_driver': kw.get('sriov_vf_driver', None),
|
||||
'sriov_vf_pdevice_id': kw.get('sriov_vf_pdevice_id', None),
|
||||
'ptp_role': kw.get('ptp_role', None)
|
||||
'ptp_role': kw.get('ptp_role', None),
|
||||
'max_tx_rate': kw.get('max_tx_rate', None)
|
||||
}
|
||||
return interface
|
||||
|
||||
|
|
|
@ -8,7 +8,9 @@ from __future__ import print_function
|
|||
import os
|
||||
import uuid
|
||||
import yaml
|
||||
import mock
|
||||
|
||||
from sysinv.common import utils
|
||||
from sysinv.common import constants
|
||||
from sysinv.puppet import interface
|
||||
from sysinv.puppet import puppet
|
||||
|
@ -250,7 +252,7 @@ class InterfaceTestCaseMixin(base.PuppetTestCaseMixin):
|
|||
return db_interface
|
||||
|
||||
def _create_vf_test(self, ifname, num_vfs, vf_driver=None,
|
||||
lower_iface=None):
|
||||
lower_iface=None, max_tx_rate=None):
|
||||
if not lower_iface:
|
||||
lower_port, lower_iface = self._create_ethernet_test(
|
||||
'sriov', constants.INTERFACE_CLASS_PCI_SRIOV,
|
||||
|
@ -277,7 +279,8 @@ class InterfaceTestCaseMixin(base.PuppetTestCaseMixin):
|
|||
'networktype': constants.NETWORK_TYPE_PCI_SRIOV,
|
||||
'imtu': 1500,
|
||||
'sriov_numvfs': num_vfs,
|
||||
'sriov_vf_driver': vf_driver}
|
||||
'sriov_vf_driver': vf_driver,
|
||||
'max_tx_rate': max_tx_rate}
|
||||
lower_iface['used_by'].append(interface['ifname'])
|
||||
db_interface = dbutils.create_test_interface(**interface)
|
||||
self.interfaces.append(db_interface)
|
||||
|
@ -1417,11 +1420,12 @@ class InterfaceTestCase(InterfaceTestCaseMixin, dbbase.BaseHostTestCase):
|
|||
self.assertEqual(expected, config)
|
||||
|
||||
def _create_sriov_vf_config(self, iface_vf_driver, port_vf_driver,
|
||||
vf_addr_list, num_vfs):
|
||||
vf_addr_list, num_vfs, max_tx_rate=None):
|
||||
self.iface['ifclass'] = constants.INTERFACE_CLASS_PCI_SRIOV
|
||||
self.iface['networktype'] = constants.NETWORK_TYPE_PCI_SRIOV
|
||||
self.iface['sriov_vf_driver'] = iface_vf_driver
|
||||
self.iface['sriov_numvfs'] = num_vfs
|
||||
self.iface['max_tx_rate'] = max_tx_rate
|
||||
self.port['sriov_vf_driver'] = port_vf_driver
|
||||
self.port['sriov_vfs_pci_address'] = vf_addr_list
|
||||
self._update_context()
|
||||
|
@ -1577,6 +1581,59 @@ class InterfaceTestCase(InterfaceTestCaseMixin, dbbase.BaseHostTestCase):
|
|||
vf_config=expected_vf_config)
|
||||
self.assertEqual(expected, config)
|
||||
|
||||
@mock.patch.object(utils, 'get_sriov_vf_index')
|
||||
def test_get_sriov_config_with_ratelimit(self, mock_get_sriov_vf_index):
|
||||
vf_addr1 = "0000:81:00.0"
|
||||
vf_addr2 = "0000:81:01.0"
|
||||
device_id = '1572'
|
||||
port_name = 'eth0'
|
||||
vf_addr_list = "{},{}".format(vf_addr1, vf_addr2)
|
||||
num_vfs = 4
|
||||
max_tx_rate = 1000
|
||||
|
||||
mock_get_sriov_vf_index.side_effect = [0, 1]
|
||||
config = self._create_sriov_vf_config(
|
||||
constants.SRIOV_DRIVER_TYPE_VFIO, 'i40evf', vf_addr_list,
|
||||
num_vfs, max_tx_rate)
|
||||
expected_vf_config = {
|
||||
'0000:81:00.0': {'addr': '0000:81:00.0', 'driver': 'vfio-pci', 'max_tx_rate': 1000, 'vfnumber': 0},
|
||||
'0000:81:01.0': {'addr': '0000:81:01.0', 'driver': 'vfio-pci', 'max_tx_rate': 1000, 'vfnumber': 1}
|
||||
}
|
||||
expected = self._get_sriov_config(
|
||||
ifname=self.iface['ifname'],
|
||||
vf_driver='vfio-pci',
|
||||
num_vfs=num_vfs,
|
||||
device_id=device_id,
|
||||
port_name=port_name,
|
||||
vf_config=expected_vf_config)
|
||||
self.assertEqual(expected, config)
|
||||
|
||||
def test_get_sriov_config_vf_sibling_with_ratelimit(self):
|
||||
port, iface = self._create_ethernet_test(
|
||||
'sriov1', constants.INTERFACE_CLASS_PCI_SRIOV,
|
||||
constants.NETWORK_TYPE_PCI_SRIOV, sriov_numvfs=4,
|
||||
iface_sriov_vf_driver=None,
|
||||
port_sriov_vf_driver="iavf",
|
||||
sriov_vfs_pci_address="0000:b1:02.0,0000:b1:02.1,0000:b1:02.2,0000:b1:02.3")
|
||||
self._create_vf_test("vf1", 2, 'vfio', lower_iface=iface)
|
||||
self._create_vf_test("vf2", 1, 'netdevice', lower_iface=iface, max_tx_rate=1000)
|
||||
self._update_context()
|
||||
|
||||
config = interface.get_sriov_config(self.context, iface)
|
||||
|
||||
expected_vf_config = {
|
||||
'0000:b1:02.0': {'addr': '0000:b1:02.0', 'driver': None},
|
||||
'0000:b1:02.1': {'addr': '0000:b1:02.1', 'driver': 'iavf', 'max_tx_rate': 1000, 'vfnumber': 1},
|
||||
'0000:b1:02.2': {'addr': '0000:b1:02.2', 'driver': 'vfio-pci'},
|
||||
'0000:b1:02.3': {'addr': '0000:b1:02.3', 'driver': 'vfio-pci'}
|
||||
}
|
||||
expected = self._get_sriov_config(
|
||||
iface['ifname'], None,
|
||||
num_vfs=4, pf_addr=port['pciaddr'],
|
||||
port_name="eth1",
|
||||
vf_config=expected_vf_config)
|
||||
self.assertEqual(expected, config)
|
||||
|
||||
def test_is_a_mellanox_cx3_device_false(self):
|
||||
self.assertFalse(
|
||||
interface.is_a_mellanox_cx3_device(self.context, self.iface))
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
#
|
||||
|
||||
import json
|
||||
import mock
|
||||
import re
|
||||
import uuid
|
||||
|
||||
from sysinv.common import utils
|
||||
from sysinv.common import constants
|
||||
from sysinv.common import device as dconstants
|
||||
from sysinv.puppet import interface
|
||||
|
@ -39,7 +41,8 @@ class SriovdpTestCase(test_interface.InterfaceTestCaseMixin, dbbase.BaseHostTest
|
|||
self.port, self.iface = self._create_ethernet_test(
|
||||
'sriov1', constants.INTERFACE_CLASS_PCI_SRIOV,
|
||||
constants.NETWORK_TYPE_PCI_SRIOV, sriov_numvfs=2,
|
||||
sriov_vf_driver='ixgbevf')
|
||||
sriov_vf_driver='ixgbevf',
|
||||
sriov_vfs_pci_address="0000:b1:02.0,0000:b1:02.1")
|
||||
|
||||
# Create a datanetwork and assign the interface to it
|
||||
dn_values = {
|
||||
|
@ -154,8 +157,9 @@ class SriovdpTestCase(test_interface.InterfaceTestCaseMixin, dbbase.BaseHostTest
|
|||
}
|
||||
return config
|
||||
|
||||
def test_generate_sriovdp_config_8086(self):
|
||||
|
||||
@mock.patch.object(utils, 'get_sriov_vf_index')
|
||||
def test_generate_sriovdp_config_8086(self, mock_get_sriov_vf_index):
|
||||
mock_get_sriov_vf_index.side_effect = [1, 2]
|
||||
self._setup_iface_configuration()
|
||||
test_config = {
|
||||
'pf_vendor': 'Intel Corporation [8086]',
|
||||
|
@ -171,13 +175,15 @@ class SriovdpTestCase(test_interface.InterfaceTestCaseMixin, dbbase.BaseHostTest
|
|||
self._get_pcidp_vendor_id(self.port),
|
||||
test_config['vf_device'],
|
||||
test_config['vf_driver'],
|
||||
pfName=self.port['name'],
|
||||
pfName="%s#1,2" % self.port['name'],
|
||||
datanetwork=self.datanetwork['name']
|
||||
)
|
||||
mock_get_sriov_vf_index.assert_called()
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_generate_sriovdp_config_mlx(self):
|
||||
|
||||
@mock.patch.object(utils, 'get_sriov_vf_index')
|
||||
def test_generate_sriovdp_config_mlx(self, mock_get_sriov_vf_index):
|
||||
mock_get_sriov_vf_index.side_effect = [1, 2]
|
||||
self._setup_iface_configuration()
|
||||
test_config = {
|
||||
'pf_vendor': 'Mellanox Technologies [15b3]',
|
||||
|
@ -193,7 +199,7 @@ class SriovdpTestCase(test_interface.InterfaceTestCaseMixin, dbbase.BaseHostTest
|
|||
self._get_pcidp_vendor_id(self.port),
|
||||
test_config['vf_device'],
|
||||
test_config['vf_driver'],
|
||||
pfName=self.port['name'],
|
||||
pfName="%s#1,2" % self.port['name'],
|
||||
datanetwork=self.datanetwork['name']
|
||||
)
|
||||
self.assertEqual(expected, actual)
|
||||
|
|
Loading…
Reference in New Issue