Merge pull request #60 from olysonek/recommend.d_rebase
Recommend.d functionality
This commit is contained in:
commit
0a2c210db3
9 changed files with 237 additions and 68 deletions
4
Makefile
4
Makefile
|
|
@ -31,6 +31,7 @@ DATADIR = /usr/share
|
|||
DOCDIR = $(DATADIR)/doc/$(NAME)
|
||||
PYTHON_SITELIB = $(shell python -c 'from distutils.sysconfig import get_python_lib; print get_python_lib();' || echo /usr/lib/python2.7/site-packages)
|
||||
TUNED_PROFILESDIR = /usr/lib/tuned
|
||||
TUNED_RECOMMEND_DIR = $(TUNED_PROFILESDIR)/recommend.d
|
||||
BASH_COMPLETIONS = $(DATADIR)/bash-completion/completions
|
||||
|
||||
release-dir:
|
||||
|
|
@ -100,6 +101,7 @@ install-dirs:
|
|||
mkdir -p $(DESTDIR)/var/log/tuned
|
||||
mkdir -p $(DESTDIR)/run/tuned
|
||||
mkdir -p $(DESTDIR)$(DOCDIR)
|
||||
mkdir -p $(DESTDIR)$(TUNED_RECOMMEND_DIR)
|
||||
|
||||
install: install-dirs
|
||||
# library
|
||||
|
|
@ -135,7 +137,7 @@ install: install-dirs
|
|||
$(DESTDIR)/etc/tuned/realtime-virtual-host-variables.conf
|
||||
mv $(DESTDIR)$(TUNED_PROFILESDIR)/cpu-partitioning/cpu-partitioning-variables.conf \
|
||||
$(DESTDIR)/etc/tuned/cpu-partitioning-variables.conf
|
||||
install -pm 0644 recommend.conf $(DESTDIR)$(TUNED_PROFILESDIR)/recommend.conf
|
||||
install -pm 0644 recommend.conf $(DESTDIR)$(TUNED_RECOMMEND_DIR)/50-tuned-recommend.conf
|
||||
|
||||
# bash completion
|
||||
install -Dpm 0644 tuned-adm.bash $(DESTDIR)$(BASH_COMPLETIONS)/tuned-adm
|
||||
|
|
|
|||
|
|
@ -17,6 +17,16 @@
|
|||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="com.redhat.tuned.profile_mode">
|
||||
<description>Show current profile selection mode</description>
|
||||
<message>Authentication is required to show current profile selection mode</message>
|
||||
<defaults>
|
||||
<allow_any>yes</allow_any>
|
||||
<allow_inactive>yes</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="com.redhat.tuned.disable">
|
||||
<description>Disable Tuned</description>
|
||||
<message>Authentication is required to disable Tuned</message>
|
||||
|
|
@ -117,6 +127,16 @@
|
|||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="com.redhat.tuned.auto_profile">
|
||||
<description>Enable automatic profile selection mode</description>
|
||||
<message>Authentication is required to change profile selection mode</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin</allow_any>
|
||||
<allow_inactive>auth_admin</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="com.redhat.tuned.verify_profile">
|
||||
<description>Verify Tuned profile</description>
|
||||
<message>Authentication is required to verify Tuned profile</message>
|
||||
|
|
|
|||
|
|
@ -71,6 +71,12 @@ if __name__ == "__main__":
|
|||
parser_verify.set_defaults(action="verify_profile")
|
||||
parser_verify.add_argument("--ignore-missing", "-i", action="store_true", help="do not treat missing/non-supported tunings as errors")
|
||||
|
||||
parser_auto_profile = subparsers.add_parser("auto_profile", help="enable automatic profile selection mode, switch to the recommended profile")
|
||||
parser_auto_profile.set_defaults(action="auto_profile")
|
||||
|
||||
parser_profile_mode = subparsers.add_parser("profile_mode", help="show current profile selection mode")
|
||||
parser_profile_mode.set_defaults(action="profile_mode")
|
||||
|
||||
args = parser.parse_args(sys.argv[1:])
|
||||
|
||||
options = vars(args)
|
||||
|
|
|
|||
|
|
@ -112,11 +112,29 @@ class Admin(object):
|
|||
|
||||
def _get_active_profile(self):
|
||||
profile_name = None
|
||||
profile_name = str.strip(self._cmd.read_file(consts.ACTIVE_PROFILE_FILE, 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
|
||||
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
|
||||
|
||||
def _print_profile_info(self, profile, profile_info):
|
||||
if profile_info[0] == True:
|
||||
print("Profile name:")
|
||||
|
|
@ -161,6 +179,19 @@ class Admin(object):
|
|||
return True
|
||||
return self._print_profile_name(profile_name)
|
||||
|
||||
def _print_profile_mode(self, mode):
|
||||
print("Profile selection mode: " + mode)
|
||||
|
||||
def _action_dbus_profile_mode(self):
|
||||
mode = self._controller.profile_mode()
|
||||
self._print_profile_mode(mode)
|
||||
return self._controller.exit(True)
|
||||
|
||||
def _action_profile_mode(self):
|
||||
mode = self._get_profile_mode()
|
||||
self._print_profile_mode(mode)
|
||||
return True
|
||||
|
||||
def _profile_print_status(self, ret, msg):
|
||||
if ret:
|
||||
if not self._controller.is_running() and not self._controller.start():
|
||||
|
|
@ -197,20 +228,23 @@ class Admin(object):
|
|||
self._controller.set_action(self._action_dbus_wait_profile, profile_name)
|
||||
return self._profile_print_status(ret, msg)
|
||||
|
||||
def _action_profile(self, profiles):
|
||||
if len(profiles) == 0:
|
||||
return self._action_list()
|
||||
profile_name = " ".join(profiles)
|
||||
if profile_name == "":
|
||||
return False
|
||||
def _restart_tuned(self):
|
||||
print("Trying to (re)start tuned...")
|
||||
(ret, msg) = self._cmd.execute(["service", "tuned", "restart"])
|
||||
if ret == 0:
|
||||
print("Tuned (re)started, changes applied.")
|
||||
else:
|
||||
print("Tuned (re)start failed, you need to (re)start tuned by hand for changes to apply.")
|
||||
|
||||
def _set_profile(self, profile_name, manual):
|
||||
if profile_name in self._profiles_locator.get_known_names():
|
||||
if self._cmd.write_to_file(consts.ACTIVE_PROFILE_FILE, profile_name):
|
||||
print("Trying to (re)start tuned...")
|
||||
(ret, msg) = self._cmd.execute(["service", "tuned", "restart"])
|
||||
if ret == 0:
|
||||
print("Tuned (re)started, changes applied.")
|
||||
else:
|
||||
print("Tuned (re)start failed, you need to (re)start tuned by hand for changes to apply.")
|
||||
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):
|
||||
self._restart_tuned()
|
||||
return True
|
||||
else:
|
||||
self._error("Unable to switch profile, do you have enough permissions?")
|
||||
|
|
@ -219,6 +253,29 @@ class Admin(object):
|
|||
self._error("Requested profile '%s' doesn't exist." % profile_name)
|
||||
return False
|
||||
|
||||
def _action_profile(self, profiles):
|
||||
if len(profiles) == 0:
|
||||
return self._action_list()
|
||||
profile_name = " ".join(profiles)
|
||||
if profile_name == "":
|
||||
return False
|
||||
return self._set_profile(profile_name, True)
|
||||
|
||||
def _action_dbus_auto_profile(self):
|
||||
profile_name = self._controller.recommend_profile()
|
||||
self._daemon_action_finished.clear()
|
||||
(ret, msg) = self._controller.auto_profile()
|
||||
if self._async or not ret:
|
||||
return self._controller.exit(self._profile_print_status(ret, msg))
|
||||
else:
|
||||
self._timestamp = time.time()
|
||||
self._controller.set_action(self._action_dbus_wait_profile, profile_name)
|
||||
return self._profile_print_status(ret, msg)
|
||||
|
||||
def _action_auto_profile(self):
|
||||
profile_name = self._cmd.recommend_profile()
|
||||
return self._set_profile(profile_name, False)
|
||||
|
||||
def _action_dbus_recommend_profile(self):
|
||||
print(self._controller.recommend_profile())
|
||||
return self._controller.exit(True)
|
||||
|
|
|
|||
|
|
@ -99,11 +99,17 @@ class DBusController(object):
|
|||
def active_profile(self):
|
||||
return self._call("active_profile")
|
||||
|
||||
def profile_mode(self):
|
||||
return self._call("profile_mode")
|
||||
|
||||
def switch_profile(self, new_profile):
|
||||
if new_profile == "":
|
||||
return (False, "No profile specified")
|
||||
return self._call("switch_profile", new_profile)
|
||||
|
||||
def auto_profile(self):
|
||||
return self._call("auto_profile")
|
||||
|
||||
def recommend_profile(self):
|
||||
return self._call("recommend_profile")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
GLOBAL_CONFIG_FILE = "/etc/tuned/tuned-main.conf"
|
||||
ACTIVE_PROFILE_FILE = "/etc/tuned/active_profile"
|
||||
PROFILE_FILE = "tuned.conf"
|
||||
AUTODETECT_FILE = "recommend.conf"
|
||||
RECOMMEND_CONF_FILE = "/etc/tuned/recommend.conf"
|
||||
DAEMONIZE_PARENT_TIMEOUT = 5
|
||||
NAMESPACE = "com.redhat.tuned"
|
||||
DBUS_BUS = NAMESPACE
|
||||
|
|
@ -12,6 +12,7 @@ DEFAULT_STORAGE_FILE = "/run/tuned/save.pickle"
|
|||
LOAD_DIRECTORIES = ["/usr/lib/tuned", "/etc/tuned"]
|
||||
PERSISTENT_STORAGE_DIR = "/var/lib/tuned"
|
||||
PLUGIN_MAIN_UNIT_NAME = "main"
|
||||
RECOMMEND_DIRECTORIES = ["/usr/lib/tuned/recommend.d", "/etc/tuned/recommend.d"]
|
||||
|
||||
TMP_FILE_SUFFIX = ".tmp"
|
||||
# max. number of consecutive errors to give up
|
||||
|
|
@ -99,3 +100,8 @@ 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
|
||||
# was set automatically or manually
|
||||
ACTIVE_PROFILE_AUTO = "auto"
|
||||
ACTIVE_PROFILE_MANUAL = "manual"
|
||||
|
|
|
|||
|
|
@ -79,19 +79,15 @@ class Controller(tuned.exports.interfaces.ExportableInterface):
|
|||
else:
|
||||
return self.stop() and self.start()
|
||||
|
||||
@exports.export("s", "(bs)")
|
||||
def switch_profile(self, profile_name, caller = None):
|
||||
if caller == "":
|
||||
return (False, "Unauthorized")
|
||||
def _switch_profile(self, profile_name, manual):
|
||||
was_running = self._daemon.is_running()
|
||||
msg = "OK"
|
||||
success = True
|
||||
reapply = False
|
||||
try:
|
||||
if was_running:
|
||||
# stop(switch_profile = True), due to profile switch
|
||||
self._daemon.stop(True)
|
||||
self._daemon.set_profile(profile_name)
|
||||
self._daemon.stop(profile_switch = True)
|
||||
self._daemon.set_profile(profile_name, manual)
|
||||
except tuned.exceptions.TunedException as e:
|
||||
success = False
|
||||
msg = str(e)
|
||||
|
|
@ -110,6 +106,19 @@ class Controller(tuned.exports.interfaces.ExportableInterface):
|
|||
|
||||
return (success, msg)
|
||||
|
||||
@exports.export("s", "(bs)")
|
||||
def switch_profile(self, profile_name, caller = None):
|
||||
if caller == "":
|
||||
return (False, "Unauthorized")
|
||||
return self._switch_profile(profile_name, True)
|
||||
|
||||
@exports.export("", "(bs)")
|
||||
def auto_profile(self, caller = None):
|
||||
if caller == "":
|
||||
return (False, "Unauthorized")
|
||||
profile_name = self.recommend_profile()
|
||||
return self._switch_profile(profile_name, False)
|
||||
|
||||
@exports.export("", "s")
|
||||
def active_profile(self, caller = None):
|
||||
if caller == "":
|
||||
|
|
@ -119,6 +128,15 @@ class Controller(tuned.exports.interfaces.ExportableInterface):
|
|||
else:
|
||||
return ""
|
||||
|
||||
@exports.export("", "s")
|
||||
def profile_mode(self, caller = None):
|
||||
if caller == "":
|
||||
return ""
|
||||
if self._daemon.manual:
|
||||
return consts.ACTIVE_PROFILE_MANUAL
|
||||
else:
|
||||
return consts.ACTIVE_PROFILE_AUTO
|
||||
|
||||
@exports.export("", "b")
|
||||
def disable(self, caller = None):
|
||||
if caller == "":
|
||||
|
|
@ -126,7 +144,7 @@ class Controller(tuned.exports.interfaces.ExportableInterface):
|
|||
if self._daemon.is_running():
|
||||
self._daemon.stop()
|
||||
if self._daemon.is_enabled():
|
||||
self._daemon.set_profile(None, save_instantly=True)
|
||||
self._daemon.set_profile(None, None, save_instantly=True)
|
||||
return True
|
||||
|
||||
@exports.export("", "b")
|
||||
|
|
|
|||
|
|
@ -57,36 +57,44 @@ class Daemon(object):
|
|||
self._profile_applied = threading.Event()
|
||||
|
||||
def _init_profile(self, profile_name):
|
||||
manual = True
|
||||
if profile_name is None:
|
||||
profile_name = self._get_active_profile()
|
||||
(profile_name, manual) = self._get_startup_profile()
|
||||
|
||||
self._profile = None
|
||||
self.set_profile(profile_name)
|
||||
self._manual = None
|
||||
self.set_profile(profile_name, manual)
|
||||
|
||||
def set_profile(self, profile_name, save_instantly=False):
|
||||
def set_profile(self, profile_name, manual, save_instantly=False):
|
||||
if self.is_running():
|
||||
raise TunedException(self._notify_profile_changed(profile_name, False, "Cannot set profile while the daemon is running."))
|
||||
|
||||
if profile_name == "" or profile_name is None:
|
||||
self._profile = None
|
||||
self._manual = None
|
||||
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:
|
||||
try:
|
||||
self._profile = self._profile_loader.load(profile_name)
|
||||
self._manual = manual
|
||||
except InvalidProfileException as e:
|
||||
raise TunedException(self._notify_profile_changed(profile_name, False, "Cannot load profile '%s': %s" % (profile_name, e)))
|
||||
|
||||
if save_instantly:
|
||||
if profile_name is None:
|
||||
profile_name = ""
|
||||
self._save_active_profile(profile_name)
|
||||
self._save_active_profile(profile_name, manual)
|
||||
|
||||
@property
|
||||
def profile(self):
|
||||
return self._profile
|
||||
|
||||
@property
|
||||
def manual(self):
|
||||
# manual == None means /etc/tuned/active_profile is empty -> automatic mode
|
||||
return self._manual == True or self._manual is None
|
||||
|
||||
@property
|
||||
def profile_loader(self):
|
||||
return self._profile_loader
|
||||
|
|
@ -103,7 +111,7 @@ class Daemon(object):
|
|||
raise TunedException("Cannot start the daemon without setting a profile.")
|
||||
|
||||
self._unit_manager.create(self._profile.units)
|
||||
self._save_active_profile(self._profile.name)
|
||||
self._save_active_profile(self._profile.name, self._manual)
|
||||
self._unit_manager.start_tuning()
|
||||
self._profile_applied.set()
|
||||
log.info("static tuning from profile '%s' applied" % self._profile.name)
|
||||
|
|
@ -154,10 +162,15 @@ class Daemon(object):
|
|||
self._unit_manager.stop_tuning(full_rollback)
|
||||
self._unit_manager.destroy_all()
|
||||
|
||||
def _save_active_profile(self, profile_name):
|
||||
def _save_active_profile(self, profile_name, manual):
|
||||
try:
|
||||
with open(consts.ACTIVE_PROFILE_FILE, "w") as f:
|
||||
f.write(profile_name + "\n")
|
||||
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)))
|
||||
|
||||
|
|
@ -165,16 +178,33 @@ class Daemon(object):
|
|||
log.info("no profile preset, checking what 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)
|
||||
self._save_active_profile(profile, False)
|
||||
return profile
|
||||
|
||||
def _get_active_profile(self):
|
||||
def _get_startup_profile(self):
|
||||
manual = False
|
||||
try:
|
||||
with open(consts.ACTIVE_PROFILE_FILE, "r") as f:
|
||||
profile = f.read().strip()
|
||||
if profile == "":
|
||||
content = f.read().strip()
|
||||
if content == "":
|
||||
profile = self._set_recommended_profile()
|
||||
return 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
|
||||
|
|
@ -182,10 +212,10 @@ class Daemon(object):
|
|||
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
|
||||
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
|
||||
return (consts.DEFAULT_PROFILE, manual)
|
||||
|
||||
def is_enabled(self):
|
||||
return self._profile is not None
|
||||
|
|
|
|||
|
|
@ -364,40 +364,64 @@ class commands:
|
|||
s = s.zfill(ls)
|
||||
return ",".join(s[i:i + 8] for i in range(0, len(s), 8))
|
||||
|
||||
def process_recommend_file(self, fname):
|
||||
matching_profile = None
|
||||
try:
|
||||
if not os.path.isfile(fname):
|
||||
return None
|
||||
config = ConfigObj(fname, list_values = False, interpolation = False)
|
||||
for section in config.keys():
|
||||
match = True
|
||||
for option in config[section].keys():
|
||||
value = config[section][option]
|
||||
if value == "":
|
||||
value = r"^$"
|
||||
if option == "virt":
|
||||
if not re.match(value, self.execute("virt-what")[1], re.S):
|
||||
match = False
|
||||
elif option == "system":
|
||||
if not re.match(value, self.read_file(consts.SYSTEM_RELEASE_FILE), re.S):
|
||||
match = False
|
||||
elif option[0] == "/":
|
||||
if not os.path.exists(option) or not re.match(value, self.read_file(option), re.S):
|
||||
match = False
|
||||
elif option[0:7] == "process":
|
||||
ps = procfs.pidstats()
|
||||
ps.reload_threads()
|
||||
if len(ps.find_by_regex(re.compile(value))) == 0:
|
||||
match = False
|
||||
if match:
|
||||
# remove the ",.*" suffix
|
||||
r = re.compile(r",[^,]*$")
|
||||
matching_profile = r.sub("", section)
|
||||
break
|
||||
except (IOError, OSError, ConfigObjError) as e:
|
||||
log.error("error processing '%s', %s" % (fname, e))
|
||||
return matching_profile
|
||||
|
||||
def recommend_profile(self, hardcoded = False):
|
||||
profile = consts.DEFAULT_PROFILE
|
||||
if hardcoded:
|
||||
return profile
|
||||
r = re.compile(r",[^,]*$")
|
||||
for f in consts.LOAD_DIRECTORIES:
|
||||
matching = self.process_recommend_file(consts.RECOMMEND_CONF_FILE)
|
||||
if matching is not None:
|
||||
return matching
|
||||
files = {}
|
||||
for directory in consts.RECOMMEND_DIRECTORIES:
|
||||
contents = []
|
||||
try:
|
||||
fname = os.path.join(f, consts.AUTODETECT_FILE)
|
||||
config = ConfigObj(fname, list_values = False, interpolation = False)
|
||||
for section in reversed(config.keys()):
|
||||
match = True
|
||||
for option in config[section].keys():
|
||||
value = config[section][option]
|
||||
if value == "":
|
||||
value = r"^$"
|
||||
if option == "virt":
|
||||
if not re.match(value, self.execute("virt-what")[1], re.S):
|
||||
match = False
|
||||
elif option == "system":
|
||||
if not re.match(value, self.read_file(consts.SYSTEM_RELEASE_FILE), re.S):
|
||||
match = False
|
||||
elif option[0] == "/":
|
||||
if not os.path.exists(option) or not re.match(value, self.read_file(option), re.S):
|
||||
match = False
|
||||
elif option[0:7] == "process":
|
||||
ps = procfs.pidstats()
|
||||
ps.reload_threads()
|
||||
if len(ps.find_by_regex(re.compile(value))) == 0:
|
||||
match = False
|
||||
if match:
|
||||
# remove the ",.*" suffix
|
||||
profile = r.sub("", section)
|
||||
except (IOError, OSError, ConfigObjError) as e:
|
||||
log.error("error parsing '%s', %s" % (fname, e))
|
||||
contents = os.listdir(directory)
|
||||
except OSError as e:
|
||||
if e.errno != errno.ENOENT:
|
||||
log.error("error accessing %s: %s" % (directory, e))
|
||||
for name in contents:
|
||||
path = os.path.join(directory, name)
|
||||
files[name] = path
|
||||
for name in sorted(files.keys()):
|
||||
path = files[name]
|
||||
matching = self.process_recommend_file(path)
|
||||
if matching is not None:
|
||||
return matching
|
||||
return profile
|
||||
|
||||
# Do not make balancing on patched Python 2 interpreter (rhbz#1028122).
|
||||
|
|
|
|||
Loading…
Reference in a new issue