1
0
mirror of https://github.com/CumulusNetworks/ifupdown2.git synced 2024-05-06 15:54:50 +00:00
tomvil 70bd0e86e7 addons: Allow to set max value in --set-ring option
This makes some automation cases much easier

```
auto eth0
iface eth0 inet dhcp
  ring-rx max
  ring-tx max

Ring parameters for eth0:
Pre-set maximums:
RX:		4096
RX Mini:	n/a
RX Jumbo:	n/a
TX:		4096
Current hardware settings:
RX:		4096
RX Mini:	n/a
RX Jumbo:	n/a
TX:		4096
```

Signed-off-by: tomvil <tomas.vilemaitis@gmail.com>
2023-04-11 15:07:41 +03:00

663 lines
28 KiB
Python

#!/usr/bin/env python3
#
# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved.
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
#
import os
try:
from ifupdown2.lib.addon import Addon
import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
import ifupdown2.ifupdown.policymanager as policymanager
import ifupdown2.ifupdown.statemanager as statemanager
from ifupdown2.ifupdown.iface import *
from ifupdown2.ifupdown.utils import utils
from ifupdown2.ifupdown.exceptions import moduleNotSupported
from ifupdown2.ifupdownaddons.utilsbase import *
from ifupdown2.ifupdownaddons.modulebase import moduleBase
except (ImportError, ModuleNotFoundError):
from lib.addon import Addon
import ifupdown.ifupdownflags as ifupdownflags
import ifupdown.policymanager as policymanager
import ifupdown.statemanager as statemanager
from ifupdown.iface import *
from ifupdown.utils import utils
from ifupdown.exceptions import moduleNotSupported
from ifupdownaddons.utilsbase import *
from ifupdownaddons.modulebase import moduleBase
class ethtool(Addon, moduleBase):
""" ifupdown2 addon module to configure ethtool attributes """
_modinfo = {
"mhelp": "ethtool configuration module for interfaces",
"attrs": {
"link-speed": {
"help": "set link speed",
"validvals": [
"10",
"100",
"1000",
"10000",
"25000",
"40000",
"50000",
"100000",
"200000",
"400000"
],
"example": ["link-speed 1000"],
"default": "varies by platform and port"
},
"link-duplex": {
"help": "set link duplex",
"example": ["link-duplex full"],
"validvals": ["half", "full"],
"default": "full"
},
"link-autoneg": {
"help": "set autonegotiation",
"example": ["link-autoneg on"],
"validvals": ["yes", "no", "on", "off"],
"default": "varies by platform and port"
},
"link-fec": {
"help": "set forward error correction mode",
"example": ["link-fec rs"],
"validvals": ["rs", "baser", "auto", "off"],
"default": "varies by platform and port"
},
'gro-offload': {
'help': 'Generic Receive Offload',
'example': ['gro-offload on'],
'validvals': ['on', 'off'],
'default': 'varies by interface'
},
'lro-offload': {
'help': 'Large Receive Offload',
'example': ['lro-offload on'],
'validvals': ['on', 'off'],
'default': 'varies by interface'
},
'gso-offload': {
'help': 'Generic Segmentation Offload',
'example': ['tso-offload on'],
'validvals': ['on', 'off'],
'default': 'varies by interface'
},
'tso-offload': {
'help': 'TCP Segmentation Offload',
'example': ['tso-offload on'],
'validvals': ['on', 'off'],
'default': 'varies by interface'
},
'ufo-offload': {
'help': 'UDP Fragmentation Offload',
'example': ['ufo-offload on'],
'validvals': ['on', 'off'],
'default': 'varies by interface'
},
'tx-offload': {
'help': 'TX Checksum Offload',
'example': ['tx-offload on'],
'validvals': ['on', 'off'],
'default': 'varies by interface'
},
'rx-offload': {
'help': 'RX Checksum Offload',
'example': ['rx-offload on'],
'validvals': ['on', 'off'],
'default': 'varies by interface'
},
'ring-rx': {
'help': 'Ring RX Parameter',
'example': ['ring-rx 512'],
'validvals': ['max', '<number>'],
'default': 'varies by interface'
},
'ring-tx': {
'help': 'Ring TX Parameter',
'example': ['ring-tx 512'],
'validvals': ['max', '<number>'],
'default': 'varies by interface'
},
}
}
def __init__(self, *args, **kargs):
Addon.__init__(self)
moduleBase.__init__(self, *args, **kargs)
if not os.path.exists(utils.ethtool_cmd):
raise moduleNotSupported('module init failed: %s: not found' % utils.ethtool_cmd)
# keep a list of iface objects who have modified link attributes
self.ifaceobjs_modified_configs = []
# Cache for features
self.feature_cache = None
self.ethtool_ignore_errors = policymanager.policymanager_api.get_module_globals(
module_name=self.__class__.__name__,
attr='ethtool_ignore_errors'
)
def do_ring_settings(self, ifaceobj, attr_name, option):
# Get the current configuration value and default value for the specified attribute
config_val = ifaceobj.get_attr_value_first(attr_name)
default_val = policymanager.policymanager_api.get_iface_default(
module_name='ethtool',
ifname=ifaceobj.name,
attr=attr_name)
# Check which variable to use, config_val > default_val. If none are set, return.
value = config_val or default_val
if not value:
return
if value == "max":
# Get the maximum value for the specified attribute
max_val = self.get_max_attr(attr_name, ifaceobj)
if not max_val:
return
value = max_val
# Get the current running value
running_val = self.get_running_attr(attr_name, ifaceobj)
# If the value is the same as the running value, do nothing
if value == running_val:
return
# Generate the ethtool command
cmd = ('%s --set-ring %s %s %s' %
(utils.ethtool_cmd, ifaceobj.name, option, value))
# Execute the ethtool command if command is generated
if cmd:
try:
utils.exec_command(cmd)
except Exception as e:
self.log_error('%s: %s' %(ifaceobj.name, str(e)), ifaceobj)
def do_offload_settings(self, ifaceobj, attr_name, eth_name):
default = 'default_' + eth_name
config_val = ifaceobj.get_attr_value_first(attr_name)
# Default
default_val = None
saved_ifaceobjs = statemanager.statemanager_api.get_ifaceobjs(ifaceobj.name)
if saved_ifaceobjs:
default_val = saved_ifaceobjs[0].get_attr_value_first(default)
if config_val or default_val:
# get running value
running_val = str(self.get_running_attr(eth_name, ifaceobj)).lower()
# Save default value
# Load state data
if not default_val:
ifaceobj.config[default] = [running_val]
elif config_val:
# resave for state
ifaceobj.config[default] = [default_val]
if not config_val:
config_val = default_val
if config_val and config_val != running_val:
try:
cmd = ('%s -K %s %s %s' %
(utils.ethtool_cmd, ifaceobj.name, eth_name, config_val))
utils.exec_command(cmd)
except Exception as e:
self.log_error('%s: %s' %(ifaceobj.name, str(e)), ifaceobj)
self.ethtool_ignore_errors = policymanager.policymanager_api.get_module_globals(
module_name=self.__class__.__name__,
attr='ethtool_ignore_errors'
)
def do_fec_settings(self, ifaceobj):
feccmd = ''
# attribute existed before but we must reset to default
config_val = ifaceobj.get_attr_value_first('link-fec')
default_val = policymanager.policymanager_api.get_iface_default(
module_name='ethtool',
ifname=ifaceobj.name,
attr='link-fec')
if not default_val and not config_val:
# there is no point in checking the running config
# if we have no default and the user did not have settings
return
# use only lowercase values
running_val = str(self.get_running_attr('fec', ifaceobj)).lower()
if config_val:
config_val = config_val.lower()
if default_val:
default_val = default_val.lower()
# check running values
if config_val and config_val == running_val:
return
if not config_val and default_val and default_val == running_val:
# nothing configured but the default is running
return
# if we got this far, we need to change it
if config_val and (config_val != running_val):
# if the configured value is not set, set it
feccmd = ' %s %s' % ("encoding", config_val)
elif default_val and (default_val != running_val):
# or if it has a default not equal to running value, set it
feccmd = ' %s %s' % ("encoding", default_val)
if feccmd:
try:
feccmd = ('%s --set-fec %s %s' %
(utils.ethtool_cmd, ifaceobj.name, feccmd))
utils.exec_command(feccmd)
except Exception as e:
if not self.ethtool_ignore_errors:
self.log_error('%s: %s' %(ifaceobj.name, str(e)), ifaceobj)
else:
pass
def do_speed_settings(self, ifaceobj, operation='post_up'):
cmd = ''
autoneg_to_configure = None
speed_to_configure = None
duplex_to_configure = None
config_speed = ifaceobj.get_attr_value_first('link-speed')
config_duplex = ifaceobj.get_attr_value_first('link-duplex')
config_autoneg = ifaceobj.get_attr_value_first('link-autoneg')
default_speed = policymanager.policymanager_api.get_iface_default(
module_name='ethtool',
ifname=ifaceobj.name,
attr='link-speed'
)
default_duplex = policymanager.policymanager_api.get_iface_default(
module_name='ethtool',
ifname=ifaceobj.name,
attr='link-duplex'
)
default_autoneg = policymanager.policymanager_api.get_iface_default(
module_name='ethtool',
ifname=ifaceobj.name,
attr='link-autoneg'
)
# autoneg wins if provided by user and is on
if config_autoneg and utils.get_boolean_from_string(config_autoneg):
autoneg_to_configure = config_autoneg
speed_to_configure = None
duplex_to_configure = None
elif config_speed:
# Any speed settings configured by the user wins
autoneg_to_configure = None
speed_to_configure = config_speed
duplex_to_configure = config_duplex
if not config_duplex:
duplex_to_configure = default_duplex
else:
# if user given autoneg config is off, we must respect that and
# override any default autoneg config
if config_autoneg and not utils.get_boolean_from_string(config_autoneg):
default_autoneg = 'off'
if default_autoneg and utils.get_boolean_from_string(default_autoneg):
autoneg_to_configure = utils.get_onoff_bool(default_autoneg)
speed_to_configure = None
duplex_to_configure = None
else:
autoneg_to_configure = None
speed_to_configure = default_speed
duplex_to_configure = default_duplex
if autoneg_to_configure:
autoneg_to_configure = utils.get_onoff_bool(autoneg_to_configure)
# check running values
running_val = self.get_running_attr('autoneg', ifaceobj)
if autoneg_to_configure != running_val:
# if the configured value is not set, set it
cmd += ' autoneg %s' % autoneg_to_configure
else:
force_set = False
if speed_to_configure:
# check running values
if utils.get_boolean_from_string(self.get_running_attr('autoneg', ifaceobj) or 'off'):
cmd = 'autoneg off'
# if we are transitioning from autoneg 'on' to 'off'
# don't check running speed
force_set = True
running_val = self.get_running_attr('speed', ifaceobj)
if force_set or (speed_to_configure != running_val):
# if the configured value is not set, set it
cmd += ' speed %s' % speed_to_configure
if duplex_to_configure:
# check running values
running_val = self.get_running_attr('duplex', ifaceobj)
if force_set or (duplex_to_configure != running_val):
# if the configured value is not set, set it
cmd += ' duplex %s' % duplex_to_configure
if cmd:
try:
cmd = ('%s -s %s %s' % (utils.ethtool_cmd, ifaceobj.name, cmd))
utils.exec_command(cmd)
except Exception as e:
if not self.ethtool_ignore_errors:
self.log_error('%s: %s' % (ifaceobj.name, str(e)), ifaceobj)
def _pre_up(self, ifaceobj, operation='post_up'):
"""
_pre_up and _pre_down will reset the layer 2 attributes to default policy
settings.
"""
if not self.cache.link_exists(ifaceobj.name):
return
self.do_speed_settings(ifaceobj)
self.do_fec_settings(ifaceobj)
self.do_ring_settings(ifaceobj, 'ring-rx', 'rx')
self.do_ring_settings(ifaceobj, 'ring-tx', 'tx')
self.do_offload_settings(ifaceobj, 'gro-offload', 'gro')
self.do_offload_settings(ifaceobj, 'lro-offload', 'lro')
self.do_offload_settings(ifaceobj, 'gso-offload', 'gso')
self.do_offload_settings(ifaceobj, 'tso-offload', 'tso')
self.do_offload_settings(ifaceobj, 'ufo-offload', 'ufo')
self.do_offload_settings(ifaceobj, 'tx-offload', 'tx')
self.do_offload_settings(ifaceobj, 'rx-offload', 'rx')
def _pre_down(self, ifaceobj):
pass #self._post_up(ifaceobj,operation="_pre_down")
def _query_check(self, ifaceobj, ifaceobjcurr):
"""
_query_check() needs to compare the configured (or running)
attribute with the running attribute.
If there is nothing configured, we compare the default attribute with
the running attribute and FAIL if they are different.
This is because a reboot will lose their running attribute
(the default will get set).
"""
for attr in ['speed', 'duplex', 'autoneg', 'fec']:
configured = ifaceobj.get_attr_value_first('link-%s'%attr)
# if there is nothing configured, do not check
if not configured:
if not ifupdownflags.flags.WITHDEFAULTS:
continue
default = policymanager.policymanager_api.get_iface_default(
module_name='ethtool',
ifname=ifaceobj.name,
attr='link-%s'%attr)
# if we have no default, do not bother checking
# this avoids ethtool calls on virtual interfaces
if not default:
continue
# autoneg comes from ethtool whereas speed and duplex from /sys/class
running_attr = self.get_running_attr(attr, ifaceobj)
if not running_attr:
if not configured:
continue
ifaceobjcurr.update_config_with_status('link-%s' % attr,
'unknown', 1)
continue
if attr == 'autoneg':
if configured == 'yes' and running_attr == 'on':
running_attr = 'yes'
elif configured == 'no' and running_attr == 'off':
running_attr = 'no'
# we make sure we can get a running value first
if (running_attr and configured and running_attr == configured):
# PASS since running is what is configured
ifaceobjcurr.update_config_with_status('link-%s'%attr,
running_attr, 0)
elif (running_attr and configured and running_attr != configured):
# We show a FAIL since it is not the configured or default
ifaceobjcurr.update_config_with_status('link-%s'%attr,
running_attr, 1)
elif (running_attr and default and running_attr == default):
# PASS since running is default
ifaceobjcurr.update_config_with_status('link-%s'%attr,
running_attr, 0)
elif (default or configured):
# We show a FAIL since it is not the configured or default
ifaceobjcurr.update_config_with_status('link-%s'%attr,
running_attr, 1)
return
def get_autoneg(self,ethtool_output=None):
"""
get_autoneg simply calls the ethtool command and parses out
the autoneg value.
"""
ethtool_attrs = ethtool_output.split()
if ('Auto-negotiation:' in ethtool_attrs):
return(ethtool_attrs[ethtool_attrs.index('Auto-negotiation:')+1])
else:
return(None)
def get_fec_encoding(self,ethtool_output=None):
"""
get_fec_encoding simply calls the ethtool show-fec command and parses out
the fec encoding value.
"""
try:
for attr in ethtool_output.splitlines():
if attr.startswith('Configured FEC encodings:'):
fec_attrs = attr.split()
return(fec_attrs[fec_attrs.index('encodings:')+1])
except Exception as e:
self.logger.debug('ethtool: problems in ethtool set-fec output'
' %s: %s' %(ethtool_output.splitlines(), str(e)))
return(None)
def get_offload_setting(self, ethtool_output, setting):
value = None
for line in ethtool_output.splitlines():
if setting in line:
if 'on' in line:
value = 'on'
elif 'off' in line:
value = 'off'
break
return value
def get_ring_setting(self, ethtool_output, setting, get_ring_max=False):
value = None
if get_ring_max:
settings = ethtool_output.split('Current hardware settings:', 1)[0]
else:
settings = ethtool_output.split('Current hardware settings:', 1)[1]
for line in settings.splitlines():
if line.startswith(setting):
value = line.split(':', 1)[1]
return value.strip()
return value
def get_attr_value(self,attr='',ifaceobj=None,get_max=False):
if not ifaceobj or not attr:
return
attr_value = None
try:
if attr == 'autoneg':
output = utils.exec_commandl([utils.ethtool_cmd, ifaceobj.name])
attr_value = self.get_autoneg(ethtool_output=output)
elif attr == 'ring-rx':
output = utils.exec_command('%s --show-ring %s'%
(utils.ethtool_cmd, ifaceobj.name))
attr_value = self.get_ring_setting(ethtool_output=output, setting='RX:', get_ring_max=get_max)
elif attr == 'ring-tx':
output = utils.exec_command('%s --show-ring %s'%
(utils.ethtool_cmd, ifaceobj.name))
attr_value = self.get_ring_setting(ethtool_output=output, setting='TX:', get_ring_max=get_max)
elif attr == 'fec':
output = utils.exec_command('%s --show-fec %s'%
(utils.ethtool_cmd, ifaceobj.name))
attr_value = self.get_fec_encoding(ethtool_output=output)
elif attr == 'gro':
if not self.feature_cache:
self.feature_cache = utils.exec_command('%s --show-features %s'%
(utils.ethtool_cmd, ifaceobj.name))
attr_value = self.get_offload_setting(ethtool_output=self.feature_cache, setting='generic-receive-offload')
elif attr == 'lro':
if not self.feature_cache:
self.feature_cache = utils.exec_command('%s --show-features %s'%
(utils.ethtool_cmd, ifaceobj.name))
attr_value = self.get_offload_setting(ethtool_output=self.feature_cache, setting='large-receive-offload')
elif attr == 'gso':
if not self.feature_cache:
self.feature_cache = utils.exec_command('%s --show-features %s'%
(utils.ethtool_cmd, ifaceobj.name))
attr_value = self.get_offload_setting(ethtool_output=self.feature_cache, setting='generic-segmentation-offload')
elif attr == 'tso':
if not self.feature_cache:
self.feature_cache = utils.exec_command('%s --show-features %s'%
(utils.ethtool_cmd, ifaceobj.name))
attr_value = self.get_offload_setting(ethtool_output=self.feature_cache, setting='tcp-segmentation-offload')
elif attr == 'ufo':
if not self.feature_cache:
self.feature_cache = utils.exec_command('%s --show-features %s'%
(utils.ethtool_cmd, ifaceobj.name))
attr_value = self.get_offload_setting(ethtool_output=self.feature_cache, setting='udp-fragmentation-offload')
elif attr == 'rx':
if not self.feature_cache:
self.feature_cache = utils.exec_command('%s --show-features %s'%
(utils.ethtool_cmd, ifaceobj.name))
attr_value = self.get_offload_setting(ethtool_output=self.feature_cache, setting='rx-checksumming')
elif attr == 'tx':
if not self.feature_cache:
self.feature_cache = utils.exec_command('%s --show-features %s'%
(utils.ethtool_cmd, ifaceobj.name))
attr_value = self.get_offload_setting(ethtool_output=self.feature_cache, setting='tx-checksumming')
else:
attr_value = self.io.read_file_oneline('/sys/class/net/%s/%s' % \
(ifaceobj.name, attr))
except Exception as e:
if not self.ethtool_ignore_errors:
# for nonexistent interfaces, we get an error (rc = 256 or 19200)
self.logger.debug('ethtool: problems calling ethtool or reading'
' /sys/class on iface %s for attr %s: %s' %
(ifaceobj.name, attr, str(e)))
return attr_value
def get_running_attr(self,attr='',ifaceobj=None):
return self.get_attr_value(attr=attr, ifaceobj=ifaceobj, get_max=False)
def get_max_attr(self,attr='',ifaceobj=None):
return self.get_attr_value(attr=attr, ifaceobj=ifaceobj, get_max=True)
def _query_running(self, ifaceobj, ifaceobj_getfunc=None):
"""
_query_running looks at the speed and duplex from /sys/class
and retreives autoneg from ethtool. We do not report autoneg
if speed is not available because this usually means the link is
down and the autoneg value is not reliable when the link is down.
"""
# do not bother showing swp ifaces that are not up for the speed
# duplex and autoneg are not reliable.
if not self.cache.link_is_up(ifaceobj.name):
return
for attr in ['speed', 'duplex', 'autoneg']:
default_val = policymanager.policymanager_api.get_iface_default(
module_name='ethtool',
ifname=ifaceobj.name,
attr='link-%s'%attr)
# do not continue if we have no defaults
# this avoids ethtool calls on virtual interfaces
if not default_val:
continue
running_attr = self.get_running_attr(attr, ifaceobj)
# Only show the link attributes if they differ from defaults
# to see the defaults, we should implement another flag (--with-defaults)
if default_val == running_attr:
continue
# do not proceed if speed = 0
if attr == 'speed' and running_attr and running_attr == '0':
return
if running_attr:
ifaceobj.update_config('link-%s'%attr, running_attr)
return
def _query(self, ifaceobj, **kwargs):
""" add default policy attributes supported by the module """
for attr in ['speed', 'duplex', 'autoneg', 'fec']:
if ifaceobj.get_attr_value_first('link-%s'%attr):
continue
default = policymanager.policymanager_api.get_iface_default(
module_name='ethtool',
ifname=ifaceobj.name,
attr='link-%s' %attr)
if not default:
continue
ifaceobj.update_config('link-%s' %attr, default)
_run_ops = {'pre-down' : _pre_down,
'pre-up' : _pre_up,
'query-checkcurr' : _query_check,
'query-running' : _query_running,
'query' : _query}
def get_ops(self):
""" returns list of ops supported by this module """
return list(self._run_ops.keys())
def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args):
""" run ethtool configuration on the interface object passed as
argument
Args:
**ifaceobj** (object): iface object
**operation** (str): any of 'post-up', 'query-checkcurr',
'query-running'
Kwargs:
**query_ifaceobj** (object): query check ifaceobject. This is only
valid when op is 'query-checkcurr'. It is an object same as
ifaceobj, but contains running attribute values and its config
status. The modules can use it to return queried running state
of interfaces. status is success if the running state is same
as user required state in ifaceobj. error otherwise.
"""
if (ifaceobj.link_kind or
ifaceobj.link_privflags & ifaceLinkPrivFlags.LOOPBACK):
return
op_handler = self._run_ops.get(operation)
if not op_handler:
return
if operation == 'query-checkcurr':
op_handler(self, ifaceobj, query_ifaceobj)
else:
op_handler(self, ifaceobj)