From b4b46f3e190899f1923037dcdaa5dad364cd0a1e Mon Sep 17 00:00:00 2001 From: Jiri Podivin Date: Fri, 30 Apr 2021 10:27:08 +0200 Subject: [PATCH] Retrieve n latest validation results New optional parameter for the CLI limiting number of validation results returned by history subcommand. Resolves: rhbz#1944872 Signed-off-by: Jiri Podivin Change-Id: Ie79062e85351ed545c33001866773bf38fdf8517 --- validations_libs/cli/history.py | 28 ++++++++++++- .../tests/test_validation_actions.py | 41 +++++++++++++++++++ validations_libs/validation_actions.py | 29 ++++++++++++- 3 files changed, 95 insertions(+), 3 deletions(-) diff --git a/validations_libs/cli/history.py b/validations_libs/cli/history.py index 59dcc4d7..b9181afd 100644 --- a/validations_libs/cli/history.py +++ b/validations_libs/cli/history.py @@ -17,6 +17,7 @@ import json import os import sys +from argparse import ArgumentError from cliff.command import Command from cliff.lister import Lister @@ -36,6 +37,15 @@ class ListHistory(Lister): metavar="", type=str, help='Display execution history for a validation') + parser.add_argument('--limit', + dest='history_limit', + type=int, + default=15, + help=( + 'Display most recent ' + 'runs of the selected . ' + ' must be > 0\n' + 'The default display limit is set to 15.\n')) parser.add_argument('--validation-log-dir', dest='validation_log_dir', default=constants.VALIDATIONS_LOG_BASEDIR, help=("Path where the validation log files " @@ -43,8 +53,24 @@ class ListHistory(Lister): return parser def take_action(self, parsed_args): + if parsed_args.history_limit < 1: + raise ArgumentError( + '--limit' + ( + "Number of the most recent runs must be > 0. " + "You have provided {}").format( + parsed_args.history_limit)) + self.app.LOG.info( + ( + "Limiting output to the maximum of " + "{} last validations.").format( + parsed_args.history_limit)) + actions = ValidationActions(parsed_args.validation_log_dir) - return actions.show_history(parsed_args.validation) + + return actions.show_history( + validation_ids=parsed_args.validation, + history_limit=parsed_args.history_limit) class GetHistory(Command): diff --git a/validations_libs/tests/test_validation_actions.py b/validations_libs/tests/test_validation_actions.py index 58d7bf3c..e2a6dbaf 100644 --- a/validations_libs/tests/test_validation_actions.py +++ b/validations_libs/tests/test_validation_actions.py @@ -368,6 +368,47 @@ class TestValidationActions(TestCase): '2019-11-25T13:40:14.404623Z', '0:00:03.753')]) + @mock.patch('os.stat') + @mock.patch('validations_libs.validation_logs.ValidationLogs.' + 'get_all_logfiles', + return_value=[ + '/tmp/123_foo_2020-03-30T13:17:22.447857Z.json', + '/tmp/123_bar_2020-03-05T13:17:22.447857Z.json']) + @mock.patch('json.load', + return_value=fakes.VALIDATIONS_LOGS_CONTENTS_LIST[0]) + @mock.patch('six.moves.builtins.open') + def test_show_history_most_recent(self, mock_open, mock_load, + mock_get_log, mock_stat): + + first_validation = mock.MagicMock() + second_validation = mock.MagicMock() + + first_validation.st_mtime = 5 + second_validation.st_mtime = 7 + + validations = { + '/tmp/123_foo_2020-03-30T13:17:22.447857Z.json': first_validation, + '/tmp/123_bar_2020-03-05T13:17:22.447857Z.json': second_validation + } + + def _generator(x=None): + if x: + return validations[x] + return first_validation + + mock_stat.side_effect = _generator + + v_actions = ValidationActions() + col, values = v_actions.show_history(history_limit=1) + + self.assertEqual(col, ('UUID', 'Validations', + 'Status', 'Execution at', + 'Duration')) + self.assertEqual(values, [('008886df-d297-1eaa-2a74-000000000008', + '512e', 'PASSED', + '2019-11-25T13:40:14.404623Z', + '0:00:03.753')]) + @mock.patch('validations_libs.validation_logs.ValidationLogs.' 'get_logfile_by_validation', return_value=['/tmp/123_foo_2020-03-30T13:17:22.447857Z.json']) diff --git a/validations_libs/validation_actions.py b/validations_libs/validation_actions.py index b7738c93..4724fc7f 100644 --- a/validations_libs/validation_actions.py +++ b/validations_libs/validation_actions.py @@ -203,6 +203,22 @@ class ValidationActions(object): return None, _hosts return playbook, limit_hosts + def _retrieve_latest_results(self, logs, history_limit): + """Retrieve the most recent validation results. + Previously retrieved logs are sorted in ascending order, + with the last time the file was modified serving as a key. + Finally we take the last `n` logs, where `n` == `history_limit` + and return them while discarding the time information. + """ + + history_limit = min(history_limit, len(logs)) + + logs = sorted( + [(os.stat(path).st_mtime, path) for path in logs], + key=lambda path: path[0]) + + return [path[1] for path in logs[-history_limit:]] + def run_validations(self, validation_name=None, inventory='localhost', group=None, extra_vars=None, validations_dir=None, extra_env_vars=None, ansible_cfg=None, quiet=True, @@ -500,7 +516,8 @@ class ValidationActions(object): return params def show_history(self, validation_ids=None, extension='json', - log_path=constants.VALIDATIONS_LOG_BASEDIR): + log_path=constants.VALIDATIONS_LOG_BASEDIR, + history_limit=None): """Return validation executions history :param validation_ids: The validation ids @@ -509,6 +526,9 @@ class ValidationActions(object): :type extension: ``string`` :param log_path: The absolute path of the validations logs directory :type log_path: ``string`` + :param history_limit: The number of most recent history logs + to be displayed. + :type history_limit: ``int`` :return: Returns the information about the validation executions history @@ -559,10 +579,15 @@ class ValidationActions(object): validation_ids = [validation_ids] logs = [] for validation_id in validation_ids: - logs.extend(vlogs.get_logfile_by_validation(validation_id)) + logs.extend( + vlogs.get_logfile_by_validation( + validation_id)) else: logs = vlogs.get_all_logfiles(extension) + if history_limit and history_limit < len(logs): + logs = self._retrieve_latest_results(logs, history_limit) + values = [] column_name = ('UUID', 'Validations', 'Status', 'Execution at',