Ethtool's tx-aggr-* parameters were not known to tuned. In addition to adding those, checking logic is changed to filter out unknown parameters supported by ethtool instead of simply failing to set any options. Resolves: RHEL-152675
796 lines
26 KiB
Python
796 lines
26 KiB
Python
import errno
|
|
from . import hotplug
|
|
from .decorators import *
|
|
import tuned.logs
|
|
from tuned.utils.nettool import ethcard
|
|
from tuned.utils.commands import commands
|
|
import os
|
|
import re
|
|
import pyudev
|
|
|
|
log = tuned.logs.get()
|
|
|
|
WOL_VALUES = "pumbagsd"
|
|
|
|
class NetTuningPlugin(hotplug.Plugin):
|
|
"""
|
|
Configures network driver, hardware and Netfilter settings.
|
|
Dynamic change of the interface speed according to the interface
|
|
utilization is also supported. The dynamic tuning is controlled by
|
|
the [option]`dynamic` and the global [option]`dynamic_tuning`
|
|
option in `tuned-main.conf`.
|
|
|
|
`wake_on_lan`:::
|
|
The [option]`wake_on_lan` option sets wake-on-lan to the specified
|
|
value as when using the `ethtool` utility.
|
|
+
|
|
.Set Wake-on-LAN for device eth0 on MagicPacket(TM)
|
|
====
|
|
----
|
|
[net]
|
|
devices=eth0
|
|
wake_on_lan=g
|
|
----
|
|
====
|
|
|
|
`coalesce`:::
|
|
The [option]`coalesce` option allows changing coalescing settings
|
|
for the specified network devices. The syntax is:
|
|
+
|
|
[subs="quotes"]
|
|
----
|
|
coalesce=__param1__ __value1__ __param2__ __value2__ ... __paramN__ __valueN__
|
|
----
|
|
+
|
|
Note that not all the coalescing parameters are supported by all
|
|
network cards. For the list of coalescing parameters of your network
|
|
device, use `ethtool -c device`.
|
|
+
|
|
.Setting coalescing parameters rx/tx-usecs for all network devices
|
|
====
|
|
----
|
|
[net]
|
|
coalesce=rx-usecs 3 tx-usecs 16
|
|
----
|
|
====
|
|
|
|
`features`:::
|
|
The [option]`features` option allows changing
|
|
the offload parameters and other features for the specified
|
|
network devices. To query the features of your network device,
|
|
use `ethtool -k device`. The syntax of the option is the same as
|
|
the [option]`coalesce` option.
|
|
+
|
|
.Turn off TX checksumming, generic segmentation and receive offload
|
|
====
|
|
----
|
|
[net]
|
|
features=tx off gso off gro off
|
|
----
|
|
====
|
|
|
|
`pause`:::
|
|
The [option]`pause` option allows changing the pause parameters for
|
|
the specified network devices. To query the pause parameters of your
|
|
network device, use `ethtool -a device`. The syntax of the option
|
|
is the same as the [option]`coalesce` option.
|
|
+
|
|
.Disable autonegotiation
|
|
====
|
|
----
|
|
[net]
|
|
pause=autoneg off
|
|
----
|
|
====
|
|
|
|
`ring`:::
|
|
The [option]`ring` option allows changing the rx/tx ring parameters
|
|
for the specified network devices. To query the ring parameters of your
|
|
network device, use `ethtool -g device`. The syntax of the option
|
|
is the same as the [option]`coalesce` option.
|
|
+
|
|
.Change the number of ring entries for the Rx/Tx rings to 1024/512 respectively
|
|
=====
|
|
-----
|
|
[net]
|
|
ring=rx 1024 tx 512
|
|
-----
|
|
=====
|
|
|
|
`channels`:::
|
|
The [option]`channels` option allows changing the numbers of channels
|
|
for the specified network device. A channel is an IRQ and the set
|
|
of queues that can trigger that IRQ. To query the channels parameters of your
|
|
network device, use `ethtool -l device`. The syntax of the option
|
|
is the same as the [option]`coalesce` option.
|
|
+
|
|
.Set the number of multi-purpose channels to 16
|
|
=====
|
|
-----
|
|
[net]
|
|
channels=combined 16
|
|
-----
|
|
=====
|
|
+
|
|
A network device either supports rx/tx or combined queue
|
|
mode. The [option]`channels` option automatically adjusts the
|
|
parameters based on the mode supported by the device as long as a
|
|
valid configuration is requested.
|
|
|
|
`nf_conntrack_hashsize`:::
|
|
The [option]`nf_conntrack_hashsize` option sets the size of the hash
|
|
table which stores lists of conntrack entries by writing to
|
|
`/sys/module/nf_conntrack/parameters/hashsize`.
|
|
+
|
|
.Adjust the size of the conntrack hash table
|
|
====
|
|
----
|
|
[net]
|
|
nf_conntrack_hashsize=131072
|
|
----
|
|
====
|
|
|
|
`txqueuelen`:::
|
|
The [option]`txqueuelen` option allows changing txqueuelen (the length
|
|
of the transmit queue). It uses `ip` utility that is in package iproute
|
|
recommended for TuneD, so the package needs to be installed for its correct
|
|
functionality. To query the txqueuelen parameters of your network device
|
|
use `ip link show` and the current value is shown after the qlen column.
|
|
+
|
|
.Adjust the length of the transmit queue
|
|
====
|
|
----
|
|
[net]
|
|
txqueuelen=5000
|
|
----
|
|
====
|
|
|
|
`mtu`:::
|
|
The [option]`mtu` option allows changing MTU (Maximum Transmission Unit).
|
|
It uses `ip` utility that is in package iproute recommended for TuneD, so
|
|
the package needs to be installed for its correct functionality. To query
|
|
the MTU parameters of your network device use `ip link show` and the
|
|
current value is shown after the MTU column.
|
|
+
|
|
.Adjust the size of the MTU
|
|
====
|
|
----
|
|
[net]
|
|
mtu=9000
|
|
----
|
|
====
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(NetTuningPlugin, self).__init__(*args, **kwargs)
|
|
self._load_smallest = 0.05
|
|
self._level_steps = 6
|
|
self._cmd = commands()
|
|
self._re_ip_link_show = {}
|
|
self._use_ip = True
|
|
self.re_not_virtual = re.compile('(?!.*/virtual/.*)')
|
|
# pyudev >= 0.21 check
|
|
if hasattr(pyudev.Device, "properties"):
|
|
self._get_device_property = self._get_device_property_1
|
|
else:
|
|
self._get_device_property = self._get_device_property_2
|
|
|
|
def _init_devices(self):
|
|
super(NetTuningPlugin, self)._init_devices()
|
|
self._devices_supported = True
|
|
self._free_devices = set()
|
|
self._assigned_devices = set()
|
|
|
|
for device in self._hardware_inventory.get_devices("net"):
|
|
if self._device_is_supported(device):
|
|
self._free_devices.add(device.sys_name)
|
|
|
|
log.debug("devices: %s" % str(self._free_devices));
|
|
|
|
def _get_device_objects(self, devices):
|
|
return [self._hardware_inventory.get_device("net", x) for x in devices]
|
|
|
|
def _device_is_supported(self, device):
|
|
return self.re_not_virtual.match(device.device_path)
|
|
|
|
def _hardware_events_init(self):
|
|
self._hardware_inventory.subscribe(self, "net", self._hardware_events_callback)
|
|
|
|
def _hardware_events_cleanup(self):
|
|
self._hardware_inventory.unsubscribe(self)
|
|
|
|
def _hardware_events_callback(self, event, device):
|
|
if self._device_is_supported(device):
|
|
super(NetTuningPlugin, self)._hardware_events_callback(event, device)
|
|
|
|
def _added_device_apply_tuning(self, instance, device_name):
|
|
if instance._load_monitor is not None:
|
|
instance._load_monitor.add_device(device_name)
|
|
super(NetTuningPlugin, self)._added_device_apply_tuning(instance, device_name)
|
|
|
|
def _removed_device_unapply_tuning(self, instance, device_name):
|
|
if instance._load_monitor is not None:
|
|
instance._load_monitor.remove_device(device_name)
|
|
super(NetTuningPlugin, self)._removed_device_unapply_tuning(instance, device_name)
|
|
|
|
# pyudev >= 0.21
|
|
def _get_device_property_1(self, pyudev_dev, prop):
|
|
return pyudev_dev.properties.get(prop)
|
|
|
|
# pyudev < 0.21
|
|
def _get_device_property_2(self, pyudev_dev, prop):
|
|
try:
|
|
p = pyudev_dev.__getitem__(prop)
|
|
except KeyError:
|
|
p = None
|
|
return p
|
|
|
|
# handle device rename
|
|
def _move_device(self, device_name):
|
|
d = self._hardware_inventory.get_device("net", device_name)
|
|
if d:
|
|
i = self._get_device_property(d, "IFINDEX")
|
|
if i:
|
|
for instance_name, instance in list(self._instances.items()):
|
|
device_name_old = instance._ifmap_orig.get(i)
|
|
if device_name_old:
|
|
log.debug("Rename device, ifindex: '%s', original name '%s', new name '%s'" % \
|
|
(i, device_name_old, device_name))
|
|
instance._ifnamemap[device_name_old] = device_name
|
|
break
|
|
|
|
def _get_curr_device_wrapper(self, instance):
|
|
# get new device name after rename
|
|
def _get_curr_device(device):
|
|
d = instance._ifnamemap.get(device)
|
|
return d if d else device
|
|
|
|
return _get_curr_device
|
|
|
|
def _instance_init(self, instance):
|
|
instance._has_static_tuning = True
|
|
instance._load_monitor = None
|
|
instance._idle = None
|
|
instance._stats = None
|
|
instance._ifnamemap = {}
|
|
instance._get_curr_device = self._get_curr_device_wrapper(instance)
|
|
instance._has_dynamic_tuning = self._option_bool(instance.options["dynamic"])
|
|
|
|
def assign_free_devices(self, instance):
|
|
super(NetTuningPlugin, self).assign_free_devices(instance)
|
|
instance._ifmap_orig = {}
|
|
devices = instance.assigned_devices
|
|
for device in devices:
|
|
d = self._hardware_inventory.get_device("net", device)
|
|
if d:
|
|
i = self._get_device_property(d, "IFINDEX")
|
|
if i:
|
|
instance._ifmap_orig[i] = device
|
|
|
|
def _instance_cleanup(self, instance):
|
|
if instance._load_monitor is not None:
|
|
self._monitors_repository.delete(instance._load_monitor)
|
|
instance._load_monitor = None
|
|
|
|
def _instance_init_dynamic(self, instance):
|
|
super(NetTuningPlugin, self)._instance_init_dynamic(instance)
|
|
instance._idle = {}
|
|
instance._stats = {}
|
|
instance._load_monitor = self._monitors_repository.create("net", instance.assigned_devices)
|
|
instance._load_monitor._set_dev_map(instance._get_curr_device)
|
|
|
|
def _instance_apply_dynamic(self, instance, device):
|
|
self._instance_update_dynamic(instance, device)
|
|
|
|
def _instance_update_dynamic(self, instance, device):
|
|
load = [int(value) for value in instance._load_monitor.get_device_load(device)]
|
|
if load is None:
|
|
return
|
|
|
|
if not device in instance._stats:
|
|
self._init_stats_and_idle(instance, device)
|
|
self._update_stats(instance, device, load)
|
|
self._update_idle(instance, device)
|
|
|
|
stats = instance._stats[device]
|
|
idle = instance._idle[device]
|
|
|
|
if idle["level"] == 0 and idle["read"] >= self._level_steps and idle["write"] >= self._level_steps:
|
|
idle["level"] = 1
|
|
log.info("%s: setting 100Mbps" % device)
|
|
ethcard(device).set_speed(100)
|
|
elif idle["level"] == 1 and (idle["read"] == 0 or idle["write"] == 0):
|
|
idle["level"] = 0
|
|
log.info("%s: setting max speed" % device)
|
|
ethcard(device).set_max_speed()
|
|
|
|
log.debug("%s load: read %0.2f, write %0.2f" % (device, stats["read"], stats["write"]))
|
|
log.debug("%s idle: read %d, write %d, level %d" % (device, idle["read"], idle["write"], idle["level"]))
|
|
|
|
@classmethod
|
|
def _get_config_options_coalesce(cls):
|
|
return {
|
|
"adaptive-rx": None,
|
|
"adaptive-tx": None,
|
|
"cqe-mode-rx": None,
|
|
"rx-usecs": None,
|
|
"rx-frames": None,
|
|
"rx-usecs-irq": None,
|
|
"rx-frames-irq": None,
|
|
"tx-usecs": None,
|
|
"tx-frames": None,
|
|
"tx-usecs-irq": None,
|
|
"tx-frames-irq": None,
|
|
"stats-block-usecs": None,
|
|
"pkt-rate-low": None,
|
|
"rx-usecs-low": None,
|
|
"rx-frames-low": None,
|
|
"tx-usecs-low": None,
|
|
"tx-frames-low": None,
|
|
"pkt-rate-high": None,
|
|
"rx-usecs-high": None,
|
|
"rx-frames-high": None,
|
|
"tx-usecs-high": None,
|
|
"tx-frames-high": None,
|
|
"sample-interval": None,
|
|
"tx-aggr-max-bytes": None,
|
|
"tx-aggr-max-frames": None,
|
|
"tx-aggr-time-usecs": None
|
|
}
|
|
|
|
@classmethod
|
|
def _get_config_options_pause(cls):
|
|
return { "autoneg": None,
|
|
"rx": None,
|
|
"tx": None }
|
|
|
|
@classmethod
|
|
def _get_config_options_ring(cls):
|
|
return { "rx": None,
|
|
"rx-mini": None,
|
|
"rx-jumbo": None,
|
|
"tx": None }
|
|
|
|
@classmethod
|
|
def _get_config_options_channels(cls):
|
|
return { "rx": None,
|
|
"tx": None,
|
|
"other": None,
|
|
"combined": None }
|
|
|
|
@classmethod
|
|
def _get_config_options(cls):
|
|
return {
|
|
"dynamic": True,
|
|
"wake_on_lan": None,
|
|
"nf_conntrack_hashsize": None,
|
|
"features": None,
|
|
"coalesce": None,
|
|
"pause": None,
|
|
"ring": None,
|
|
"channels": None,
|
|
"txqueuelen": None,
|
|
"mtu": None,
|
|
}
|
|
|
|
def _init_stats_and_idle(self, instance, device):
|
|
max_speed = self._calc_speed(ethcard(instance._get_curr_device(device)).get_max_speed())
|
|
instance._stats[device] = { "new": 4 * [0], "max": 2 * [max_speed, 1] }
|
|
instance._idle[device] = { "level": 0, "read": 0, "write": 0 }
|
|
|
|
def _update_stats(self, instance, device, new_load):
|
|
# put new to old
|
|
instance._stats[device]["old"] = old_load = instance._stats[device]["new"]
|
|
instance._stats[device]["new"] = new_load
|
|
|
|
# load difference
|
|
diff = [new_old[0] - new_old[1] for new_old in zip(new_load, old_load)]
|
|
instance._stats[device]["diff"] = diff
|
|
|
|
# adapt maximum expected load if the difference is higer
|
|
old_max_load = instance._stats[device]["max"]
|
|
max_load = [max(pair) for pair in zip(old_max_load, diff)]
|
|
instance._stats[device]["max"] = max_load
|
|
|
|
# read/write ratio
|
|
instance._stats[device]["read"] = float(diff[0]) / float(max_load[0])
|
|
instance._stats[device]["write"] = float(diff[2]) / float(max_load[2])
|
|
|
|
def _update_idle(self, instance, device):
|
|
# increase counter if there is no load, otherwise reset the counter
|
|
for operation in ["read", "write"]:
|
|
if instance._stats[device][operation] < self._load_smallest:
|
|
instance._idle[device][operation] += 1
|
|
else:
|
|
instance._idle[device][operation] = 0
|
|
|
|
def _instance_unapply_dynamic(self, instance, device):
|
|
if device in instance._idle and instance._idle[device]["level"] > 0:
|
|
instance._idle[device]["level"] = 0
|
|
log.info("%s: setting max speed" % device)
|
|
ethcard(instance._get_curr_device(device)).set_max_speed()
|
|
|
|
def _calc_speed(self, speed):
|
|
# 0.6 is just a magical constant (empirical value): Typical workload on netcard won't exceed
|
|
# that and if it does, then the code is smart enough to adapt it.
|
|
# 1024 * 1024 as for MB -> B
|
|
# speed / 8 Mb -> MB
|
|
return (int) (0.6 * 1024 * 1024 * speed / 8)
|
|
|
|
# parse features/coalesce config parameters (those defined in profile configuration)
|
|
# context is for error message
|
|
def _parse_config_parameters(self, value, context):
|
|
# expand config variables
|
|
value = self._variables.expand(value)
|
|
# split supporting various dellimeters
|
|
v = str(re.sub(r"(:\s*)|(\s+)|(\s*;\s*)|(\s*,\s*)", " ", value)).split()
|
|
lv = len(v)
|
|
if lv % 2 != 0:
|
|
log.error("invalid %s parameter: '%s'" % (context, str(value)))
|
|
return None
|
|
if lv == 0:
|
|
return dict()
|
|
# convert flat list to dict
|
|
return dict(list(zip(v[::2], v[1::2])))
|
|
|
|
# parse features/coalesce device parameters (those returned by ethtool)
|
|
def _parse_device_parameters(self, value):
|
|
# substitute "Adaptive RX: val1 TX: val2" to 'adaptive-rx: val1' and
|
|
# 'adaptive-tx: val2' and workaround for ethtool inconsistencies
|
|
# (rhbz#1225375)
|
|
value = self._cmd.multiple_re_replace({
|
|
"Adaptive RX:": "adaptive-rx:",
|
|
"\\s+TX:": "\nadaptive-tx:",
|
|
"CQE mode RX:": "cqe-mode-rx:",
|
|
"rx-frame-low:": "rx-frames-low:",
|
|
"rx-frame-high:": "rx-frames-high:",
|
|
"tx-frame-low:": "tx-frames-low:",
|
|
"tx-frame-high:": "tx-frames-high:",
|
|
"large-receive-offload:": "lro:",
|
|
"rx-checksumming:": "rx:",
|
|
"tx-checksumming:": "tx:",
|
|
"scatter-gather:": "sg:",
|
|
"tcp-segmentation-offload:": "tso:",
|
|
"udp-fragmentation-offload:": "ufo:",
|
|
"generic-segmentation-offload:": "gso:",
|
|
"generic-receive-offload:": "gro:",
|
|
"rx-vlan-offload:": "rxvlan:",
|
|
"tx-vlan-offload:": "txvlan:",
|
|
"ntuple-filters:": "ntuple:",
|
|
"receive-hashing:": "rxhash:",
|
|
}, value)
|
|
# remove empty lines, remove fixed parameters (those with "[fixed]")
|
|
vl = [v for v in value.split('\n') if len(str(v)) > 0 and not re.search(r"\[fixed\]$", str(v))]
|
|
if len(vl) < 2:
|
|
return None
|
|
# skip first line (device name), split to key/value,
|
|
# remove pairs which are not key/value
|
|
return dict([u for u in [re.split(r":\s*", str(v)) for v in vl[1:]] if len(u) == 2])
|
|
|
|
@classmethod
|
|
def _nf_conntrack_hashsize_path(self):
|
|
return "/sys/module/nf_conntrack/parameters/hashsize"
|
|
|
|
@command_set("wake_on_lan", per_device=True)
|
|
def _set_wake_on_lan(self, value, device, instance, sim, remove):
|
|
if value is None:
|
|
return None
|
|
|
|
# see man ethtool for possible wol values, 0 added as an alias for 'd'
|
|
value = re.sub(r"0", "d", str(value));
|
|
if not re.match(r"^[" + WOL_VALUES + r"]+$", value):
|
|
log.warning("Incorrect 'wake_on_lan' value.")
|
|
return None
|
|
|
|
if not sim:
|
|
self._cmd.execute(["ethtool", "-s", instance._get_curr_device(device), "wol", value])
|
|
return value
|
|
|
|
@command_get("wake_on_lan")
|
|
def _get_wake_on_lan(self, device, instance, ignore_missing=False):
|
|
value = None
|
|
try:
|
|
m = re.match(r".*Wake-on:\s*([" + WOL_VALUES + "]+).*",
|
|
self._cmd.execute(["ethtool", instance._get_curr_device(device)])[1], re.S)
|
|
if m:
|
|
value = m.group(1)
|
|
except IOError:
|
|
pass
|
|
return value
|
|
|
|
@command_set("nf_conntrack_hashsize")
|
|
def _set_nf_conntrack_hashsize(self, value, instance, sim, remove):
|
|
if value is None:
|
|
return None
|
|
|
|
hashsize = int(value)
|
|
if hashsize >= 0:
|
|
if not sim:
|
|
self._cmd.write_to_file(self._nf_conntrack_hashsize_path(), hashsize, \
|
|
no_error = [errno.ENOENT] if remove else False)
|
|
return hashsize
|
|
else:
|
|
return None
|
|
|
|
@command_get("nf_conntrack_hashsize")
|
|
def _get_nf_conntrack_hashsize(self, instance):
|
|
value = self._cmd.read_file(self._nf_conntrack_hashsize_path())
|
|
if len(value) > 0:
|
|
return int(value)
|
|
return None
|
|
|
|
def _call_ip_link(self, args=[]):
|
|
if not self._use_ip:
|
|
return None
|
|
args = ["ip", "link"] + args
|
|
(rc, out, err_msg) = self._cmd.execute(args, no_errors=[errno.ENOENT], return_err=True)
|
|
if rc == -errno.ENOENT:
|
|
log.warning("ip command not found, ignoring for other devices")
|
|
self._use_ip = False
|
|
return None
|
|
elif rc:
|
|
log.info("Problem calling ip command")
|
|
log.debug("(rc: %s, msg: '%s')" % (rc, err_msg))
|
|
return None
|
|
return out
|
|
|
|
def _ip_link_show(self, device=None):
|
|
args = ["show"]
|
|
if device:
|
|
args.append(device)
|
|
return self._call_ip_link(args)
|
|
|
|
@command_set("txqueuelen", per_device=True)
|
|
def _set_txqueuelen(self, value, device, instance, sim, remove):
|
|
if value is None:
|
|
return None
|
|
try:
|
|
int(value)
|
|
except ValueError:
|
|
log.warning("txqueuelen value '%s' is not integer" % value)
|
|
return None
|
|
if not sim:
|
|
# there is inconsistency in "ip", where "txqueuelen" is set as it, but is shown as "qlen"
|
|
res = self._call_ip_link(["set", "dev", instance._get_curr_device(device), "txqueuelen", value])
|
|
if res is None:
|
|
log.warning("Cannot set txqueuelen for device '%s'" % instance._get_curr_device(device))
|
|
return None
|
|
return value
|
|
|
|
def _get_re_ip_link_show(self, arg):
|
|
"""
|
|
Return regex for int arg value from "ip link show" command
|
|
"""
|
|
if arg not in self._re_ip_link_show:
|
|
self._re_ip_link_show[arg] = re.compile(r".*\s+%s\s+(\d+)" % arg)
|
|
return self._re_ip_link_show[arg]
|
|
|
|
@command_get("txqueuelen")
|
|
def _get_txqueuelen(self, device, instance, ignore_missing=False):
|
|
out = self._ip_link_show(instance._get_curr_device(device))
|
|
if out is None:
|
|
if not ignore_missing:
|
|
log.info("Cannot get 'ip link show' result for txqueuelen value for device '%s'" % \
|
|
instance._get_curr_device(device))
|
|
return None
|
|
res = self._get_re_ip_link_show("qlen").search(out)
|
|
if res is None:
|
|
# We can theoretically get device without qlen (http://linux-ip.net/gl/ip-cref/ip-cref-node17.html)
|
|
if not ignore_missing:
|
|
log.info("Cannot get txqueuelen value from 'ip link show' result for device '%s'" % \
|
|
instance._get_curr_device(device))
|
|
return None
|
|
return res.group(1)
|
|
|
|
@command_set("mtu", per_device=True)
|
|
def _set_mtu(self, value, device, instance, sim, remove):
|
|
if value is None:
|
|
return None
|
|
try:
|
|
int(value)
|
|
except ValueError:
|
|
log.warning("mtu value '%s' is not integer" % value)
|
|
return None
|
|
if not sim:
|
|
res = self._call_ip_link(["set", "dev", instance._get_curr_device(device), "mtu", value])
|
|
if res is None:
|
|
log.warning("Cannot set mtu for device '%s'" % instance._get_curr_device(device))
|
|
return None
|
|
return value
|
|
|
|
@command_get("mtu")
|
|
def _get_mtu(self, device, instance, ignore_missing=False):
|
|
out = self._ip_link_show(instance._get_curr_device(device))
|
|
if out is None:
|
|
if not ignore_missing:
|
|
log.info("Cannot get 'ip link show' result for mtu value for device '%s'" % \
|
|
instance._get_curr_device(device))
|
|
return None
|
|
res = self._get_re_ip_link_show("mtu").search(out)
|
|
if res is None:
|
|
# mtu value should be always present, but it's better to have a test
|
|
if not ignore_missing:
|
|
log.info("Cannot get mtu value from 'ip link show' result for device '%s'" % \
|
|
instance._get_curr_device(device))
|
|
return None
|
|
return res.group(1)
|
|
|
|
# d is dict: {parameter: value}
|
|
def _check_parameters(self, context, d):
|
|
if context == "features":
|
|
return d
|
|
params = set(d.keys())
|
|
supported_getter = { "coalesce": self._get_config_options_coalesce, \
|
|
"pause": self._get_config_options_pause, \
|
|
"ring": self._get_config_options_ring, \
|
|
"channels": self._get_config_options_channels }
|
|
supported = set(supported_getter[context]().keys())
|
|
if not params.issubset(supported):
|
|
log.warning("ignoring unknown %s parameter(s): %s" % (context, str(params - supported)))
|
|
return {k: v for k, v in d.items() if k in supported}
|
|
|
|
# parse output of ethtool -a
|
|
def _parse_pause_parameters(self, s):
|
|
s = self._cmd.multiple_re_replace(\
|
|
{"Autonegotiate": "autoneg",
|
|
"RX": "rx",
|
|
"TX": "tx"}, s)
|
|
l = s.split("\n")[1:]
|
|
l = [x for x in l if x != '' and not re.search(r"\[fixed\]", x)]
|
|
return dict([x for x in [re.split(r":\s*", x) for x in l] if len(x) == 2])
|
|
|
|
# parse output of ethtool -g
|
|
def _parse_ring_parameters(self, s):
|
|
a = re.split(r"^Current hardware settings:$", s, flags=re.MULTILINE)
|
|
s = a[1]
|
|
s = self._cmd.multiple_re_replace(\
|
|
{"RX": "rx",
|
|
"RX Mini": "rx-mini",
|
|
"RX Jumbo": "rx-jumbo",
|
|
"TX": "tx"}, s)
|
|
l = s.split("\n")
|
|
l = [x for x in l if x != '']
|
|
l = [x for x in [re.split(r":\s*", x) for x in l] if len(x) == 2]
|
|
return dict(l)
|
|
|
|
# parse output of ethtool -l
|
|
def _parse_channels_parameters(self, s):
|
|
a = re.split(r"^Current hardware settings:$", s, flags=re.MULTILINE)
|
|
s = a[1]
|
|
s = self._cmd.multiple_re_replace(\
|
|
{"RX": "rx",
|
|
"TX": "tx",
|
|
"Other": "other",
|
|
"Combined": "combined"}, s)
|
|
l = s.split("\n")
|
|
l = [x for x in l if x != '']
|
|
l = [x for x in [re.split(r":\s*", x) for x in l] if len(x) == 2]
|
|
return dict(l)
|
|
|
|
def _replace_channels_parameters(self, context, params_list, dev_params):
|
|
mod_params_list = []
|
|
if "combined" in params_list:
|
|
mod_params_list.extend(["rx", params_list[1], "tx", params_list[1]])
|
|
else:
|
|
cnt = str(max(int(params_list[1]), int(params_list[3])))
|
|
mod_params_list.extend(["combined", cnt])
|
|
return dict(list(zip(mod_params_list[::2], mod_params_list[1::2])))
|
|
|
|
def _check_device_support(self, instance, context, parameters, device, dev_params):
|
|
"""Filter unsupported parameters and log warnings about it
|
|
|
|
Positional parameters:
|
|
instance -- instance calling it
|
|
context -- context of change
|
|
parameters -- parameters to change
|
|
device -- name of device on which should be parameters set
|
|
dev_params -- dictionary of currently known parameters of device
|
|
"""
|
|
supported_parameters = set(dev_params.keys())
|
|
parameters_to_change = set(parameters.keys())
|
|
# if parameters_to_change contains unsupported parameter(s) then remove
|
|
# it/them
|
|
unsupported_parameters = (parameters_to_change
|
|
- supported_parameters)
|
|
for param in unsupported_parameters:
|
|
log.warning("%s parameter %s is not supported by device %s" % (
|
|
context,
|
|
param,
|
|
instance._get_curr_device(device),
|
|
))
|
|
parameters.pop(param, None)
|
|
|
|
def _get_device_parameters(self, instance, context, device):
|
|
context2opt = { "coalesce": "-c", "features": "-k", "pause": "-a", "ring": "-g", \
|
|
"channels": "-l"}
|
|
opt = context2opt[context]
|
|
ret, value = self._cmd.execute(["ethtool", opt, instance._get_curr_device(device)])
|
|
if ret != 0 or len(value) == 0:
|
|
return None
|
|
context2parser = { "coalesce": self._parse_device_parameters, \
|
|
"features": self._parse_device_parameters, \
|
|
"pause": self._parse_pause_parameters, \
|
|
"ring": self._parse_ring_parameters, \
|
|
"channels": self._parse_channels_parameters }
|
|
parser = context2parser[context]
|
|
d = parser(value)
|
|
return self._check_parameters(context, d)
|
|
|
|
def _set_device_parameters(self, instance, context, value, device, sim,
|
|
dev_params = None):
|
|
if value is None or len(value) == 0:
|
|
return None
|
|
d = self._check_parameters(context, self._parse_config_parameters(value, context))
|
|
# check if device supports parameters and filter out unsupported ones
|
|
if dev_params:
|
|
self._check_device_support(instance, context, d, device, dev_params)
|
|
# replace the channel parameters based on the device support
|
|
if context == "channels" and str(dev_params[next(iter(d))]) in ["n/a", "0"]:
|
|
d = self._replace_channels_parameters(context, self._cmd.dict2list(d), dev_params)
|
|
|
|
if not sim and len(d) != 0:
|
|
log.debug("setting %s: %s" % (context, str(d)))
|
|
context2opt = { "coalesce": "-C", "features": "-K", "pause": "-A", "ring": "-G", \
|
|
"channels": "-L"}
|
|
opt = context2opt[context]
|
|
# ignore ethtool return code 80, it means parameter is already set
|
|
self._cmd.execute(["ethtool", opt, instance._get_curr_device(device)] + \
|
|
self._cmd.dict2list(d), no_errors = [80])
|
|
return d
|
|
|
|
def _custom_parameters(self, context, start, value, device, verify, instance):
|
|
storage_key = self._storage_key(
|
|
command_name = context,
|
|
device_name = device)
|
|
if start:
|
|
params_current = self._get_device_parameters(instance, context, device)
|
|
if params_current is None or len(params_current) == 0:
|
|
return False
|
|
params_set = self._set_device_parameters(instance, context,
|
|
value, device, verify,
|
|
dev_params = params_current)
|
|
# if none of parameters passed checks then the command completely
|
|
# failed
|
|
if params_set is None or len(params_set) == 0:
|
|
return False
|
|
relevant_params_current = [(param, value) for param, value
|
|
in params_current.items()
|
|
if param in params_set]
|
|
relevant_params_current = dict(relevant_params_current)
|
|
if verify:
|
|
res = (self._cmd.dict2list(params_set)
|
|
== self._cmd.dict2list(relevant_params_current))
|
|
self._log_verification_result(context, res,
|
|
params_set,
|
|
relevant_params_current,
|
|
device = instance._get_curr_device(device))
|
|
return res
|
|
# saved are only those parameters which passed checks
|
|
self._storage.set(storage_key, " ".join(
|
|
self._cmd.dict2list(relevant_params_current)))
|
|
else:
|
|
original_value = self._storage.get(storage_key)
|
|
# in storage are only those parameters which were already tested
|
|
# so skip check for supported parameters
|
|
self._set_device_parameters(instance, context, original_value, device, False)
|
|
return None
|
|
|
|
@command_custom("features", per_device = True)
|
|
def _features(self, start, value, device, verify, ignore_missing, instance):
|
|
return self._custom_parameters("features", start, value, device, verify, instance)
|
|
|
|
@command_custom("coalesce", per_device = True)
|
|
def _coalesce(self, start, value, device, verify, ignore_missing, instance):
|
|
return self._custom_parameters("coalesce", start, value, device, verify, instance)
|
|
|
|
@command_custom("pause", per_device = True)
|
|
def _pause(self, start, value, device, verify, ignore_missing, instance):
|
|
return self._custom_parameters("pause", start, value, device, verify, instance)
|
|
|
|
@command_custom("ring", per_device = True)
|
|
def _ring(self, start, value, device, verify, ignore_missing, instance):
|
|
return self._custom_parameters("ring", start, value, device, verify, instance)
|
|
|
|
@command_custom("channels", per_device = True)
|
|
def _channels(self, start, value, device, verify, ignore_missing, instance):
|
|
return self._custom_parameters("channels", start, value, device, verify, instance)
|