- rteval: Implement initial dmidecode support
- rteval: Fix -aNone being passed to cyclictest
This commit is contained in:
parent
ac40a10aae
commit
2ecfb501eb
45
SOURCES/rteval-Fix-aNone-being-passed-to-cyclictest.patch
Normal file
45
SOURCES/rteval-Fix-aNone-being-passed-to-cyclictest.patch
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
From 17a39863e5f89fbaf51dc158bc7e27f46676d46d Mon Sep 17 00:00:00 2001
|
||||||
|
From: Tomas Glozar <tglozar@redhat.com>
|
||||||
|
Date: Tue, 11 Jun 2024 16:45:53 +0200
|
||||||
|
Subject: [PATCH] rteval: Fix -aNone being passed to cyclictest
|
||||||
|
|
||||||
|
When rteval is called via the command line, cpulists for both
|
||||||
|
measurements and loads default to an empty string and are further
|
||||||
|
processed by parse_cpulist_from_config. However, this is not true when
|
||||||
|
rteval is used as a module: in that case, neither the default
|
||||||
|
command-line value is used nor parse_cpulist_from_config is run, leading
|
||||||
|
to None being set to the config property of measurements which is
|
||||||
|
explicitely passed down to the corresponding cyclictest config property.
|
||||||
|
|
||||||
|
After 64ce7848dfab ("rteval: Add relative cpulists for measurements"),
|
||||||
|
where the check for None was removed from the cyclictest module, rteval
|
||||||
|
passes "-aNone" to cyclictest when being used as a module.
|
||||||
|
|
||||||
|
Call parse_cpulist_from_config with an empty string to get the default
|
||||||
|
cpulist to pass to cyclictest module if cpulist is empty in
|
||||||
|
the measurement config.
|
||||||
|
|
||||||
|
Fixes: 64ce7848dfab ("rteval: Add relative cpulists for measurements")
|
||||||
|
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
|
||||||
|
Signed-off-by: John Kacur <jkacur@redhat.com>
|
||||||
|
---
|
||||||
|
rteval/modules/measurement/__init__.py | 3 +++
|
||||||
|
1 file changed, 3 insertions(+)
|
||||||
|
|
||||||
|
diff --git a/rteval/modules/measurement/__init__.py b/rteval/modules/measurement/__init__.py
|
||||||
|
index 11bd7b0fce69..43c0fda30ce1 100644
|
||||||
|
--- a/rteval/modules/measurement/__init__.py
|
||||||
|
+++ b/rteval/modules/measurement/__init__.py
|
||||||
|
@@ -148,6 +148,9 @@ measurement profiles, based on their characteristics"""
|
||||||
|
modcfg = self.__cfg.GetSection("measurement")
|
||||||
|
cpulist = modcfg.cpulist
|
||||||
|
run_on_isolcpus = modcfg.run_on_isolcpus
|
||||||
|
+ if cpulist is None:
|
||||||
|
+ # Get default cpulist value
|
||||||
|
+ cpulist = cpulist_utils.collapse_cpulist(parse_cpulist_from_config("", run_on_isolcpus))
|
||||||
|
|
||||||
|
for (modname, modtype) in modcfg:
|
||||||
|
if isinstance(modtype, str) and modtype.lower() == 'module': # Only 'module' will be supported (ds)
|
||||||
|
--
|
||||||
|
2.45.2
|
||||||
|
|
298
SOURCES/rteval-Implement-initial-dmidecode-support.patch
Normal file
298
SOURCES/rteval-Implement-initial-dmidecode-support.patch
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
From 43c45ba7dae488c16beb359834b3a71ebf6068d6 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Tomas Glozar <tglozar@redhat.com>
|
||||||
|
Date: Mon, 4 Mar 2024 11:26:03 +0100
|
||||||
|
Subject: rteval: Implement initial dmidecode support
|
||||||
|
|
||||||
|
Previously rteval used python-dmidecode to gather DMI data from a
|
||||||
|
system. Since python-dmidecode is without a maintainer, its support was
|
||||||
|
removed in d142f0d2 ("rteval: Disable use of python-dmidecode").
|
||||||
|
|
||||||
|
Add get_dmidecode_xml() function into rteval/sysinfo/dmi.py that does
|
||||||
|
simple parsing of dmidecode command-line tool output without any
|
||||||
|
structure changes and include it into the rteval report.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- ProcessWarnings() in rteval.sysinfo.dmi was reworked into a class
|
||||||
|
method of DMIinfo and to use the class's __log field as logger. It
|
||||||
|
now also does not ignore warnings that appear when running rteval as
|
||||||
|
non-root, since that is no longer supported. Additionally,
|
||||||
|
a duplicate call in rteval-cmd was removed.
|
||||||
|
- rteval/rteval_dmi.xsl XSLT template was left untouched and is
|
||||||
|
currectly not used. In a future commit, it is expected to be rewritten
|
||||||
|
to transform the XML format outputted by get_dmidecode_xml() into the
|
||||||
|
same format that was used with python-dmidecode.
|
||||||
|
|
||||||
|
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
|
||||||
|
Signed-off-by: John Kacur <jkacur@redhat.com>
|
||||||
|
---
|
||||||
|
rteval-cmd | 2 -
|
||||||
|
rteval/sysinfo/__init__.py | 2 +-
|
||||||
|
rteval/sysinfo/dmi.py | 178 +++++++++++++++++++++++++++++----------------
|
||||||
|
3 files changed, 118 insertions(+), 64 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/rteval-cmd b/rteval-cmd
|
||||||
|
index a5e8746..018a414 100755
|
||||||
|
--- a/rteval-cmd
|
||||||
|
+++ b/rteval-cmd
|
||||||
|
@@ -268,8 +268,6 @@ if __name__ == '__main__':
|
||||||
|
| (rtevcfg.debugging and Log.DEBUG)
|
||||||
|
logger.SetLogVerbosity(loglev)
|
||||||
|
|
||||||
|
- dmi.ProcessWarnings(logger=logger)
|
||||||
|
-
|
||||||
|
# Load modules
|
||||||
|
loadmods = LoadModules(config, logger=logger)
|
||||||
|
measuremods = MeasurementModules(config, logger=logger)
|
||||||
|
diff --git a/rteval/sysinfo/__init__.py b/rteval/sysinfo/__init__.py
|
||||||
|
index d3f9efb..09af52e 100644
|
||||||
|
--- a/rteval/sysinfo/__init__.py
|
||||||
|
+++ b/rteval/sysinfo/__init__.py
|
||||||
|
@@ -30,7 +30,7 @@ class SystemInfo(KernelInfo, SystemServices, dmi.DMIinfo, CPUtopology,
|
||||||
|
NetworkInfo.__init__(self, logger=logger)
|
||||||
|
|
||||||
|
# Parse initial DMI decoding errors
|
||||||
|
- dmi.ProcessWarnings(logger=logger)
|
||||||
|
+ self.ProcessWarnings()
|
||||||
|
|
||||||
|
# Parse CPU info
|
||||||
|
CPUtopology._parse(self)
|
||||||
|
diff --git a/rteval/sysinfo/dmi.py b/rteval/sysinfo/dmi.py
|
||||||
|
index c01a0ee..f1aab9f 100644
|
||||||
|
--- a/rteval/sysinfo/dmi.py
|
||||||
|
+++ b/rteval/sysinfo/dmi.py
|
||||||
|
@@ -3,6 +3,7 @@
|
||||||
|
# Copyright 2009 - 2013 Clark Williams <williams@redhat.com>
|
||||||
|
# Copyright 2009 - 2013 David Sommerseth <davids@redhat.com>
|
||||||
|
# Copyright 2022 John Kacur <jkacur@redhat.com>
|
||||||
|
+# Copyright 2024 Tomas Glozar <tglozar@redhat.com>
|
||||||
|
#
|
||||||
|
""" dmi.py class to wrap DMI Table Information """
|
||||||
|
|
||||||
|
@@ -10,65 +11,125 @@ import sys
|
||||||
|
import os
|
||||||
|
import libxml2
|
||||||
|
import lxml.etree
|
||||||
|
+import shutil
|
||||||
|
+import re
|
||||||
|
+from subprocess import Popen, PIPE, SubprocessError
|
||||||
|
from rteval.Log import Log
|
||||||
|
from rteval import xmlout
|
||||||
|
from rteval import rtevalConfig
|
||||||
|
|
||||||
|
-try:
|
||||||
|
- # import dmidecode
|
||||||
|
- dmidecode_avail = False
|
||||||
|
-except ModuleNotFoundError:
|
||||||
|
- dmidecode_avail = False
|
||||||
|
-
|
||||||
|
-def set_dmidecode_avail(val):
|
||||||
|
- """ Used to set global variable dmidecode_avail from a function """
|
||||||
|
- global dmidecode_avail
|
||||||
|
- dmidecode_avail = val
|
||||||
|
-
|
||||||
|
-def ProcessWarnings(logger=None):
|
||||||
|
- """ Process Warnings from dmidecode """
|
||||||
|
-
|
||||||
|
- if not dmidecode_avail:
|
||||||
|
- return
|
||||||
|
-
|
||||||
|
- if not hasattr(dmidecode, 'get_warnings'):
|
||||||
|
- return
|
||||||
|
-
|
||||||
|
- warnings = dmidecode.get_warnings()
|
||||||
|
- if warnings is None:
|
||||||
|
- return
|
||||||
|
-
|
||||||
|
- ignore1 = '/dev/mem: Permission denied'
|
||||||
|
- ignore2 = 'No SMBIOS nor DMI entry point found, sorry.'
|
||||||
|
- ignore3 = 'Failed to open memory buffer (/dev/mem): Permission denied'
|
||||||
|
- ignore = (ignore1, ignore2, ignore3)
|
||||||
|
- for warnline in warnings.split('\n'):
|
||||||
|
- # Ignore these warnings, as they are "valid" if not running as root
|
||||||
|
- if warnline in ignore:
|
||||||
|
- continue
|
||||||
|
|
||||||
|
- # All other warnings will be printed
|
||||||
|
- if len(warnline) > 0:
|
||||||
|
- logger.log(Log.DEBUG, f"** DMI WARNING ** {warnline}")
|
||||||
|
- set_dmidecode_avail(False)
|
||||||
|
+def get_dmidecode_xml(dmidecode_executable):
|
||||||
|
+ """
|
||||||
|
+ Transform human-readable dmidecode output into machine-processable XML format
|
||||||
|
+ :param dmidecode_executable: Path to dmidecode tool executable
|
||||||
|
+ :return: Tuple of values with resulting XML and dmidecode warnings
|
||||||
|
+ """
|
||||||
|
+ proc = Popen(dmidecode_executable, text=True, stdout=PIPE, stderr=PIPE)
|
||||||
|
+ outs, errs = proc.communicate()
|
||||||
|
+ parts = outs.split("\n\n")
|
||||||
|
+ if len(parts) < 2:
|
||||||
|
+ raise RuntimeError("Parsing dmidecode output failed")
|
||||||
|
+ header = parts[0]
|
||||||
|
+ handles = parts[1:]
|
||||||
|
+ root = lxml.etree.Element("dmidecode")
|
||||||
|
+ # Parse dmidecode output header
|
||||||
|
+ # Note: Only supports SMBIOS data currently
|
||||||
|
+ regex = re.compile(r"# dmidecode (\d+\.\d+)\n"
|
||||||
|
+ r"Getting SMBIOS data from sysfs\.\n"
|
||||||
|
+ r"SMBIOS ((?:\d+\.)+\d+) present\.\n"
|
||||||
|
+ r"(?:(\d+) structures occupying (\d+) bytes\.\n)?"
|
||||||
|
+ r"Table at (0x[0-9A-Fa-f]+)\.", re.MULTILINE)
|
||||||
|
+ match = re.match(regex, header)
|
||||||
|
+ if match is None:
|
||||||
|
+ raise RuntimeError("Parsing dmidecode output failed")
|
||||||
|
+ root.attrib["dmidecodeversion"] = match.group(1)
|
||||||
|
+ root.attrib["smbiosversion"] = match.group(2)
|
||||||
|
+ if match.group(3) is not None:
|
||||||
|
+ root.attrib["structures"] = match.group(3)
|
||||||
|
+ if match.group(4) is not None:
|
||||||
|
+ root.attrib["size"] = match.group(4)
|
||||||
|
+ root.attrib["address"] = match.group(5)
|
||||||
|
+
|
||||||
|
+ # Generate element per handle in dmidecode output
|
||||||
|
+ for handle_text in handles:
|
||||||
|
+ if not handle_text:
|
||||||
|
+ # Empty line
|
||||||
|
+ continue
|
||||||
|
|
||||||
|
- dmidecode.clear_warnings()
|
||||||
|
+ handle = lxml.etree.Element("Handle")
|
||||||
|
+ lines = handle_text.splitlines()
|
||||||
|
+ # Parse handle header
|
||||||
|
+ if len(lines) < 2:
|
||||||
|
+ raise RuntimeError("Parsing dmidecode handle failed")
|
||||||
|
+ header, name, content = lines[0], lines[1], lines[2:]
|
||||||
|
+ match = re.match(r"Handle (0x[0-9A-Fa-f]{4}), "
|
||||||
|
+ r"DMI type (\d+), (\d+) bytes", header)
|
||||||
|
+ if match is None:
|
||||||
|
+ raise RuntimeError("Parsing dmidecode handle failed")
|
||||||
|
+ handle.attrib["address"] = match.group(1)
|
||||||
|
+ handle.attrib["type"] = match.group(2)
|
||||||
|
+ handle.attrib["bytes"] = match.group(3)
|
||||||
|
+ handle.attrib["name"] = name
|
||||||
|
+
|
||||||
|
+ # Parse all fields in handle and create an element for each
|
||||||
|
+ list_field = None
|
||||||
|
+ for index, line in enumerate(content):
|
||||||
|
+ line = content[index]
|
||||||
|
+ if line.rfind("\t") > 0:
|
||||||
|
+ # We are inside a list field, add value to it
|
||||||
|
+ value = lxml.etree.Element("Value")
|
||||||
|
+ value.text = line.strip()
|
||||||
|
+ list_field.append(value)
|
||||||
|
+ continue
|
||||||
|
+ line = line.lstrip().split(":", 1)
|
||||||
|
+ if len(line) != 2:
|
||||||
|
+ raise RuntimeError("Parsing dmidecode field failed")
|
||||||
|
+ if not line[1] or (index + 1 < len(content) and
|
||||||
|
+ content[index + 1].rfind("\t") > 0):
|
||||||
|
+ # No characters after : or next line is inside list field
|
||||||
|
+ # means a list field
|
||||||
|
+ # Note: there are list fields which specify a number of
|
||||||
|
+ # items, for example "Installable Languages", so merely
|
||||||
|
+ # checking for no characters after : is not enough
|
||||||
|
+ list_field = lxml.etree.Element("List")
|
||||||
|
+ list_field.attrib["Name"] = line[0].strip()
|
||||||
|
+ handle.append(list_field)
|
||||||
|
+ else:
|
||||||
|
+ # Regular field
|
||||||
|
+ field = lxml.etree.Element("Field")
|
||||||
|
+ field.attrib["Name"] = line[0].strip()
|
||||||
|
+ field.text = line[1].strip()
|
||||||
|
+ handle.append(field)
|
||||||
|
+
|
||||||
|
+ root.append(handle)
|
||||||
|
+
|
||||||
|
+ return root, errs
|
||||||
|
|
||||||
|
|
||||||
|
class DMIinfo:
|
||||||
|
- '''class used to obtain DMI info via python-dmidecode'''
|
||||||
|
+ '''class used to obtain DMI info via dmidecode'''
|
||||||
|
|
||||||
|
def __init__(self, logger=None):
|
||||||
|
self.__version = '0.6'
|
||||||
|
self._log = logger
|
||||||
|
|
||||||
|
- if not dmidecode_avail:
|
||||||
|
- logger.log(Log.DEBUG, "DMI info unavailable, ignoring DMI tables")
|
||||||
|
+ dmidecode_executable = shutil.which("dmidecode")
|
||||||
|
+ if dmidecode_executable is None:
|
||||||
|
+ logger.log(Log.DEBUG, "DMI info unavailable,"
|
||||||
|
+ " ignoring DMI tables")
|
||||||
|
self.__fake = True
|
||||||
|
return
|
||||||
|
|
||||||
|
self.__fake = False
|
||||||
|
- self.__dmixml = dmidecode.dmidecodeXML()
|
||||||
|
+ try:
|
||||||
|
+ self.__dmixml, self.__warnings = get_dmidecode_xml(
|
||||||
|
+ dmidecode_executable)
|
||||||
|
+ except (RuntimeError, OSError, SubprocessError) as error:
|
||||||
|
+ logger.log(Log.DEBUG, "DMI info unavailable: {};"
|
||||||
|
+ " ignoring DMI tables".format(str(error)))
|
||||||
|
+ self.__fake = True
|
||||||
|
+ return
|
||||||
|
|
||||||
|
self.__xsltparser = self.__load_xslt('rteval_dmi.xsl')
|
||||||
|
|
||||||
|
@@ -88,30 +149,25 @@ class DMIinfo:
|
||||||
|
|
||||||
|
raise RuntimeError(f'Could not locate XSLT template for DMI data ({fname})')
|
||||||
|
|
||||||
|
+ def ProcessWarnings(self):
|
||||||
|
+ """Prints out warnings from dmidecode into log if there were any"""
|
||||||
|
+ if self.__fake or self._log is None:
|
||||||
|
+ return
|
||||||
|
+ for warnline in self.__warnings.split('\n'):
|
||||||
|
+ if len(warnline) > 0:
|
||||||
|
+ self._log.log(Log.DEBUG, f"** DMI WARNING ** {warnline}")
|
||||||
|
+
|
||||||
|
def MakeReport(self):
|
||||||
|
""" Add DMI information to final report """
|
||||||
|
- rep_n = libxml2.newNode("DMIinfo")
|
||||||
|
- rep_n.newProp("version", self.__version)
|
||||||
|
if self.__fake:
|
||||||
|
+ rep_n = libxml2.newNode("DMIinfo")
|
||||||
|
+ rep_n.newProp("version", self.__version)
|
||||||
|
rep_n.addContent("No DMI tables available")
|
||||||
|
rep_n.newProp("not_available", "1")
|
||||||
|
- else:
|
||||||
|
- self.__dmixml.SetResultType(dmidecode.DMIXML_DOC)
|
||||||
|
- try:
|
||||||
|
- dmiqry = xmlout.convert_libxml2_to_lxml_doc(self.__dmixml.QuerySection('all'))
|
||||||
|
- except Exception as ex1:
|
||||||
|
- self._log.log(Log.DEBUG, f'** EXCEPTION {str(ex1)}, will query BIOS only')
|
||||||
|
- try:
|
||||||
|
- # If we can't query 'all', at least query 'bios'
|
||||||
|
- dmiqry = xmlout.convert_libxml2_to_lxml_doc(self.__dmixml.QuerySection('bios'))
|
||||||
|
- except Exception as ex2:
|
||||||
|
- rep_n.addContent("No DMI tables available")
|
||||||
|
- rep_n.newProp("not_available", "1")
|
||||||
|
- self._log.log(Log.DEBUG, f'** EXCEPTION {str(ex2)}, dmi info will not be reported')
|
||||||
|
- return rep_n
|
||||||
|
- resdoc = self.__xsltparser(dmiqry)
|
||||||
|
- dmi_n = xmlout.convert_lxml_to_libxml2_nodes(resdoc.getroot())
|
||||||
|
- rep_n.addChild(dmi_n)
|
||||||
|
+ return rep_n
|
||||||
|
+ rep_n = xmlout.convert_lxml_to_libxml2_nodes(self.__dmixml)
|
||||||
|
+ rep_n.setName("DMIinfo")
|
||||||
|
+ rep_n.newProp("version", self.__version)
|
||||||
|
return rep_n
|
||||||
|
|
||||||
|
def unit_test(rootdir):
|
||||||
|
@@ -130,12 +186,12 @@ def unit_test(rootdir):
|
||||||
|
log = Log()
|
||||||
|
log.SetLogVerbosity(Log.DEBUG|Log.INFO)
|
||||||
|
|
||||||
|
- ProcessWarnings(logger=log)
|
||||||
|
if os.getuid() != 0:
|
||||||
|
print("** ERROR ** Must be root to run this unit_test()")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
d = DMIinfo(logger=log)
|
||||||
|
+ d.ProcessWarnings()
|
||||||
|
dx = d.MakeReport()
|
||||||
|
x = libxml2.newDoc("1.0")
|
||||||
|
x.setRootElement(dx)
|
||||||
|
--
|
||||||
|
cgit 1.2.3-korg
|
@ -1,6 +1,6 @@
|
|||||||
Name: rteval
|
Name: rteval
|
||||||
Version: 3.7
|
Version: 3.7
|
||||||
Release: 7%{?dist}
|
Release: 10%{?dist}.alma.1
|
||||||
Summary: Utility to evaluate system suitability for RT Linux
|
Summary: Utility to evaluate system suitability for RT Linux
|
||||||
|
|
||||||
Group: Development/Tools
|
Group: Development/Tools
|
||||||
@ -27,6 +27,8 @@ Requires: stress-ng
|
|||||||
Requires: perl-interpreter, perl-devel, perl-generators
|
Requires: perl-interpreter, perl-devel, perl-generators
|
||||||
Requires: libmpc, libmpc-devel
|
Requires: libmpc, libmpc-devel
|
||||||
Requires: dwarves
|
Requires: dwarves
|
||||||
|
Requires: dmidecode
|
||||||
|
Requires: procps-ng
|
||||||
BuildArch: noarch
|
BuildArch: noarch
|
||||||
|
|
||||||
#Patches
|
#Patches
|
||||||
@ -38,6 +40,11 @@ Patch5: rteval-Refactor-collapse_cpulist-in-systopology.patch
|
|||||||
Patch6: rteval-Minor-improvements-to-CpuList-class.patch
|
Patch6: rteval-Minor-improvements-to-CpuList-class.patch
|
||||||
Patch7: rteval-Convert-CpuList-class-to-a-module.patch
|
Patch7: rteval-Convert-CpuList-class-to-a-module.patch
|
||||||
Patch8: rteval-Add-relative-cpulists-for-measurements.patch
|
Patch8: rteval-Add-relative-cpulists-for-measurements.patch
|
||||||
|
# Patches were taken from:
|
||||||
|
# https://gitlab.com/redhat/centos-stream/rpms/rteval/-/commit/f0f764c0aecf1770679636294a3232159c5f825e
|
||||||
|
Patch9: rteval-Fix-aNone-being-passed-to-cyclictest.patch
|
||||||
|
# https://git.kernel.org/pub/scm/utils/rteval/rteval.git/patch/?id=43c45ba7dae488c16beb359834b3a71ebf6068d6
|
||||||
|
Patch10: rteval-Implement-initial-dmidecode-support.patch
|
||||||
|
|
||||||
%description
|
%description
|
||||||
The rteval script is a utility for measuring various aspects of
|
The rteval script is a utility for measuring various aspects of
|
||||||
@ -58,6 +65,8 @@ to the screen.
|
|||||||
%patch6 -p1
|
%patch6 -p1
|
||||||
%patch7 -p1
|
%patch7 -p1
|
||||||
%patch8 -p1
|
%patch8 -p1
|
||||||
|
%patch9 -p1
|
||||||
|
%patch10 -p1
|
||||||
|
|
||||||
%build
|
%build
|
||||||
%{__python3} setup.py build
|
%{__python3} setup.py build
|
||||||
@ -79,6 +88,10 @@ to the screen.
|
|||||||
%{_bindir}/rteval
|
%{_bindir}/rteval
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Tue Sep 03 2024 Eduard Abdullin <eabdullin@almalinux.org> - 3.7-10.alma.1
|
||||||
|
- rteval: Implement initial dmidecode support
|
||||||
|
- rteval: Fix -aNone being passed to cyclictest
|
||||||
|
|
||||||
* Wed Jan 31 2024 Tomas Glozar <tglozar@redhat.com> - 3.7-7
|
* Wed Jan 31 2024 Tomas Glozar <tglozar@redhat.com> - 3.7-7
|
||||||
- Added patchset for relative cpuset functionality from upstream
|
- Added patchset for relative cpuset functionality from upstream
|
||||||
Resolves: RHEL-9912
|
Resolves: RHEL-9912
|
||||||
|
Loading…
Reference in New Issue
Block a user