1
0
Fork 0
tuned/tuned/plugins/plugin_net.py
Fothsid f5cfd2a722 net: add more coalescing options, filter out unsupported ones
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
2026-05-11 11:58:31 +02:00

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)