Skip to content

Commit 1a21d0e

Browse files
committed
[cleaner] Make cleaner concurrent
Allow running cleaner concurrently via child processes. They synchronize on the ordering of items added to dataset of each mapper by creating numbered files in a directory specific for each mapper. Together with deterministic generation of obfuscated values, this ensures the individual processes end up with identical mappings. Resolves: sosreport#3097 Closes: sosreport#3988 Signed-off-by: Pavel Moravec <pmoravec@redhat.com> Signed-off-by: Pablo Fernández Rodríguez <pafernan@redhat.com>
1 parent b15f657 commit 1a21d0e

2 files changed

Lines changed: 45 additions & 88 deletions

File tree

sos/cleaner/__init__.py

Lines changed: 27 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,14 @@
3434
SoSCollectorDirectory)
3535
from sos.cleaner.archives.generic import DataDirArchive, TarballArchive
3636
from sos.cleaner.archives.insights import InsightsArchive
37-
from sos.utilities import (get_human_readable, import_module, ImporterHelper,
38-
file_is_certificate, is_executable)
37+
from sos.utilities import (get_human_readable, import_module, ImporterHelper)
3938

4039

4140
# an auxiliary method to kick off child processes over its instances
4241
def obfuscate_arc_files(arc, flist):
4342
return arc.obfuscate_arc_files(flist)
4443

4544

