diff --git a/.gitignore b/.gitignore index 8f9b96c..6e87030 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/tuned-2.16.0.tar.gz +SOURCES/tuned-2.17.0-rc.1.tar.gz diff --git a/.tuned.metadata b/.tuned.metadata index 2d7f88a..e7abb30 100644 --- a/.tuned.metadata +++ b/.tuned.metadata @@ -1 +1 @@ -e20fcfb734f869fb175cb88dc7ef6e5eb3cd5946 SOURCES/tuned-2.16.0.tar.gz +a0214633ceb4b1801616625b2a346b340ad7c53c SOURCES/tuned-2.17.0-rc.1.tar.gz diff --git a/SOURCES/tuned-2.16.0-configobj-drop.patch b/SOURCES/tuned-2.16.0-configobj-drop.patch deleted file mode 100644 index b4bccfb..0000000 --- a/SOURCES/tuned-2.16.0-configobj-drop.patch +++ /dev/null @@ -1,829 +0,0 @@ -From 063277a05b3a174f9265d36032ca097ee5b7cc9c Mon Sep 17 00:00:00 2001 -From: Jan Zerdik -Date: Fri, 30 Jul 2021 11:48:59 +0200 -Subject: [PATCH] Removing dependency on python-configobj. - -Resolves: rhbz#1936386 - -Signed-off-by: Jan Zerdik ---- - recommend.conf | 2 +- - tests/unit/profiles/test_loader.py | 7 +++ - tests/unit/profiles/test_locator.py | 18 ++++++- - tests/unit/profiles/test_variables.py | 32 ++++++++++++ - tests/unit/utils/test_global_config.py | 13 ++++- - tuned-gui.py | 5 +- - tuned.spec | 3 +- - tuned/consts.py | 21 ++++++++ - tuned/gtk/gui_plugin_loader.py | 43 +++++++++------- - tuned/gtk/gui_profile_loader.py | 71 ++++++++++++++++++-------- - tuned/gtk/gui_profile_saver.py | 28 ++++++---- - tuned/profiles/loader.py | 38 ++++++-------- - tuned/profiles/locator.py | 25 ++++++--- - tuned/profiles/variables.py | 27 +++++----- - tuned/utils/global_config.py | 55 +++++++++++++++----- - tuned/utils/profile_recommender.py | 19 ++++--- - 16 files changed, 288 insertions(+), 119 deletions(-) - create mode 100644 tests/unit/profiles/test_variables.py - -diff --git a/recommend.conf b/recommend.conf -index f3442ca8..7561696c 100644 ---- a/recommend.conf -+++ b/recommend.conf -@@ -29,7 +29,7 @@ - # Limitation: - # Each profile can be specified only once, because there cannot be - # multiple sections in the configuration file with the same name --# (ConfigObj limitation). -+# (ConfigParser limitation). - # If there is a need to specify the profile multiple times, unique - # suffix like ',ANYSTRING' can be used. Everything after the last ',' - # is stripped by the parser, e.g.: -diff --git a/tests/unit/profiles/test_loader.py b/tests/unit/profiles/test_loader.py -index b6ea76e9..149353d8 100644 ---- a/tests/unit/profiles/test_loader.py -+++ b/tests/unit/profiles/test_loader.py -@@ -46,6 +46,8 @@ def setUpClass(cls): - f.write('file_path=${i:PROFILE_DIR}/whatever\n') - f.write('script=random_name.sh\n') - f.write('[test_unit]\ntest_option=hello world\n') -+ f.write('devices=/dev/${variable1},/dev/${variable2}\n') -+ f.write('[variables]\nvariable1=net\nvariable2=cpu') - - def setUp(self): - locator = profiles.Locator([self._profiles_dir]) -@@ -105,6 +107,11 @@ def test_load_config_data(self): - self.assertEqual(config['test_unit']['test_option'],\ - 'hello world') - -+ def test_variables(self): -+ config = self._loader.load(['dummy4']) -+ self.assertEqual(config.units['test_unit'].devices,\ -+ '/dev/net,/dev/cpu') -+ - @classmethod - def tearDownClass(cls): - shutil.rmtree(cls._test_dir) -diff --git a/tests/unit/profiles/test_locator.py b/tests/unit/profiles/test_locator.py -index cce88daa..bf2906d7 100644 ---- a/tests/unit/profiles/test_locator.py -+++ b/tests/unit/profiles/test_locator.py -@@ -30,7 +30,10 @@ def _create_profile(cls, load_dir, profile_name): - conf_name = os.path.join(profile_dir, "tuned.conf") - os.mkdir(profile_dir) - with open(conf_name, "w") as conf_file: -- pass -+ if profile_name != "custom": -+ conf_file.write("[main]\nsummary=this is " + profile_name + "\n") -+ else: -+ conf_file.write("summary=this is " + profile_name + "\n") - - def test_init(self): - Locator([]) -@@ -65,3 +68,16 @@ def test_ignore_nonexistent_dirs(self): - self.assertEqual(balanced, os.path.join(self._tmp_load_dirs[0], "balanced", "tuned.conf")) - known = locator.get_known_names() - self.assertListEqual(known, ["balanced", "powersafe"]) -+ -+ def test_get_known_names_summary(self): -+ self.assertEqual(("balanced", "this is balanced"), sorted(self.locator.get_known_names_summary())[0]) -+ -+ def test_get_profile_attrs(self): -+ attrs = self.locator.get_profile_attrs("balanced", ["summary", "wrong_attr"], ["this is default", "this is wrong attr"]) -+ self.assertEqual([True, "balanced", "this is balanced", "this is wrong attr"], attrs) -+ -+ attrs = self.locator.get_profile_attrs("custom", ["summary"], ["wrongly writen profile"]) -+ self.assertEqual([True, "custom", "wrongly writen profile"], attrs) -+ -+ attrs = self.locator.get_profile_attrs("different", ["summary"], ["non existing profile"]) -+ self.assertEqual([False, "", "", ""], attrs) -diff --git a/tests/unit/profiles/test_variables.py b/tests/unit/profiles/test_variables.py -new file mode 100644 -index 00000000..47fff2c1 ---- /dev/null -+++ b/tests/unit/profiles/test_variables.py -@@ -0,0 +1,32 @@ -+import unittest -+import tempfile -+import shutil -+from tuned.profiles import variables, profile -+ -+class VariablesTestCase(unittest.TestCase): -+ -+ @classmethod -+ def setUpClass(cls): -+ cls.test_dir = tempfile.mkdtemp() -+ -+ with open(cls.test_dir + "/variables", 'w') as f: -+ f.write("variable1=var1\n") -+ -+ def test_from_file(self): -+ v = variables.Variables() -+ v.add_from_file(self.test_dir + "/variables") -+ self.assertEqual("This is var1", v.expand("This is ${variable1}")) -+ -+ def test_from_unit(self): -+ mock_unit = { -+ "include": self.test_dir + "/variables", -+ "variable2": "var2" -+ } -+ v = variables.Variables() -+ v.add_from_cfg(mock_unit) -+ -+ self.assertEqual("This is var1 and this is var2", v.expand("This is ${variable1} and this is ${variable2}")) -+ -+ @classmethod -+ def tearDownClass(cls): -+ shutil.rmtree(cls.test_dir) -diff --git a/tests/unit/utils/test_global_config.py b/tests/unit/utils/test_global_config.py -index 5b93888c..8981d544 100644 ---- a/tests/unit/utils/test_global_config.py -+++ b/tests/unit/utils/test_global_config.py -@@ -12,7 +12,8 @@ def setUpClass(cls): - cls.test_dir = tempfile.mkdtemp() - with open(cls.test_dir + '/test_config','w') as f: - f.write('test_option = hello\ntest_bool = 1\ntest_size = 12MB\n'\ -- + 'false_bool=0\n') -+ + 'false_bool=0\n'\ -+ + consts.CFG_LOG_FILE_COUNT + " = " + str(consts.CFG_DEF_LOG_FILE_COUNT) + "1\n") - - cls._global_config = global_config.GlobalConfig(\ - cls.test_dir + '/test_config') -@@ -28,10 +29,18 @@ def test_get_size(self): - self.assertEqual(self._global_config.get_size('test_size'),\ - 12*1024*1024) - -- self._global_config.set('test_size','bad_value') -+ self._global_config.set('test_size', 'bad_value') - - self.assertIsNone(self._global_config.get_size('test_size')) - -+ def test_default(self): -+ daemon = self._global_config.get(consts.CFG_DAEMON) -+ self.assertEqual(daemon, consts.CFG_DEF_DAEMON) -+ -+ log_file_count = self._global_config.get(consts.CFG_LOG_FILE_COUNT) -+ self.assertIsNotNone(log_file_count) -+ self.assertNotEqual(log_file_count, consts.CFG_DEF_LOG_FILE_COUNT) -+ - @classmethod - def tearDownClass(cls): - shutil.rmtree(cls.test_dir) -diff --git a/tuned-gui.py b/tuned-gui.py -index a2792792..3953f82f 100755 ---- a/tuned-gui.py -+++ b/tuned-gui.py -@@ -48,7 +48,7 @@ - import sys - import os - import time --import configobj -+import collections - import subprocess - - import tuned.logs -@@ -508,8 +508,7 @@ def on_click_button_confirm_profile_update(self, data): - - def data_to_profile_config(self): - name = self._gobj('entryProfileName').get_text() -- config = configobj.ConfigObj(list_values = False, -- interpolation = False) -+ config = collections.OrderedDict() - - activated = self._gobj('comboboxIncludeProfile').get_active() - model = self._gobj('comboboxIncludeProfile').get_model() -diff --git a/tuned.spec b/tuned.spec -index e3a494fd..7afe1935 100644 ---- a/tuned.spec -+++ b/tuned.spec -@@ -66,9 +66,8 @@ BuildRequires: %{_py}, %{_py}-devel - %if %{without python3} && ( ! 0%{?rhel} || 0%{?rhel} >= 8 ) - BuildRequires: %{_py}-mock - %endif --BuildRequires: %{_py}-configobj - BuildRequires: %{_py}-pyudev --Requires: %{_py}-pyudev, %{_py}-configobj -+Requires: %{_py}-pyudev - Requires: %{_py}-linux-procfs, %{_py}-perf - %if %{without python3} - Requires: %{_py}-schedutils -diff --git a/tuned/consts.py b/tuned/consts.py -index 58cbf4a3..8eb075ba 100644 ---- a/tuned/consts.py -+++ b/tuned/consts.py -@@ -16,6 +16,8 @@ - LOAD_DIRECTORIES = ["/usr/lib/tuned", "/etc/tuned"] - PERSISTENT_STORAGE_DIR = "/var/lib/tuned" - PLUGIN_MAIN_UNIT_NAME = "main" -+# Magic section header because ConfigParser does not support "headerless" config -+MAGIC_HEADER_NAME = "this_is_some_magic_section_header_because_of_compatibility" - RECOMMEND_DIRECTORIES = ["/usr/lib/tuned/recommend.d", "/etc/tuned/recommend.d"] - - TMP_FILE_SUFFIX = ".tmp" -@@ -79,6 +81,10 @@ - PREFIX_PROFILE_FACTORY = "System" - PREFIX_PROFILE_USER = "User" - -+# After adding new option to tuned-main.conf add here its name with CFG_ prefix -+# and eventually default value with CFG_DEF_ prefix (default is None) -+# and function for check with CFG_FUNC_ prefix -+# (see configobj for methods, default is get for string) - CFG_DAEMON = "daemon" - CFG_DYNAMIC_TUNING = "dynamic_tuning" - CFG_SLEEP_INTERVAL = "sleep_interval" -@@ -87,25 +93,40 @@ - CFG_REAPPLY_SYSCTL = "reapply_sysctl" - CFG_DEFAULT_INSTANCE_PRIORITY = "default_instance_priority" - CFG_UDEV_BUFFER_SIZE = "udev_buffer_size" -+CFG_LOG_FILE_COUNT = "log_file_count" -+CFG_LOG_FILE_MAX_SIZE = "log_file_max_size" - CFG_UNAME_STRING = "uname_string" - CFG_CPUINFO_STRING = "cpuinfo_string" - - # no_daemon mode - CFG_DEF_DAEMON = True -+CFG_FUNC_DAEMON = "getboolean" - # default configuration - CFG_DEF_DYNAMIC_TUNING = True -+CFG_FUNC_DYNAMIC_TUNING = "getboolean" - # how long to sleep before checking for events (in seconds) - CFG_DEF_SLEEP_INTERVAL = 1 -+CFG_FUNC_SLEEP_INTERVAL = "getint" - # update interval for dynamic tuning (in seconds) - CFG_DEF_UPDATE_INTERVAL = 10 -+CFG_FUNC_UPDATE_INTERVAL = "getint" - # recommend command availability - CFG_DEF_RECOMMEND_COMMAND = True -+CFG_FUNC_RECOMMEND_COMMAND = "getboolean" - # reapply system sysctl - CFG_DEF_REAPPLY_SYSCTL = True -+CFG_FUNC_REAPPLY_SYSCTL = "getboolean" - # default instance priority - CFG_DEF_DEFAULT_INSTANCE_PRIORITY = 0 -+CFG_FUNC_DEFAULT_INSTANCE_PRIORITY = "getint" - # default pyudev.Monitor buffer size - CFG_DEF_UDEV_BUFFER_SIZE = 1024 * 1024 -+# default log file count -+CFG_DEF_LOG_FILE_COUNT = 2 -+CFG_FUNC_LOG_FILE_COUNT = "getint" -+# default log file max size -+CFG_DEF_LOG_FILE_MAX_SIZE = 1024 * 1024 -+ - - PATH_CPU_DMA_LATENCY = "/dev/cpu_dma_latency" - -diff --git a/tuned/gtk/gui_plugin_loader.py b/tuned/gtk/gui_plugin_loader.py -index d364602d..f943a220 100644 ---- a/tuned/gtk/gui_plugin_loader.py -+++ b/tuned/gtk/gui_plugin_loader.py -@@ -25,25 +25,23 @@ - ''' - - import importlib --from validate import Validator - - import tuned.consts as consts - import tuned.logs -- --import configobj as ConfigObj -+try: -+ from configparser import ConfigParser, Error -+ from io import StringIO -+except ImportError: -+ # python2.7 support, remove RHEL-7 support end -+ from ConfigParser import ConfigParser, Error -+ from StringIO import StringIO - from tuned.exceptions import TunedException -+from tuned.utils.global_config import GlobalConfig - - from tuned.admin.dbus_controller import DBusController - - __all__ = ['GuiPluginLoader'] - --global_config_spec = ['dynamic_tuning = boolean(default=%s)' -- % consts.CFG_DEF_DYNAMIC_TUNING, -- 'sleep_interval = integer(default=%s)' -- % consts.CFG_DEF_SLEEP_INTERVAL, -- 'update_interval = integer(default=%s)' -- % consts.CFG_DEF_UPDATE_INTERVAL] -- - - class GuiPluginLoader(): - -@@ -84,19 +82,26 @@ def _load_global_config(self, file_name=consts.GLOBAL_CONFIG_FILE): - """ - - try: -- config = ConfigObj.ConfigObj(file_name, -- configspec=global_config_spec, -- raise_errors = True, file_error = True, list_values = False, interpolation = False) -+ config_parser = ConfigParser() -+ config_parser.optionxform = str -+ with open(file_name) as f: -+ config_parser.readfp(StringIO("[" + consts.MAGIC_HEADER_NAME + "]\n" + f.read())) -+ config, functions = GlobalConfig.get_global_config_spec() -+ for option in config_parser.options(consts.MAGIC_HEADER_NAME): -+ if option in config: -+ try: -+ func = getattr(config_parser, functions[option]) -+ config[option] = func(consts.MAGIC_HEADER_NAME, option) -+ except Error: -+ raise TunedException("Global TuneD configuration file '%s' is not valid." -+ % file_name) -+ else: -+ config[option] = config_parser.get(consts.MAGIC_HEADER_NAME, option, raw=True) - except IOError as e: - raise TunedException("Global TuneD configuration file '%s' not found." - % file_name) -- except ConfigObj.ConfigObjError as e: -+ except Error as e: - raise TunedException("Error parsing global TuneD configuration file '%s'." - % file_name) -- vdt = Validator() -- if not config.validate(vdt, copy=True): -- raise TunedException("Global TuneD configuration file '%s' is not valid." -- % file_name) - return config - -- -diff --git a/tuned/gtk/gui_profile_loader.py b/tuned/gtk/gui_profile_loader.py -index c50dd9ff..dcd16b72 100644 ---- a/tuned/gtk/gui_profile_loader.py -+++ b/tuned/gtk/gui_profile_loader.py -@@ -25,10 +25,17 @@ - ''' - - import os --import configobj -+try: -+ from configparser import ConfigParser, Error -+ from io import StringIO -+except ImportError: -+ # python2.7 support, remove RHEL-7 support end -+ from ConfigParser import ConfigParser, Error -+ from StringIO import StringIO - import subprocess - import json - import sys -+import collections - - import tuned.profiles.profile as p - import tuned.consts -@@ -59,14 +66,21 @@ def set_raw_profile(self, profile_name, config): - - profilePath = self._locate_profile_path(profile_name) - -- config_lines = config.split('\n') -- - if profilePath == tuned.consts.LOAD_DIRECTORIES[1]: - file_path = profilePath + '/' + profile_name + '/' + tuned.consts.PROFILE_FILE -- -- config_obj = configobj.ConfigObj(infile=config_lines,list_values = False, interpolation = False) -- config_obj.filename = file_path -- config_obj.initial_comment = ('#', 'tuned configuration', '#') -+ config_parser = ConfigParser() -+ config_parser.optionxform = str -+ config_parser.readfp(StringIO(config)) -+ -+ config_obj = { -+ 'main': collections.OrderedDict(), -+ 'filename': file_path, -+ 'initial_comment': ('#', 'tuned configuration', '#') -+ } -+ for s in config_parser.sections(): -+ config_obj['main'][s] = collections.OrderedDict() -+ for o in config_parser.options(s): -+ config_obj['main'][s][o] = config_parser.get(s, o, raw=True) - self._save_profile(config_obj) - self._refresh_profiles() - else: -@@ -76,8 +90,15 @@ def set_raw_profile(self, profile_name, config): - - def load_profile_config(self, profile_name, path): - conf_path = path + '/' + profile_name + '/' + tuned.consts.PROFILE_FILE -- profile_config = configobj.ConfigObj(conf_path, list_values = False, -- interpolation = False) -+ config = ConfigParser() -+ config.optionxform = str -+ profile_config = collections.OrderedDict() -+ with open(conf_path) as f: -+ config.readfp(f) -+ for s in config.sections(): -+ profile_config[s] = collections.OrderedDict() -+ for o in config.options(s): -+ profile_config[s][o] = config.get(s, o, raw=True) - return profile_config - - def _locate_profile_path(self, profile_name): -@@ -95,11 +116,11 @@ def _load_all_profiles(self): - try: - self.profiles[profile] = p.Profile(profile, - self.load_profile_config(profile, d)) -- except configobj.ParseError: -+ except Error: - pass - - # print "can not make \""+ profile +"\" profile without correct config on path: " + d --# except: -+# except:StringIO - # raise managerException.ManagerException("Can not make profile") - # print "can not make \""+ profile +"\" profile without correct config with path: " + d - -@@ -113,20 +134,24 @@ def _refresh_profiles(self): - - def save_profile(self, profile): - path = tuned.consts.LOAD_DIRECTORIES[1] + '/' + profile.name -- config = configobj.ConfigObj(list_values = False, interpolation = False) -- config.filename = path + '/' + tuned.consts.PROFILE_FILE -- config.initial_comment = ('#', 'tuned configuration', '#') -+ config = { -+ 'main': collections.OrderedDict(), -+ 'filename': path + '/' + tuned.consts.PROFILE_FILE, -+ 'initial_comment': ('#', 'tuned configuration', '#') -+ } -+ config['filename'] = path + '/' + tuned.consts.PROFILE_FILE -+ config['initial_comment'] = ('#', 'tuned configuration', '#') - - try: -- config['main'] = profile.options -+ config['main']['main'] = profile.options - except KeyError: -- config['main'] = '' -+ config['main']['main'] = {} - - # profile dont have main section - - pass - for (name, unit) in list(profile.units.items()): -- config[name] = unit.options -+ config['main'][name] = unit.options - - self._save_profile(config) - -@@ -148,18 +173,20 @@ def update_profile( - if old_profile_name != profile.name: - self.remove_profile(old_profile_name, is_admin=is_admin) - -- config = configobj.ConfigObj(list_values = False, interpolation = False) -- config.filename = path + '/' + tuned.consts.PROFILE_FILE -- config.initial_comment = ('#', 'tuned configuration', '#') -+ config = { -+ 'main': collections.OrderedDict(), -+ 'filename': path + '/' + tuned.consts.PROFILE_FILE, -+ 'initial_comment': ('#', 'tuned configuration', '#') -+ } - try: -- config['main'] = profile.options -+ config['main']['main'] = profile.options - except KeyError: - - # profile dont have main section - - pass - for (name, unit) in list(profile.units.items()): -- config[name] = unit.options -+ config['main'][name] = unit.options - - self._save_profile(config) - -diff --git a/tuned/gtk/gui_profile_saver.py b/tuned/gtk/gui_profile_saver.py -index b339cba1..24b0fe3a 100644 ---- a/tuned/gtk/gui_profile_saver.py -+++ b/tuned/gtk/gui_profile_saver.py -@@ -1,7 +1,11 @@ - import os - import sys - import json --from configobj import ConfigObj -+try: -+ from configparser import ConfigParser -+except ImportError: -+ # python2.7 support, remove RHEL-7 support end -+ from ConfigParser import ConfigParser - - - if __name__ == "__main__": -@@ -11,13 +15,19 @@ - if not os.path.exists(profile_dict['filename']): - os.makedirs(os.path.dirname(profile_dict['filename'])) - -- profile_configobj = ConfigObj() -- for section in profile_dict['sections']: -- profile_configobj[section] = profile_dict['main'][section] -- -- profile_configobj.filename = os.path.join('/etc','tuned',os.path.dirname(os.path.abspath(profile_dict['filename'])),'tuned.conf') -- profile_configobj.initial_comment = profile_dict['initial_comment'] -- -- profile_configobj.write() -+ profile_configobj = ConfigParser() -+ profile_configobj.optionxform = str -+ for section, options in profile_dict['main'].items(): -+ profile_configobj.add_section(section) -+ for option, value in options.items(): -+ profile_configobj.set(section, option, value) -+ -+ path = os.path.join('/etc','tuned',os.path.dirname(os.path.abspath(profile_dict['filename'])),'tuned.conf') -+ with open(path, 'w') as f: -+ profile_configobj.write(f) -+ with open(path, 'r+') as f: -+ content = f.read() -+ f.seek(0, 0) -+ f.write("\n".join(profile_dict['initial_comment']) + "\n" + content) - - sys.exit(0) -diff --git a/tuned/profiles/loader.py b/tuned/profiles/loader.py -index 7f132b4f..31037182 100644 ---- a/tuned/profiles/loader.py -+++ b/tuned/profiles/loader.py -@@ -1,6 +1,10 @@ - import tuned.profiles.profile - import tuned.profiles.variables --from configobj import ConfigObj, ConfigObjError -+try: -+ from configparser import ConfigParser, Error -+except ImportError: -+ # python2.7 support, remove RHEL-7 support end -+ from ConfigParser import ConfigParser, Error - import tuned.consts as consts - import os.path - import collections -@@ -96,30 +100,22 @@ def _expand_profile_dir(self, profile_dir, string): - - def _load_config_data(self, file_name): - try: -- config_obj = ConfigObj(file_name, raise_errors = True, list_values = False, interpolation = False) -- except ConfigObjError as e: -+ config_obj = ConfigParser() -+ config_obj.optionxform=str -+ with open(file_name) as f: -+ config_obj.readfp(f) -+ except Error as e: - raise InvalidProfileException("Cannot parse '%s'." % file_name, e) - - config = collections.OrderedDict() -- for section in list(config_obj.keys()): -- config[section] = collections.OrderedDict() -- try: -- keys = list(config_obj[section].keys()) -- except AttributeError: -- raise InvalidProfileException("Error parsing section '%s' in file '%s'." % (section, file_name)) -- for option in keys: -- config[section][option] = config_obj[section][option] -- - dir_name = os.path.dirname(file_name) -- # TODO: Could we do this in the same place as the expansion of other functions? -- for section in config: -- for option in config[section]: -+ for section in list(config_obj.sections()): -+ config[section] = collections.OrderedDict() -+ for option in config_obj.options(section): -+ config[section][option] = config_obj.get(section, option, raw=True) - config[section][option] = self._expand_profile_dir(dir_name, config[section][option]) -- -- # TODO: HACK, this needs to be solved in a better way (better config parser) -- for unit_name in config: -- if "script" in config[unit_name] and config[unit_name].get("script", None) is not None: -- script_path = os.path.join(dir_name, config[unit_name]["script"]) -- config[unit_name]["script"] = [os.path.normpath(script_path)] -+ if config[section].get("script") is not None: -+ script_path = os.path.join(dir_name, config[section]["script"]) -+ config[section]["script"] = [os.path.normpath(script_path)] - - return config -diff --git a/tuned/profiles/locator.py b/tuned/profiles/locator.py -index 3fd46916..994bdfb5 100644 ---- a/tuned/profiles/locator.py -+++ b/tuned/profiles/locator.py -@@ -1,6 +1,12 @@ - import os - import tuned.consts as consts --from configobj import ConfigObj, ConfigObjError -+try: -+ from configparser import ConfigParser, Error -+ from io import StringIO -+except ImportError: -+ # python2.7 support, remove RHEL-7 support end -+ from ConfigParser import ConfigParser, Error -+ from StringIO import StringIO - - class Locator(object): - """ -@@ -48,8 +54,12 @@ def parse_config(self, profile_name): - if config_file is None: - return None - try: -- return ConfigObj(config_file, list_values = False, interpolation = False) -- except (IOError, OSError, ConfigObjError) as e: -+ config = ConfigParser() -+ config.optionxform = str -+ with open(config_file) as f: -+ config.readfp(StringIO("[" + consts.MAGIC_HEADER_NAME + "]\n" + f.read())) -+ return config -+ except (IOError, OSError, Error) as e: - return None - - # Get profile attributes (e.g. summary, description), attrs is list of requested attributes, -@@ -75,17 +85,16 @@ def get_profile_attrs(self, profile_name, attrs, defvals = None): - config = self.parse_config(profile_name) - if config is None: - return [False, "", "", ""] -- if consts.PLUGIN_MAIN_UNIT_NAME in config: -- d = config[consts.PLUGIN_MAIN_UNIT_NAME] -- else: -- d = dict() -+ main_unit_in_config = consts.PLUGIN_MAIN_UNIT_NAME in config.sections() - vals = [True, profile_name] - for (attr, defval) in zip(attrs, defvals): - if attr == "" or attr is None: - vals[0] = False - vals = vals + [""] -+ elif main_unit_in_config and attr in config.options(consts.PLUGIN_MAIN_UNIT_NAME): -+ vals = vals + [config.get(consts.PLUGIN_MAIN_UNIT_NAME, attr, raw=True)] - else: -- vals = vals + [d.get(attr, defval)] -+ vals = vals + [defval] - return vals - - def list_profiles(self): -diff --git a/tuned/profiles/variables.py b/tuned/profiles/variables.py -index 2e101661..a9e27aea 100644 ---- a/tuned/profiles/variables.py -+++ b/tuned/profiles/variables.py -@@ -4,7 +4,13 @@ - from .functions import functions as functions - import tuned.consts as consts - from tuned.utils.commands import commands --from configobj import ConfigObj, ConfigObjError -+try: -+ from configparser import ConfigParser, Error -+ from io import StringIO -+except ImportError: -+ # python2.7 support, remove RHEL-7 support end -+ from ConfigParser import ConfigParser, Error -+ from StringIO import StringIO - - log = tuned.logs.get() - -@@ -40,24 +46,21 @@ def add_variable(self, variable, value): - self._lookup_re[r'(? -Date: Thu, 15 Jul 2021 20:48:54 +0200 -Subject: [PATCH] scheduler: new option cgroup_ps_blacklist -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -This option allows skipping processes belonging to the blacklisted -cgroups. It matches the regular expression against items from the -/proc/PID/cgroups. Items/lines from the /proc/PID/cgroups are separated -by commas ','. Each item consists of the: -hierarchy-ID:controller-list:cgroup-path - -Example of the content on which the regular expression is run: -10:hugetlb:/,9:perf_event:/,8:blkio:/ - -For cgroups v2 the hierarchy-ID is 0 and the controller-list is ''. -For details see man cgroups.7. The only difference from the man -cgroups.7 is that it uses commas for separation of the items instead -of the new lines. The commas are added by the python-linux-procfs -(it's the behavior of the python-linux-procfs-0.6.3). - -Multiple regular expressions can be separated by the semicolon ';'. - -Examples: -[scheduler] -isolated_cores=1 -cgroup_ps_blacklist=:/daemons\b - -It will move all processes away from the core 1 except processes which -belongs to the cgroup '/daemons'. The '\b' is regular expression -metacharacter that matches word boundary (i.e. it matches only -'/daemons', not e.g. '/daemonset' or '/group/daemons'). In this example -we do not care about the hierarchy-ID and the controller-list. - -[scheduler] -isolated_cores=1 -cgroup_ps_blacklist=\b8:blkio:/,|$ - -In this example it skips processes belonging to the cgroup '/', -with hierarchy-ID 8 and controller-list blkio. The ',|$' is needed -because the '\b' matches word boundary and the non-alphanumeric -character '/' is not taken as a word, thus the '\b' will not match there. - -[scheduler] -isolated_cores=1 -cgroup_ps_blacklist=:/daemons\b;:/test\b - -In this example two regular expressions are used which tries to match -'/daemons' and '/test' cgroup-path. If either matches (i.e. the OR operator), -the process is skipped (i.e. not moved away from the core 1). - -Resolves: rhbz#1980715 - -Signed-off-by: Jaroslav Škarvada ---- - tuned/plugins/plugin_scheduler.py | 19 +++++++++++++++++++ - 1 file changed, 19 insertions(+) - -diff --git a/tuned/plugins/plugin_scheduler.py b/tuned/plugins/plugin_scheduler.py -index e2f7ca2..8e77417 100644 ---- a/tuned/plugins/plugin_scheduler.py -+++ b/tuned/plugins/plugin_scheduler.py -@@ -156,6 +156,7 @@ class SchedulerPlugin(base.Plugin): - # default is to whitelist all and blacklist none - self._ps_whitelist = ".*" - self._ps_blacklist = "" -+ self._cgroup_ps_blacklist_re = "" - self._cpus = perf.cpu_map() - self._scheduler_storage_key = self._storage_key( - command_name = "scheduler") -@@ -251,6 +252,7 @@ class SchedulerPlugin(base.Plugin): - "cgroup_mount_point_init": False, - "cgroup_groups_init": True, - "cgroup_for_isolated_cores": None, -+ "cgroup_ps_blacklist": None, - "ps_whitelist": None, - "ps_blacklist": None, - "default_irq_smp_affinity": "calc", -@@ -811,6 +813,14 @@ class SchedulerPlugin(base.Plugin): - elif event.type == perf.RECORD_EXIT: - self._remove_pid(instance, int(event.tid)) - -+ @command_custom("cgroup_ps_blacklist", per_device = False) -+ def _cgroup_ps_blacklist(self, enabling, value, verify, ignore_missing): -+ # currently unsupported -+ if verify: -+ return None -+ if enabling and value is not None: -+ self._cgroup_ps_blacklist_re = "|".join(["(%s)" % v for v in re.split(r"(? 8 +# not on CentOS +%if 0%{!?centos:1} Requires: subscription-manager +%endif %else %if 0%{?rhel} > 7 Requires: python3-syspurpose %endif %endif -# rhbz#1980715 -Patch0: tuned-2.16.0-scheduler-cgroups-exclude.patch -# rhbz#1936386 -Patch1: tuned-2.16.0-configobj-drop.patch %description The tuned package contains a daemon that tunes system settings dynamically. @@ -249,6 +248,13 @@ Requires: %{name} = %{version} %description profiles-postgresql Additional tuned profile(s) targeted to PostgreSQL server loads. +%package profiles-openshift +Summary: Additional TuneD profile(s) optimized for OpenShift +Requires: %{name} = %{version} + +%description profiles-openshift +Additional TuneD profile(s) optimized for OpenShift. + %prep %autosetup -p1 -n %{name}-%{version}%{?prerel2} @@ -414,6 +420,9 @@ fi %exclude %{_prefix}/lib/tuned/cpu-partitioning %exclude %{_prefix}/lib/tuned/spectrumscale-ece %exclude %{_prefix}/lib/tuned/postgresql +%exclude %{_prefix}/lib/tuned/openshift +%exclude %{_prefix}/lib/tuned/openshift-control-plane +%exclude %{_prefix}/lib/tuned/openshift-node %{_prefix}/lib/tuned %dir %{_sysconfdir}/tuned %dir %{_sysconfdir}/tuned/recommend.d @@ -531,7 +540,24 @@ fi %{_prefix}/lib/tuned/postgresql %{_mandir}/man7/tuned-profiles-postgresql.7* +%files profiles-openshift +%{_prefix}/lib/tuned/openshift +%{_prefix}/lib/tuned/openshift-control-plane +%{_prefix}/lib/tuned/openshift-node +%{_mandir}/man7/tuned-profiles-openshift.7* + %changelog +* Sun Jan 2 2022 Jaroslav Škarvada - 2.17.0-0.1.rc1 +- new release + - rebased tuned to latest upstream + resolves: rhbz#2003838 + - cpu-partitioning: fixed no_balance_cores on newer kernels + resolves: rhbz#1874596 + +* Mon Dec 6 2021 Jaroslav Škarvada - 2.16.0-4 +- spec: do not require subscription-manager on CentOS + Resolves: rhbz#2029405 + * Wed Aug 18 2021 Jaroslav Škarvada - 2.16.0-3 - scheduler: allow exclude of processes from the specific cgroup(s) Resolves: rhbz#1980715