1
0
Fork 0
tuned/tuned/plugins/plugin_mounts.py
Jaroslav Škarvada 6def0e44cf
plugin_net: added support for hotplug and rename
This will not completely resolve RHEL-60906, because there will be
always race condition if the rename event happens before the instance
is fully initialized, but it should significantly improve the situation.

Drawback is that it can report in the logs device names that shouldn't
be matched by the plugin_net instance, i.e. it matches devices according
to their original names but accesses them with their new names. This
can be confusing. Also in the runtime API (e.g. for removal of the
device from the instance), the original device names have to be used.
For insertion of the device to the instance, new names have to be used.
This can be even more confusing.

Unfortunately, there probably isn't better alternative now, because
for correct operation we would have to handle rename by removal and
addition events. This would require rollback and retune steps which would
lead to very negative performance consequences. Also we would have to
cope with partially tuned devices if the rename event happens during
profile application. It's doable, but it would require big
architectural changes.

The PR also extends internal API, now the plugin instances receive
'instance' as a method parameter.

Also fixed some existing CodeQL warnings.

Resolves: RHEL-60906

Signed-off-by: Jaroslav Škarvada <jskarvad@redhat.com>
2025-01-16 18:40:41 +01:00

181 lines
5.4 KiB
Python

import tuned.consts as consts
from . import base
from .decorators import *
from subprocess import Popen,PIPE
import tuned.logs
from tuned.utils.commands import commands
import glob
log = tuned.logs.get()
cmd = commands()
class MountsPlugin(base.Plugin):
"""
Enables or disables barriers for mounts according to the value of the
boolean option [option]`disable_barriers`. The option additionally allows
the special value `force`, which disables barriers even on mountpoints with
write back caches.
NOTE: Only extended file systems (ext) are supported by this plug-in.
"""
@classmethod
def _generate_mountpoint_topology(cls):
"""
Gets the information about disks, partitions and mountpoints. Stores information about used filesystem and
creates a list of all underlying devices (in case of LVM) for each mountpoint.
"""
mountpoint_topology = {}
current_disk = None
stdout, stderr = Popen(["lsblk", "-rno", \
"TYPE,RM,KNAME,FSTYPE,MOUNTPOINT"], \
stdout=PIPE, stderr=PIPE, close_fds=True, \
universal_newlines = True).communicate()
for columns in [line.split() for line in stdout.splitlines()]:
if len(columns) < 3:
continue
device_type, device_removable, device_name = columns[:3]
filesystem = columns[3] if len(columns) > 3 else None
mountpoint = columns[4] if len(columns) > 4 else None
if device_type == "disk":
current_disk = device_name
continue
# skip removable, skip nonpartitions
if device_removable == "1" or device_type not in ["part", "lvm"]:
continue
if mountpoint is None or mountpoint == "[SWAP]":
continue
mountpoint_topology.setdefault(mountpoint, {"disks": set(), "device_name": device_name, "filesystem": filesystem})
mountpoint_topology[mountpoint]["disks"].add(current_disk)
cls._mountpoint_topology = mountpoint_topology
def _init_devices(self):
self._generate_mountpoint_topology()
self._devices_supported = True
self._free_devices = set(self._mountpoint_topology.keys())
self._assigned_devices = set()
@classmethod
def _get_config_options(self):
return {
"disable_barriers": None,
}
def _instance_init(self, instance):
instance._has_dynamic_tuning = False
instance._has_static_tuning = True
def _instance_cleanup(self, instance):
pass
def _get_device_cache_type(self, device):
"""
Get device cache type. This will work only for devices on SCSI kernel subsystem.
"""
source_filenames = glob.glob("/sys/block/%s/device/scsi_disk/*/cache_type" % device)
for source_filename in source_filenames:
return cmd.read_file(source_filename).strip()
return None
def _mountpoint_has_writeback_cache(self, mountpoint):
"""
Checks if the device has 'write back' cache. If the cache type cannot be determined, asume some other cache.
"""
for device in self._mountpoint_topology[mountpoint]["disks"]:
if self._get_device_cache_type(device) == "write back":
return True
return False
def _mountpoint_has_barriers(self, mountpoint):
"""
Checks if a given mountpoint is mounted with barriers enabled or disabled.
"""
with open("/proc/mounts") as mounts_file:
for line in mounts_file:
# device mountpoint filesystem options dump check
columns = line.split()
if columns[0][0] != "/":
continue
if columns[1] == mountpoint:
option_list = columns[3]
break
else:
return None
options = option_list.split(",")
for option in options:
(name, sep, value) = option.partition("=")
# nobarrier barrier=0
if name == "nobarrier" or (name == "barrier" and value == "0"):
return False
# barrier barrier=1
elif name == "barrier":
return True
else:
# default
return True
def _remount_partition(self, partition, options):
"""
Remounts partition.
"""
remount_command = ["/usr/bin/mount", partition, "-o", "remount,%s" % options]
cmd.execute(remount_command)
@command_custom("disable_barriers", per_device=True)
def _disable_barriers(self, start, value, mountpoint, verify, ignore_missing, instance):
storage_key = self._storage_key(
command_name = "disable_barriers",
device_name = mountpoint)
force = str(value).lower() == "force"
value = force or self._option_bool(value)
if start:
if not value:
return None
reject_reason = None
if not self._mountpoint_topology[mountpoint]["filesystem"].startswith("ext"):
reject_reason = "filesystem not supported"
elif not force and self._mountpoint_has_writeback_cache(mountpoint):
reject_reason = "device uses write back cache"
else:
original_value = self._mountpoint_has_barriers(mountpoint)
if original_value is None:
reject_reason = "unknown current setting"
elif original_value == False:
if verify:
log.info(consts.STR_VERIFY_PROFILE_OK % mountpoint)
return True
else:
reject_reason = "barriers already disabled"
elif verify:
log.error(consts.STR_VERIFY_PROFILE_FAIL % mountpoint)
return False
if reject_reason is not None:
log.info("not disabling barriers on '%s' (%s)" % (mountpoint, reject_reason))
return None
self._storage.set(storage_key, original_value)
log.info("disabling barriers on '%s'" % mountpoint)
self._remount_partition(mountpoint, "barrier=0")
else:
if verify:
return None
original_value = self._storage.get(storage_key)
if original_value is None:
return None
log.info("enabling barriers on '%s'" % mountpoint)
self._remount_partition(mountpoint, "barrier=1")
self._storage.unset(storage_key)
return None