46-
4745
class SoSCleaner(SoSComponent):
4846
"""
4947
This function is designed to obfuscate potentially sensitive information
@@ -588,34 +586,9 @@ def obfuscate_report_paths(self):
588586
"WARNING: binary files that potentially contain sensitive "
589587
"information will NOT be removed from the final archive\n"
590588
)
591-
if (self.opts.treat_certificates == "obfuscate"
592-
and not is_executable("openssl")):
593-
self.opts.treat_certificates = "remove"
594-
self.ui_log.warning(
595-
"WARNING: No `openssl` command available. Replacing "
596-
"`--treat-certificates` from `obfuscate` to `remove`."
597-
)
598-
if self.opts.treat_certificates == "obfuscate":
599-
self.ui_log.warning(
600-
"WARNING: certificate files that potentially contain "
601-
"sensitive information will be CONVERTED to text and "
602-
"OBFUSCATED in the final archive.\n"
603-
)
604-
elif self.opts.treat_certificates == "keep":
605-
self.ui_log.warning(
606-
"WARNING: certificate files that potentially contain "
607-
"sensitive information will be KEPT in the final "
608-
"archive as is.\n"
609-
)
610-
elif self.opts.treat_certificates == "remove":
611-
self.ui_log.warning(
612-
"WARNING: certificate files that potentially contain "
613-
"sensitive information will be REMOVED in the final "
614-
"archive.\n"
615-
)
616-
for report_path in self.report_paths:
617-
self.ui_log.info(f"Obfuscating {report_path.archive_path}")
618-
self.obfuscate_report(report_path)
589+
pool = ThreadPoolExecutor(self.opts.jobs)
590+
pool.map(self.obfuscate_report, self.report_paths, chunksize=1)
591+
pool.shutdown(wait=True)
619592
# finally, obfuscate the nested archive if one exists
620593
if self.nested_archive:
621594
self._replace_obfuscated_archives()
@@ -732,63 +705,31 @@ def obfuscate_report(self, archive): # pylint: disable=too-many-branches
732705
if not archive.is_extracted:
733706
archive.extract()
734707
archive.report_msg("Beginning obfuscation...")
708+
735709
for fname in archive.get_file_list():
736710
short_name = fname.split(archive.archive_name + '/')[1]
737-
is_certificate = file_is_certificate(fname)
738-
if is_certificate:
739-
if is_certificate == "certificatekey":
740-
# Always remove certificate Key files
741-
archive.remove_file(short_name)
742-
continue
743-
if self.opts.treat_certificates == "keep":
744-
continue
745-
if self.opts.treat_certificates == "remove":
746-
archive.remove_file(short_name)
747-
continue
748-
if self.opts.treat_certificates == "obfuscate":
749-
archive.certificate_to_text(fname)
750-
751-
file_list = list(archive.get_files())
752-
# we can't call simple
753-
# executor.map(archive.obfuscate_arc_files,archive.get_files())
754-
# because a child process does not carry forward internal changes
755-
# (e.g. mappings' datasets) from one call of obfuscate_arc_files
756-
# method to another. Each obfuscate_arc_files method starts with
757-
# vanilla parent archive, that is initialised *once* at its
758-
# beginning via initializer=archive.load_parser_entries
759-
# - but not afterwards..
760-
#
761-
# So we must pass list of all files for each worker at the
762-
# beginning. This means less granularity of the child processes
763-
# work (one worker can finish much sooner than the other), but
764-
# it is the best we can have (or have found)
765-
#
766-
# At least, the "file_list[i::self.opts.jobs]" means subsequent
767-
# files (speculativelly of similar size and content) are
768-
# distributed to different processes, which attempts to split the
769-
# load evenly. Yet better approach might be reorderig file_list
770-
# based on files' sizes.
771-
772-
files_obfuscated_count = total_sub_count = removed_file_count = 0
773-
archive_list = [archive for i in range(self.opts.jobs)]
774-
with ProcessPoolExecutor(
775-
max_workers=self.opts.jobs,
776-
initializer=archive.load_parser_entries) as executor:
777-
futures = executor.map(obfuscate_arc_files, archive_list,
778-
[file_list[i::self.opts.jobs] for i in
779-
range(self.opts.jobs)])
780-
for (foc, tsc, rfc) in futures:
781-
files_obfuscated_count += foc
782-
total_sub_count += tsc
783-
removed_file_count += rfc
784-
785-
# As there is no easy way to get dataset dicts from child
786-
# processes' mappings, we can reload our own parent-process
787-
# archive from the disk files. The trick is that sequence of
788-
# files/entries is the source of truth of *sequence* of calling
789-
# *all* mapping.all(item) methods - so replaying this will
790-
# generate the right datasets!
791-
archive.load_parser_entries()
711+
if archive.should_skip_file(short_name):
712+
continue
713+
if (not self.opts.keep_binary_files and
714+
archive.should_remove_file(short_name)):
715+
# We reach this case if the option --keep-binary-files
716+
# was not used, and the file is in a list to be removed
717+
archive.remove_file(short_name)
718+
continue
719+
if (self.opts.keep_binary_files and
720+
(file_is_binary(fname) or
721+
archive.should_remove_file(short_name))):
722+
# We reach this case if the option --keep-binary-files
723+
# is used. In this case we want to make sure
724+
# the cleaner doesn't try to clean a binary file
725+
continue
726+
try:
727+
count = self.obfuscate_file(fname, short_name,
728+
archive.archive_name)
729+
if count:
730+
archive.update_sub_count(short_name, count)
731+
except Exception as err:
732+
self.log_debug(f"Unable to parse file {short_name}: {err}")
792733

793734
try:
794735
self.obfuscate_directory_names(archive)

sos/cleaner/archives/__init__.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
import tempfile
1818

1919
from concurrent.futures import ProcessPoolExecutor
20-
from sos.utilities import file_is_binary, sos_get_command_output
20+
from sos.utilities import (file_is_binary, sos_get_command_output,
21+
file_is_certificate)
2122

2223

2324
# python older than 3.8 will hit a pickling error when we go to spawn a new
@@ -63,7 +64,8 @@ class SoSObfuscationArchive():
6364
is_nested = False
6465
prep_files = {}
6566

66-
def __init__(self, archive_path, tmpdir, keep_binary_files):
67+
def __init__(self, archive_path, tmpdir, keep_binary_files,
68+
treat_certificates):
6769
self.archive_path = archive_path
6870
self.final_archive_path = self.archive_path
6971
self.tmpdir = tmpdir
@@ -76,6 +78,7 @@ def __init__(self, archive_path, tmpdir, keep_binary_files):
7678
self._load_self()
7779
self.archive_root = ''
7880
self.keep_binary_files = keep_binary_files
81+
self.treat_certificates = treat_certificates
7982
self.parsers = ()
8083
self.log_info(
8184
f"Loaded {self.archive_path} as type {self.description}"
@@ -173,6 +176,19 @@ def obfuscate_arc_files(self, flist):
173176
# don't run the obfuscation on the link, but on the actual
174177
# file at some other point.
175178
continue
179+
is_certificate = file_is_certificate(filename)
180+
if is_certificate:
181+
if is_certificate == "certificatekey":
182+
# Always remove certificate Key files
183+
self.remove_file(short_name)
184+
continue
185+
if self.treat_certificates == "keep":
186+
continue
187+
if self.treat_certificates == "remove":
188+
self.remove_file(short_name)
189+
continue
190+
if self.treat_certificates == "obfuscate":
191+
self.certificate_to_text(filename)
176192
_parsers = [
177193
_p for _p in self.parsers if not
178194
any(

0 commit comments

Comments
 (0)