From b2c80cabe52d339d0caf06cc6852dafc1a2af6d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= Date: Mon, 18 Sep 2017 10:50:41 +0200 Subject: [PATCH] Store profile selection mode in a separate file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The profile selection mode is now written to /etc/tuned/profile_mode instead of the second line of /etc/tuned/active_profile. Here are the rules for interpreting the contents of the files. If either of the files does not exist, it is treated the same as if it were empty. If active_profile is empty: If profile_mode contains 'manual': Manual mode, no profile will be used, tuned will run without a profile else if profile_mode is empty or contains 'auto': Automatic mode, recommended profile is used else: Error else: If profile_mode contains 'manual', or is empty (for compatibility reasons): Manual mode, the profile in active_profile will be used else if profile_mode contains 'auto': Automatic mode, recommended profile is used else: Error Resolves: https://github.com/redhat-performance/tuned/issues/64 Signed-off-by: Ondřej Lysoněk --- tuned/admin/admin.py | 69 +++++++++++++++++++------------------- tuned/consts.py | 3 +- tuned/daemon/controller.py | 22 ++++++++---- tuned/daemon/daemon.py | 69 +++++++++++--------------------------- tuned/utils/commands.py | 44 ++++++++++++++++++++++++ 5 files changed, 116 insertions(+), 91 deletions(-) diff --git a/tuned/admin/admin.py b/tuned/admin/admin.py index fd96b0a..8d1b107 100644 --- a/tuned/admin/admin.py +++ b/tuned/admin/admin.py @@ -3,6 +3,7 @@ import tuned.admin from tuned.utils.commands import commands from tuned.profiles import Locator as profiles_locator from exceptions import TunedAdminDBusException +from tuned.exceptions import TunedException import tuned.consts as consts import os import sys @@ -111,29 +112,14 @@ class Admin(object): return profile_name def _get_active_profile(self): - profile_name = None - contents = str.strip(self._cmd.read_file(consts.ACTIVE_PROFILE_FILE)) - if contents == '': - profile_name = '' - else: - arr = contents.split('\n') - profile_name = arr[0] - if profile_name == "": - profile_name = None + profile_name, manual = self._cmd.get_active_profile() return profile_name def _get_profile_mode(self): - contents = str.strip(self._cmd.read_file(consts.ACTIVE_PROFILE_FILE)) - if contents == '': - mode = consts.ACTIVE_PROFILE_AUTO - else: - arr = contents.split('\n') - if len(arr) == 1: - # The file was generated by old Tuned -> manual mode - mode = consts.ACTIVE_PROFILE_MANUAL - else: - mode = arr[1] - return mode + (profile, manual) = self._cmd.get_active_profile() + if manual is None: + manual = profile is not None + return consts.ACTIVE_PROFILE_MANUAL if manual else consts.ACTIVE_PROFILE_AUTO def _print_profile_info(self, profile, profile_info): if profile_info[0] == True: @@ -157,7 +143,14 @@ class Admin(object): def _action_profile_info(self, profile = ""): if profile == "": - profile = self._get_active_profile() + try: + profile = self._get_active_profile() + if profile is None: + print("No current active profile.") + return False + except TunedException as e: + self._error(str(e)) + return False return self._print_profile_info(profile, self._profiles_locator.get_profile_attrs(profile, [consts.PROFILE_ATTR_SUMMARY, consts.PROFILE_ATTR_DESCRIPTION], ["", ""])) def _print_profile_name(self, profile_name): @@ -172,7 +165,11 @@ class Admin(object): return self._controller.exit(self._print_profile_name(self._dbus_get_active_profile())) def _action_active(self): - profile_name = self._get_active_profile() + try: + profile_name = self._get_active_profile() + except TunedException as e: + self._error(str(e)) + return False if profile_name is not None and not self._tuned_is_running(): print("It seems that tuned daemon is not running, preset profile is not activated.") print("Preset profile: %s" % profile_name) @@ -183,14 +180,21 @@ class Admin(object): print("Profile selection mode: " + mode) def _action_dbus_profile_mode(self): - mode = self._controller.profile_mode() + mode, error = self._controller.profile_mode() self._print_profile_mode(mode) + if error != "": + self._error(error) + return self._controller.exit(False) return self._controller.exit(True) def _action_profile_mode(self): - mode = self._get_profile_mode() - self._print_profile_mode(mode) - return True + try: + mode = self._get_profile_mode() + self._print_profile_mode(mode) + return True + except TunedException as e: + self._error(str(e)) + return False def _profile_print_status(self, ret, msg): if ret: @@ -238,16 +242,13 @@ class Admin(object): def _set_profile(self, profile_name, manual): if profile_name in self._profiles_locator.get_known_names(): - s = profile_name + '\n' - if manual: - s += consts.ACTIVE_PROFILE_MANUAL + '\n' - else: - s += consts.ACTIVE_PROFILE_AUTO + '\n' - if self._cmd.write_to_file(consts.ACTIVE_PROFILE_FILE, s): + try: + self._cmd.save_active_profile(profile_name, manual) self._restart_tuned() return True - else: - self._error("Unable to switch profile, do you have enough permissions?") + except TunedException as e: + self._error(str(e)) + self._error("Unable to switch profile.") return False else: self._error("Requested profile '%s' doesn't exist." % profile_name) diff --git a/tuned/consts.py b/tuned/consts.py index 7ac6094..0b9fed3 100644 --- a/tuned/consts.py +++ b/tuned/consts.py @@ -1,5 +1,6 @@ GLOBAL_CONFIG_FILE = "/etc/tuned/tuned-main.conf" ACTIVE_PROFILE_FILE = "/etc/tuned/active_profile" +PROFILE_MODE_FILE = "/etc/tuned/profile_mode" PROFILE_FILE = "tuned.conf" RECOMMEND_CONF_FILE = "/etc/tuned/recommend.conf" DAEMONIZE_PARENT_TIMEOUT = 5 @@ -104,7 +105,7 @@ STR_VERIFY_PROFILE_FAIL = "verify: failed: '%s'" # timout for tuned-adm operations in seconds ADMIN_TIMEOUT = 600 -# Strings for /etc/tuned/active_profile specifying if the active profile +# Strings for /etc/tuned/profile_mode specifying if the active profile # was set automatically or manually ACTIVE_PROFILE_AUTO = "auto" ACTIVE_PROFILE_MANUAL = "manual" diff --git a/tuned/daemon/controller.py b/tuned/daemon/controller.py index 087380b..59a55b1 100644 --- a/tuned/daemon/controller.py +++ b/tuned/daemon/controller.py @@ -1,6 +1,7 @@ from tuned import exports import tuned.logs import tuned.exceptions +from tuned.exceptions import TunedException import threading import tuned.consts as consts from tuned.utils.commands import commands @@ -128,14 +129,23 @@ class Controller(tuned.exports.interfaces.ExportableInterface): else: return "" - @exports.export("", "s") + @exports.export("", "(ss)") def profile_mode(self, caller = None): if caller == "": - return "" - if self._daemon.manual: - return consts.ACTIVE_PROFILE_MANUAL - else: - return consts.ACTIVE_PROFILE_AUTO + return "unknown", "Unauthorized" + manual = self._daemon.manual + if manual is None: + # This means no profile is applied. Check the preset value. + try: + profile, manual = self._cmd.get_active_profile() + if manual is None: + manual = profile is not None + except TunedException as e: + mode = "unknown" + error = str(e) + return mode, error + mode = consts.ACTIVE_PROFILE_MANUAL if manual else consts.ACTIVE_PROFILE_AUTO + return mode, "" @exports.export("", "b") def disable(self, caller = None): diff --git a/tuned/daemon/daemon.py b/tuned/daemon/daemon.py index 4292575..930c40c 100644 --- a/tuned/daemon/daemon.py +++ b/tuned/daemon/daemon.py @@ -60,6 +60,11 @@ class Daemon(object): manual = True if profile_name is None: (profile_name, manual) = self._get_startup_profile() + if profile_name is None: + log.info("No profile is preset, running in manual mode. No profile will be enabled.") + # Passed through '-p' cmdline option + elif profile_name == "": + log.info("No profile will be enabled.") self._profile = None self._manual = None @@ -71,7 +76,7 @@ class Daemon(object): if profile_name == "" or profile_name is None: self._profile = None - self._manual = None + self._manual = manual elif profile_name not in self.profile_loader.profile_locator.get_known_names(): raise TunedException(self._notify_profile_changed(profile_name, False, "Requested profile '%s' doesn't exist." % profile_name)) else: @@ -92,8 +97,7 @@ class Daemon(object): @property def manual(self): - # manual == None means /etc/tuned/active_profile is empty -> automatic mode - return self._manual == True or self._manual is None + return self._manual @property def profile_loader(self): @@ -164,58 +168,23 @@ class Daemon(object): def _save_active_profile(self, profile_name, manual): try: - with open(consts.ACTIVE_PROFILE_FILE, "w") as f: - if len(profile_name) > 0: - f.write(profile_name + "\n") - if manual: - f.write(consts.ACTIVE_PROFILE_MANUAL + "\n") - else: - f.write(consts.ACTIVE_PROFILE_AUTO + "\n") - except (OSError,IOError) as e: - log.error("Cannot write active profile into %s: %s" % (consts.ACTIVE_PROFILE_FILE, str(e))) + self._cmd.save_active_profile(profile_name, manual) + except TunedException as e: + log.error(str(e)) - def _set_recommended_profile(self): - log.info("no profile preset, checking what is recommended for your configuration") + def _get_recommended_profile(self): + log.info("Running in automatic mode, checking what profile is recommended for your configuration.") profile = self._cmd.recommend_profile(hardcoded = not self._recommend_command) - log.info("using '%s' profile and setting it as active" % profile) - self._save_active_profile(profile, False) + log.info("Using '%s' profile" % profile) return profile def _get_startup_profile(self): - manual = False - try: - with open(consts.ACTIVE_PROFILE_FILE, "r") as f: - content = f.read().strip() - if content == "": - profile = self._set_recommended_profile() - else: - arr = content.split('\n') - if len(arr) > 2 or (len(arr) == 2 and arr[1] != consts.ACTIVE_PROFILE_AUTO and arr[1] != consts.ACTIVE_PROFILE_MANUAL): - profile = self._set_recommended_profile() - log.error("cannot read active profile from '%s': bad format. Falling back to '%s' profile." - % consts.ACTIVE_PROFILE_FILE, profile) - else: - profile = arr[0] - if len(arr) == 2: - manual = arr[1] == consts.ACTIVE_PROFILE_MANUAL - if not manual: - profile = self._set_recommended_profile() - else: - # The file has only one line - generated by previous Tuned version. - # Treat the profile as manually set. - manual = True - return (profile, manual) - except IOError as e: - if e.errno == errno.ENOENT: - # No such file or directory - profile = self._set_recommended_profile() - else: - profile = consts.DEFAULT_PROFILE - log.error("error reading active profile from '%s', falling back to '%s' profile" % (consts.ACTIVE_PROFILE_FILE, profile)) - return (profile, manual) - except (OSError, EOFError) as e: - log.error("cannot read active profile, falling back to '%s' profile" % consts.DEFAULT_PROFILE) - return (consts.DEFAULT_PROFILE, manual) + profile, manual = self._cmd.get_active_profile() + if manual is None: + manual = profile is not None + if not manual: + profile = self._get_recommended_profile() + return profile, manual def is_enabled(self): return self._profile is not None diff --git a/tuned/utils/commands.py b/tuned/utils/commands.py index c0b4e2d..9e31d78 100644 --- a/tuned/utils/commands.py +++ b/tuned/utils/commands.py @@ -8,6 +8,7 @@ from configobj import ConfigObj, ConfigObjError import re import procfs from subprocess import * +from tuned.exceptions import TunedException log = tuned.logs.get() @@ -463,3 +464,46 @@ class commands: return val except ValueError: return None + + def get_active_profile(self): + profile_name = "" + mode = "" + try: + with open(consts.ACTIVE_PROFILE_FILE, "r") as f: + profile_name = f.read().strip() + except IOError as e: + if e.errno != errno.ENOENT: + raise TunedException("Failed to read active profile: %s" % e) + except (OSError, EOFError) as e: + raise TunedException("Failed to read active profile: %s" % e) + try: + with open(consts.PROFILE_MODE_FILE, "r") as f: + mode = f.read().strip() + if mode not in ["", consts.ACTIVE_PROFILE_AUTO, consts.ACTIVE_PROFILE_MANUAL]: + raise TunedException("Invalid value in file %s." % consts.PROFILE_MODE_FILE) + except IOError as e: + if e.errno != errno.ENOENT: + raise TunedException("Failed to read profile mode: %s" % e) + except (OSError, EOFError) as e: + raise TunedException("Failed to read profile mode: %s" % e) + if mode == "": + manual = None + else: + manual = mode == consts.ACTIVE_PROFILE_MANUAL + if profile_name == "": + profile_name = None + return (profile_name, manual) + + def save_active_profile(self, profile_name, manual): + try: + with open(consts.ACTIVE_PROFILE_FILE, "w") as f: + if profile_name is not None: + f.write(profile_name + "\n") + except (OSError,IOError) as e: + raise TunedException("Cannot write active profile into %s: %s" % (consts.ACTIVE_PROFILE_FILE, str(e))) + try: + with open(consts.PROFILE_MODE_FILE, "w") as f: + mode = consts.ACTIVE_PROFILE_MANUAL if manual else consts.ACTIVE_PROFILE_AUTO + f.write(mode + "\n") + except (OSError,IOError) as e: + raise TunedException("Cannot write profile mode into %s: %s" % (consts.PROFILE_MODE_FILE, str(e)))