Validation actions refactor

Unused workdir argument of the run_validations method removed.

Host and playbook skipping simplified.

Docstrings adjusted, including some of the more important
inline code examples.

Test sensitivity to API changes was increased.
More strict assertions have been put on the calls made in CLI.

Logging statements related to the run action
in validation_actions.py were expanded.

New debug level logging statements introduced.

One of the testing constants in the fakes.py was altered
as it was not accurate representation of the real data,
and as such was breaking the new logging statements
during unit test runs.

Deprecation notice for 'log_path' and 'groups' arguments,
in both docstring and logging.

Redundant conditionals for argument types were removed.

The checks of the arguments are implemented
in the function called in the subsequent statement.

As there is no scenario in which one set of these checks
passes, while the other fails with the same values,
we can consider one of them to be redundant.

Helper function 'get_validations_details' without active references
was removed from utils submodule.

Signed-off-by: Jiri Podivin <jpodivin@redhat.com>
Change-Id: I781f6a6f9fc4bd558af56b648f0e0ee9f165dfab
(cherry picked from commit b24d4ce792)
This commit is contained in:
Jiri Podivin 2021-07-28 16:48:40 +02:00
parent 8d2e7c2bc9
commit 154b7b0020
14 changed files with 348 additions and 281 deletions

View File

@ -62,11 +62,10 @@ class ListHistory(BaseLister):
("Limiting output to the maximum of "
"{} last validations.").format(history_limit))
actions = ValidationActions()
actions = ValidationActions(log_path=parsed_args.validation_log_dir)
return actions.show_history(
validation_ids=parsed_args.validation,
log_path=parsed_args.validation_log_dir,
history_limit=history_limit)

View File

@ -171,7 +171,8 @@ class Run(BaseCommand):
# Get config:
config = self.base.config
v_actions = ValidationActions(parsed_args.validation_dir)
v_actions = ValidationActions(
parsed_args.validation_dir, log_path=parsed_args.validation_log_dir)
# Ansible execution should be quiet while using the validations_json
# default callback and be verbose while passing ANSIBLE_SDTOUT_CALLBACK
# environment variable to Ansible through the --extra-env-vars argument
@ -213,7 +214,6 @@ class Run(BaseCommand):
python_interpreter=parsed_args.python_interpreter,
quiet=quiet_mode,
ssh_user=parsed_args.ssh_user,
log_path=parsed_args.validation_log_dir,
validation_config=config,
skip_list=skip_list)
except RuntimeError as e:

View File

@ -71,8 +71,8 @@ class ShowGroup(BaseLister):
self.base.set_argument_parser(self, parsed_args)
v_actions = ValidationActions(parsed_args.validation_dir)
return v_actions.group_information(
constants.VALIDATION_GROUPS_INFO,
validation_config=self.base.config)

View File

@ -68,10 +68,10 @@ class Group(object):
return self.data
@property
def get_formated_group(self):
"""Get a formated content for output display
def get_formated_groups(self):
"""Get a formated list of groups for output display
:return:
:return: information about parsed groups
:rtype: `list` of `tuples`
:Example:

View File

@ -29,7 +29,8 @@ class TestListHistory(BaseCommand):
self.cmd = history.ListHistory(self.app, None)
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'show_history')
'show_history',
autospec=True)
def test_list_history(self, mock_history):
arglist = ['--validation-log-dir', '/foo/log/dir']
verifylist = [('validation_log_dir', '/foo/log/dir')]
@ -80,7 +81,8 @@ class TestGetHistory(BaseCommand):
@mock.patch('validations_libs.validation_logs.ValidationLogs.'
'get_logfile_content_by_uuid',
return_value=fakes.VALIDATIONS_LOGS_CONTENTS_LIST)
return_value=fakes.VALIDATIONS_LOGS_CONTENTS_LIST,
autospec=True)
def test_get_history(self, mock_logs):
arglist = ['123']
verifylist = [('uuid', '123')]
@ -91,7 +93,8 @@ class TestGetHistory(BaseCommand):
@mock.patch('validations_libs.validation_logs.ValidationLogs.'
'get_logfile_content_by_uuid',
return_value=fakes.VALIDATIONS_LOGS_CONTENTS_LIST)
return_value=fakes.VALIDATIONS_LOGS_CONTENTS_LIST,
autospec=True)
def test_get_history_from_log_dir(self, mock_logs):
arglist = ['123', '--validation-log-dir', '/foo/log/dir']
verifylist = [('uuid', '123'), ('validation_log_dir', '/foo/log/dir')]
@ -102,7 +105,8 @@ class TestGetHistory(BaseCommand):
@mock.patch('validations_libs.validation_logs.ValidationLogs.'
'get_logfile_content_by_uuid',
return_value=fakes.VALIDATIONS_LOGS_CONTENTS_LIST)
return_value=fakes.VALIDATIONS_LOGS_CONTENTS_LIST,
autospec=True)
def test_get_history_full_arg(self, mock_logs):
arglist = ['123', '--full']
verifylist = [('uuid', '123'), ('full', True)]

View File

@ -30,7 +30,8 @@ class TestList(BaseCommand):
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'list_validations',
return_value=fakes.VALIDATIONS_LIST)
return_value=fakes.VALIDATIONS_LIST,
autospec=True)
def test_list_validations(self, mock_list):
arglist = ['--validation-dir', 'foo']
verifylist = [('validation_dir', 'foo')]
@ -59,7 +60,8 @@ class TestList(BaseCommand):
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'list_validations',
return_value=[])
return_value=[],
autospec=True)
def test_list_validations_empty(self, mock_list):
arglist = ['--validation-dir', 'foo']
verifylist = [('validation_dir', 'foo')]
@ -69,7 +71,8 @@ class TestList(BaseCommand):
self.assertEqual(result, [])
@mock.patch('validations_libs.utils.parse_all_validations_on_disk',
return_value=fakes.VALIDATIONS_LIST_GROUP)
return_value=fakes.VALIDATIONS_LIST_GROUP,
autospec=True)
def test_list_validations_group(self, mock_list):
arglist = ['--validation-dir', 'foo', '--group', 'prep']
verifylist = [('validation_dir', 'foo'),
@ -82,7 +85,8 @@ class TestList(BaseCommand):
self.assertEqual(result, val_list)
@mock.patch('validations_libs.utils.parse_all_validations_on_disk',
return_value=fakes.VALIDATIONS_LIST_GROUP)
return_value=fakes.VALIDATIONS_LIST_GROUP,
autospec=True)
def test_list_validations_by_category(self, mock_list):
arglist = ['--validation-dir', 'foo', '--category', 'networking']
verifylist = [('validation_dir', 'foo'),
@ -95,7 +99,8 @@ class TestList(BaseCommand):
self.assertEqual(result, val_list)
@mock.patch('validations_libs.utils.parse_all_validations_on_disk',
return_value=fakes.VALIDATIONS_LIST_GROUP)
return_value=fakes.VALIDATIONS_LIST_GROUP,
autospec=True)
def test_list_validations_by_product(self, mock_list):
arglist = ['--validation-dir', 'foo', '--product', 'product1']
verifylist = [('validation_dir', 'foo'),

View File

@ -32,7 +32,8 @@ class TestRun(BaseCommand):
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=None)
return_value=None,
autospec=True)
def test_run_command_return_none(self, mock_run):
args = self._set_args(['--validation', 'foo'])
verifylist = [('validation_name', ['foo'])]
@ -43,7 +44,8 @@ class TestRun(BaseCommand):
@mock.patch('validations_libs.cli.common.open')
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN))
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN),
autospec=True)
def test_run_command_success(self, mock_run, mock_open):
args = self._set_args(['--validation', 'foo'])
verifylist = [('validation_name', ['foo'])]
@ -65,10 +67,12 @@ class TestRun(BaseCommand):
return_value='doe')
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN))
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN),
autospec=True)
@mock.patch('validations_libs.utils.load_config', return_value={})
def test_run_command_extra_vars(self, mock_config, mock_run,
mock_user, mock_print, mock_log_dir):
def test_run_command_extra_vars(self, mock_config,
mock_run, mock_user,
mock_print, mock_log_dir):
run_called_args = {
'inventory': 'localhost',
'limit_hosts': None,
@ -83,7 +87,6 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir,
'validation_config': {},
'skip_list': None
}
@ -95,7 +98,9 @@ class TestRun(BaseCommand):
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_run.assert_called_with(**run_called_args)
call_args = mock_run.mock_calls[0][2]
self.assertDictEqual(call_args, run_called_args)
@mock.patch('validations_libs.constants.VALIDATIONS_LOG_BASEDIR')
@mock.patch('validations_libs.cli.common.print_dict')
@ -103,10 +108,11 @@ class TestRun(BaseCommand):
return_value='doe')
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN))
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN),
autospec=True)
@mock.patch('validations_libs.utils.load_config', return_value={})
def test_run_command_extra_vars_twice(self, mock_config,
mock_run, mock_user, mock_print,
def test_run_command_extra_vars_twice(self, mock_config, mock_run,
mock_user, mock_print,
mock_log_dir):
run_called_args = {
'inventory': 'localhost',
@ -122,7 +128,6 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir,
'validation_config': {},
'skip_list': None
}
@ -135,7 +140,9 @@ class TestRun(BaseCommand):
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_run.assert_called_with(**run_called_args)
call_args = mock_run.mock_calls[0][2]
self.assertDictEqual(call_args, run_called_args)
def test_run_command_exclusive_vars(self):
arglist = ['--validation', 'foo',
@ -154,7 +161,8 @@ class TestRun(BaseCommand):
return_value='doe')
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN))
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN),
autospec=True)
@mock.patch('validations_libs.utils.load_config', return_value={})
def test_run_command_extra_vars_file(self, mock_config, mock_run,
mock_user, mock_open,
@ -174,7 +182,6 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir,
'validation_config': {},
'skip_list': None
}
@ -186,14 +193,17 @@ class TestRun(BaseCommand):
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_run.assert_called_with(**run_called_args)
call_args = mock_run.mock_calls[0][2]
self.assertDictEqual(call_args, run_called_args)
@mock.patch('validations_libs.constants.VALIDATIONS_LOG_BASEDIR')
@mock.patch('getpass.getuser',
return_value='doe')
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN))
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN),
autospec=True)
@mock.patch('validations_libs.utils.load_config', return_value={})
def test_run_command_extra_env_vars(self, mock_config, mock_run,
mock_user, mock_log_dir):
@ -211,7 +221,6 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir,
'validation_config': {},
'skip_list': None
}
@ -223,14 +232,17 @@ class TestRun(BaseCommand):
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_run.assert_called_with(**run_called_args)
call_args = mock_run.mock_calls[0][2]
self.assertDictEqual(call_args, run_called_args)
@mock.patch('validations_libs.constants.VALIDATIONS_LOG_BASEDIR')
@mock.patch('getpass.getuser',
return_value='doe')
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN))
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN),
autospec=True)
@mock.patch('validations_libs.utils.load_config', return_value={})
def test_run_command_extra_env_vars_with_custom_callback(self,
mock_config,
@ -240,7 +252,6 @@ class TestRun(BaseCommand):
run_called_args = {
'inventory': 'localhost',
'limit_hosts': None,
'log_path': mock_log_dir,
'quiet': False,
'group': [],
'category': [],
@ -264,14 +275,17 @@ class TestRun(BaseCommand):
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_run.assert_called_with(**run_called_args)
call_args = mock_run.mock_calls[0][2]
self.assertDictEqual(call_args, run_called_args)
@mock.patch('validations_libs.constants.VALIDATIONS_LOG_BASEDIR')
@mock.patch('getpass.getuser',
return_value='doe')
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN))
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN),
autospec=True)
@mock.patch('validations_libs.utils.load_config', return_value={})
def test_run_command_extra_env_vars_twice(self, mock_config,
mock_run, mock_user,
@ -290,7 +304,6 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir,
'validation_config': {},
'skip_list': None
}
@ -303,14 +316,17 @@ class TestRun(BaseCommand):
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_run.assert_called_with(**run_called_args)
call_args = mock_run.mock_calls[0][2]
self.assertDictEqual(call_args, run_called_args)
@mock.patch('validations_libs.constants.VALIDATIONS_LOG_BASEDIR')
@mock.patch('getpass.getuser',
return_value='doe')
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN))
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN),
autospec=True)
@mock.patch('validations_libs.utils.load_config', return_value={})
def test_run_command_extra_env_vars_and_extra_vars(self,
mock_config,
@ -331,7 +347,6 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir,
'validation_config': {},
'skip_list': None
}
@ -345,7 +360,9 @@ class TestRun(BaseCommand):
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_run.assert_called_with(**run_called_args)
call_args = mock_run.mock_calls[0][2]
self.assertDictEqual(call_args, run_called_args)
def test_run_command_exclusive_wrong_extra_vars(self):
arglist = ['--validation', 'foo',
@ -356,50 +373,18 @@ class TestRun(BaseCommand):
self.assertRaises(Exception, self.check_parser, self.cmd,
arglist, verifylist)
@mock.patch('validations_libs.utils.find_config_file',
return_value="/etc/validations_foo.cfg")
@mock.patch('validations_libs.constants.VALIDATIONS_LOG_BASEDIR')
@mock.patch('getpass.getuser',
return_value='doe')
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=copy.deepcopy(fakes.FAKE_FAILED_RUN))
return_value=copy.deepcopy(fakes.FAKE_FAILED_RUN),
autospec=True)
@mock.patch('validations_libs.utils.load_config', return_value={})
def test_run_command_failed_validation(self, mock_config,
mock_run, mock_user, mock_log_dir):
run_called_args = {
'inventory': 'localhost',
'limit_hosts': None,
'group': [],
'category': [],
'product': [],
'extra_vars': None,
'validations_dir': '/usr/share/ansible/validation-playbooks',
'base_dir': '/usr/share/ansible',
'validation_name': ['foo'],
'extra_env_vars': None,
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir,
'validation_config': {},
'skip_list': None
}
arglist = ['--validation', 'foo']
verifylist = [('validation_name', ['foo'])]
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(RuntimeError, self.cmd.take_action, parsed_args)
mock_run.assert_called_with(**run_called_args)
@mock.patch('getpass.getuser',
return_value='doe')
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=[])
@mock.patch('validations_libs.utils.load_config', return_value={})
def test_run_command_no_validation(self, mock_config, mock_run,
mock_user):
def test_run_command_failed_validation(self, mock_config, mock_run, mock_user,
mock_log_dir, mock_config_file):
run_called_args = {
'inventory': 'localhost',
'limit_hosts': None,
@ -418,8 +403,56 @@ class TestRun(BaseCommand):
'skip_list': None
}
arglist = ['--validation', 'foo']
verifylist = [('validation_name', ['foo'])]
arglist = [
'--validation', 'foo',
'--extra-vars', 'key=value',
'--extra-env-vars', 'key2=value2']
verifylist = [
('validation_name', ['foo']),
('extra_vars', {'key': 'value'}),
('extra_env_vars', {'key2': 'value2'})]
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(RuntimeError, self.cmd.take_action, parsed_args)
call_args = mock_run.mock_calls[0][2]
self.assertDictEqual(call_args, run_called_args)
@mock.patch('validations_libs.constants.VALIDATIONS_LOG_BASEDIR')
@mock.patch('getpass.getuser',
return_value='doe')
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=[],
autospec=True)
def test_run_command_no_validation(self, mock_run, mock_user, mock_log_dir):
run_called_args = {
'inventory': 'localhost',
'limit_hosts': None,
'group': [],
'category': [],
'product': [],
'extra_vars': {'key': 'value'},
'validations_dir': '/usr/share/ansible/validation-playbooks',
'base_dir': '/usr/share/ansible',
'validation_name': ['foo'],
'extra_env_vars': {'key2': 'value2'},
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'validation_config': {},
'skip_list': None,
'log_path': mock_log_dir}
arglist = [
'--validation', 'foo',
'--extra-vars', 'key=value',
'--extra-env-vars', 'key2=value2']
verifylist = [
('validation_name', ['foo']),
('extra_vars', {'key': 'value'}),
('extra_env_vars', {'key2': 'value2'})]
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@ -451,7 +484,6 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir,
'validation_config': {},
'skip_list': None
}
@ -489,7 +521,6 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir,
'validation_config': {},
'skip_list': None
}
@ -526,7 +557,6 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir,
'validation_config': {},
'skip_list': {'key': 'value'}
}

View File

@ -17,6 +17,8 @@ try:
except ImportError:
import mock
from validations_libs import group
from validations_libs.validation_actions import ValidationActions
from validations_libs.cli import show
from validations_libs.tests import fakes
from validations_libs.tests.cli.fakes import BaseCommand
@ -48,22 +50,17 @@ class TestShowGroup(BaseCommand):
@mock.patch('yaml.safe_load', return_value=fakes.GROUP)
@mock.patch('six.moves.builtins.open')
def test_show_validations_group_info(self, mock_open, mock_yaml, mock_actions):
arglist = []
mock_info = mock.MagicMock()
mock_info.group_information = mock.MagicMock(return_value='foo')
mock_actions.return_value = mock_info
method_calls = [
mock.call(fakes.FAKE_VALIDATIONS_PATH),
mock.call().group_information(validation_config={})]
arglist = []
parsed_args = self.check_parser(self.cmd, arglist, [])
group_info = self.cmd.take_action(parsed_args)
mock_actions.assert_called_once_with(
validation_path=fakes.FAKE_VALIDATIONS_PATH)
mock_info.group_information.assert_called_once()
self.assertEqual('foo', group_info)
self.cmd.take_action(parsed_args)
mock_actions.assert_called_with(fakes.FAKE_VALIDATIONS_PATH)
class TestShowParameter(BaseCommand):
@ -73,10 +70,15 @@ class TestShowParameter(BaseCommand):
self.cmd = show.ShowParameter(self.app, None)
@mock.patch('six.moves.builtins.open')
def test_show_validations_parameters_by_group(self, mock_open):
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'show_validations_parameters', autospec=True)
def test_show_validations_parameters_by_group(self, mock_show, mock_open):
arglist = ['--group', 'prep']
verifylist = [('group', ['prep'])]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_show.assert_called_once()
def test_show_parameter_exclusive_group(self):
arglist = ['--validation', 'foo', '--group', 'bar']
@ -86,23 +88,32 @@ class TestShowParameter(BaseCommand):
arglist, verifylist)
@mock.patch('six.moves.builtins.open')
def test_show_validations_parameters_by_validations(self, mock_open):
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'show_validations_parameters', autospec=True)
def test_show_validations_parameters_by_validations(self, mock_show, mock_open):
arglist = ['--group', 'prep']
verifylist = [('group', ['prep'])]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_show.assert_called_once()
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'show_validations_parameters')
'show_validations_parameters', autospec=True)
def test_show_validations_parameters_by_categories(self, mock_show):
arglist = ['--category', 'os']
verifylist = [('category', ['os'])]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_show.assert_called_once()
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'show_validations_parameters')
'show_validations_parameters', autospec=True)
def test_show_validations_parameters_by_products(self, mock_show):
arglist = ['--product', 'product1']
verifylist = [('product', ['product1'])]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_show.assert_called_once()

View File

@ -57,9 +57,9 @@ VALIDATION_LIST_RESULT = (('ID', 'Name', 'Groups', 'Categories', 'Products'),
['product1'])])
GROUPS_LIST = [
('group1', 'Group1 description'),
('group2', 'Group2 description'),
('group3', 'Group3 description'),
'group1',
'group2',
'group3'
]
BAD_VALIDATIONS_LOGS_CONTENTS_LIST = [{

View File

@ -40,7 +40,7 @@ class TestGroup(TestCase):
def test_get_formated_group(self, mock_open, mock_yaml):
grp = Group('/tmp/foo')
ret = [('no-op', 'noop-foo'), ('post', 'post-foo'), ('pre', 'pre-foo')]
data = grp.get_formated_group
data = grp.get_formated_groups
self.assertEqual(data, ret)
@mock.patch('yaml.safe_load', return_value=fakes.GROUP)

View File

@ -354,22 +354,6 @@ class TestUtils(TestCase):
result = utils.get_validation_group_name_list('/foo/groups.yaml')
self.assertEqual(result, ['no-op', 'post', 'pre'])
@mock.patch('validations_libs.utils.parse_all_validations_on_disk',
return_value=[fakes.FAKE_METADATA])
@mock.patch('yaml.safe_load', return_value=fakes.GROUP)
@mock.patch('six.moves.builtins.open')
def test_get_validations_details(self, mock_open, mock_load, mock_parse):
result = utils.get_validations_details('foo')
self.assertEqual(result, fakes.FAKE_METADATA)
@mock.patch('six.moves.builtins.open')
def test_get_validations_details_wrong_type(self, mock_open):
validation = ['foo']
self.assertRaises(TypeError,
utils.get_validations_details,
validation=validation)
def test_get_validations_parameters_wrong_validations_data_type(self):
self.assertRaises(TypeError,
utils.get_validations_parameters,

View File

@ -20,8 +20,6 @@ except ImportError:
import mock
from mock import ANY
import json
from unittest import TestCase
from validations_libs.tests import fakes
@ -62,8 +60,7 @@ class TestValidationActions(TestCase):
skip_list = {'fake': {'hosts': 'ALL',
'reason': None,
'lp': None
}
}
}}
run = ValidationActions()
run_return = run.run_validations(playbook, inventory,
@ -124,9 +121,8 @@ class TestValidationActions(TestCase):
}
}
run = ValidationActions()
run = ValidationActions(log_path='/var/log/validations')
run_return = run.run_validations(playbook, inventory,
log_path='/var/log/validations',
validations_dir='/tmp/foo',
skip_list=skip_list,
limit_hosts='!cloud1')
@ -185,9 +181,8 @@ class TestValidationActions(TestCase):
}
}
run = ValidationActions()
run = ValidationActions(log_path='/var/log/validations')
run_return = run.run_validations(playbook, inventory,
log_path='/var/log/validations',
validations_dir='/tmp/foo',
skip_list=skip_list,
limit_hosts='cloud,cloud1,!cloud2')
@ -256,9 +251,8 @@ class TestValidationActions(TestCase):
run = ValidationActions()
self.assertRaises(RuntimeError, run.run_validations,
validation_name='fake.yaml',
validations_dir='/tmp/foo'
)
validation_name=['fake'],
validations_dir='/tmp/foo')
@mock.patch('validations_libs.utils.get_validations_playbook')
def test_validation_run_not_all_found(self, mock_validation_play):
@ -271,7 +265,7 @@ class TestValidationActions(TestCase):
validations_dir='/tmp/foo')
except RuntimeError as runtime_error:
self.assertEqual(
"Validation ['foo'] not found in /tmp/foo.",
"Following validations were not found in '/tmp/foo': foo",
str(runtime_error))
else:
self.fail("Runtime error exception should have been raised")
@ -435,7 +429,7 @@ class TestValidationActions(TestCase):
@mock.patch('six.moves.builtins.open')
def test_group_information(self, mock_open, mock_yaml, mock_data):
v_actions = ValidationActions()
col, values = v_actions.group_information('512e')
col, values = v_actions.group_information()
self.assertEqual(col, ('Groups', 'Description',
'Number of Validations'))
self.assertEqual(values, [('no-op', 'noop-foo', 1),

View File

@ -382,36 +382,6 @@ def get_validation_group_name_list(groups_path=None):
return gp.get_groups_keys_list
def get_validations_details(validation):
"""Return information details for a validation
:param validation: Name of the validation
:type validation: `string`
:return: The information of the validation
:rtype: `dict`
:raises: a `TypeError` exception if `validation` is not a string
:Example:
>>> validation = "check-something"
>>> get_validations_details(validation)
{'description': 'Verify that the server has enough something.',
'groups': ['group1', 'group2'],
'categories': ['category1', 'category2'],
'products': ['product1', 'product2'],
'id': 'check-something',
'name': 'Verify the server fits the something requirements'}
"""
if not isinstance(validation, six.string_types):
raise TypeError("The 'validation' argument must be a String")
results = parse_all_validations_on_disk(constants.ANSIBLE_VALIDATION_DIR)
for r in results:
if r['id'] == validation:
return r
return {}
def get_validations_data(
validation,
path=constants.ANSIBLE_VALIDATION_DIR,

View File

@ -42,10 +42,23 @@ class ValidationActions(object):
"""
def __init__(self, validation_path=constants.ANSIBLE_VALIDATION_DIR):
def __init__(self, validation_path=constants.ANSIBLE_VALIDATION_DIR,
groups_path=constants.VALIDATION_GROUPS_INFO,
log_path=constants.VALIDATIONS_LOG_BASEDIR):
"""
:param groups_path: The absolute path to the validation groups
definition file.
(Defaults to ``constants.VALIDATION_GROUPS_INFO``)
:type groups_path: ``string``
:param log_path: The absolute path of the validations logs directory
(Defaults to ``constants.VALIDATIONS_LOG_BASEDIR``)
:type log_path: ``string``
"""
self.log = logging.getLogger(__name__ + ".ValidationActions")
self.validation_path = validation_path
self.log_path = log_path
self.groups_path = groups_path
def list_validations(self,
groups=None,
@ -84,7 +97,7 @@ class ValidationActions(object):
| val3 | val_name3 | ['group4'] | ['category3'] | ['product3'] |
+------+-----------+----------------------+---------------+--------------+
:Example:
:example:
>>> path = "/foo/bar"
>>> groups = ['group1']
@ -130,13 +143,16 @@ class ValidationActions(object):
return (column_names, return_values)
def show_validations(self, validation,
log_path=constants.VALIDATIONS_LOG_BASEDIR,
log_path=None,
validation_config=None):
"""Display detailed information about a Validation
:param validation: The name of the validation
:type validation: `string`
:param log_path: The absolute path of the validations logs
:param log_path: The absolute path of the validations logs.
The 'log_path' argument is deprecated and
will be removed in the next release.
Use the 'log_path' argument of the init method.
:type log_path: `string`
:param validation_config: A dictionary of configuration for Validation
loaded from an validation.cfg file.
@ -146,7 +162,7 @@ class ValidationActions(object):
:return: The detailed information for a validation
:rtype: `dict`
:Example:
:example:
>>> path = "/foo/bar"
>>> validation = 'foo'
@ -166,7 +182,14 @@ class ValidationActions(object):
"""
self.log = logging.getLogger(__name__ + ".show_validations")
# Get validation data:
vlog = ValidationLogs(log_path)
if log_path:
self.log.warning((
"The 'log_path' argument is deprecated and"
" will be removed in the next release. "
"Use the 'log_path' argument of the init method."))
vlog = ValidationLogs(log_path)
else:
vlog = ValidationLogs(self.log_path)
data = v_utils.get_validations_data(
validation,
self.validation_path,
@ -185,75 +208,91 @@ class ValidationActions(object):
data.update(data_format)
return data
def _skip_hosts(self, skip_list, playbook, limit_hosts=None):
def _skip_hosts(self, skip_list, limit_hosts=None):
"""Check Ansible Hosts and return an updated limit_hosts
:param skip_list: The list of the validation to skip
:type validation_name: ``dict``
:param playbook: The name of the playbook
:type base_dir: ``string``
:param skip_list: list of hosts to skip with reasons why
:type skip_list: `dict`
:param limit_hosts: Limit the execution to the hosts.
:type limit_hosts: ``string``
:return the limit hosts according the skip_list or None if the
validation should be skipped on ALL hosts.
:example
limit_hosts = 'cloud1,cloud2'
skip_list = {'xyz': {'hosts': 'cloud1',
'reason': None,
'lp': None}
}
>>> _skip_hosts(skip_list, playbook, limit_hosts='cloud1,cloud2')
'cloud2,!cloud1'
:example:
>>> v_actions = ValidationActions()
>>> limit_hosts = 'cloud1,cloud2'
>>> skip_list = {
... 'xyz': {
... 'hosts': 'cloud1',
... 'reason': None,
... 'lp': None}}
>>> v_actions._skip_hosts(skip_list, validation, limit_hosts='cloud1,cloud2')
'!cloud1,cloud2'
"""
hosts = skip_list[playbook].get('hosts', 'all')
hosts = skip_list.get('hosts', 'all')
if hosts.lower() == 'all':
return None
else:
_hosts = ['!{}'.format(hosts)]
if limit_hosts:
# check if skipped hosts is already in limit host
_hosts.extend([limit for limit in limit_hosts.split(',')
if hosts not in limit])
return ','.join(_hosts)
_hosts = ['!{}'.format(hosts)]
if limit_hosts:
# check if skipped hosts is already in limit host
_hosts.extend([limit for limit in limit_hosts.split(',')
if hosts not in limit])
return ','.join(_hosts)
def _skip_playbook(self, skip_list, playbook, limit_hosts=None):
"""Check if playbook is in the ski plist
:param skip_list: The list of the validation to skip
:type validation_name: ``dict``
"""Check if playbook is in the skiplist
:param skip_list: Dictionary of validations to skip.
:type skip_list: `dictionary`
:param playbook: The name of the playbook
:type base_dir: ``string``
:type playbook: `string`
:param limit_hosts: Limit the execution to the hosts.
:type limit_hosts: ``string``
:type limit_hosts: `string`
:return a tuple of playbook and hosts
:example
skip_list = {'xyz': {'hosts': 'cloud1',
'reason': None,
'lp': None}
}
If playbook not in skip list:
>>> _skip_playbook(skip_list, 'foo', None)
('foo', None)
:rtype: `tuple`
If playbook in the skip list, but with restriction only on
host cloud1:
>>> _skip_playbook(skip_list, 'xyz', None)
('xyz', '!cloud1')
:example:
If playbook in the skip list, and should be skip on ALL hosts:
skip_list = {'xyz': {'hosts': 'ALL',
'reason': None,
'lp': None}
}
>>> _skip_playbook(skip_list, 'xyz', None)
(None, None)
>>> skip_list = {
... 'xyz': {
... 'hosts': 'cloud1',
... 'reason': None,
... 'lp': None}}
If playbook is not in skip list:
>>> v_actions = ValidationActions()
>>> v_actions._skip_playbook(skip_list, 'foo', None)
('foo', None)
If playbook is in the skip list, but with restriction only on
host cloud1:
>>> v_actions = ValidationActions()
>>> v_actions._skip_playbook(skip_list, 'xyz', None)
('xyz', '!cloud1')
If playbook in the skip list, and should be skip on ALL hosts:
>>> skip_list = {
... 'xyz': {
... 'hosts': 'ALL',
... 'reason': None,
... 'lp': None}}
>>> v_actions = ValidationActions()
>>> v_actions._skip_playbook(skip_list, 'xyz', None)
(None, None)
"""
if skip_list:
if playbook in skip_list.keys():
_hosts = self._skip_hosts(skip_list, playbook,
limit_hosts)
if playbook in skip_list:
self.log.info((
"Validation '{}' skipped on following hosts '{}' "
"with reason: '{}'.").format(
playbook,
skip_list[playbook].get('hosts', 'All'),
skip_list[playbook].get('reason', None)))
_hosts = self._skip_hosts(
skip_list[playbook],
limit_hosts)
if _hosts:
return playbook, _hosts
else:
@ -266,6 +305,14 @@ class ValidationActions(object):
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.
:param logs: List of validation log file paths
:type logs: `list`
:param history_limit: number of entries to display
:type history_limit: `int`
:return: List of time-modified, path tuples of length =< history_limit
:rtype: `list`
"""
history_limit = min(history_limit, len(logs))
@ -280,17 +327,16 @@ class ValidationActions(object):
group=None, category=None, product=None,
extra_vars=None, validations_dir=None,
extra_env_vars=None, ansible_cfg=None, quiet=True,
workdir=None, limit_hosts=None, run_async=False,
limit_hosts=None, run_async=False,
base_dir=constants.DEFAULT_VALIDATIONS_BASEDIR,
log_path=constants.VALIDATIONS_LOG_BASEDIR,
python_interpreter=None, skip_list=None,
log_path=None, python_interpreter=None, skip_list=None,
callback_whitelist=None,
output_callback='validation_stdout', ssh_user=None,
validation_config=None):
"""Run one or multiple validations by name(s), by group(s) or by
product(s)
:param validation_name: A list of validation names
:param validation_name: A list of validation names.
:type validation_name: ``list``
:param inventory: Either proper inventory file, or a comma-separated
list. (Defaults to ``localhost``)
@ -315,8 +361,6 @@ class ValidationActions(object):
:type ansible_cfg: ``string``
:param quiet: Disable all output (Defaults to ``True``)
:type quiet: ``Boolean``
:param workdir: Location of the working directory
:type workdir: ``string``
:param limit_hosts: Limit the execution to the hosts.
:type limit_hosts: ``string``
:param run_async: Enable the Ansible asynchronous mode
@ -329,6 +373,9 @@ class ValidationActions(object):
:param log_path: The absolute path of the validations logs directory
(Defaults to
``constants.VALIDATIONS_LOG_BASEDIR``)
The absolute path of the validations logs directory.
The 'log_path' argument is deprecated and will be removed in the next release.
Use the 'log_path' argument of the init method.
:type log_path: ``string``
:param python_interpreter: Path to the Python interpreter to be
used for module execution on remote targets,
@ -359,7 +406,7 @@ class ValidationActions(object):
Status, Status_by_Host, UUID and Unreachable_Hosts)
:rtype: ``list``
:Example:
:example:
>>> path = "/u/s/a"
>>> validation_name = ['foo', 'bar']
@ -402,27 +449,45 @@ class ValidationActions(object):
for val in validations:
playbooks.append("{path}/{id}.yaml".format(**val))
elif validation_name:
self.log.debug(
"Getting the {} validation.".format(
validation_name))
playbooks = v_utils.get_validations_playbook(
validations_dir,
validation_name,
validation_config=validation_config)
if not playbooks or len(validation_name) != len(playbooks):
p = []
found_playbooks = []
for play in playbooks:
p.append(os.path.basename(os.path.splitext(play)[0]))
found_playbooks.append(
os.path.basename(os.path.splitext(play)[0]))
unknown_validation = list(set(validation_name) - set(p))
unknown_validations = list(
set(validation_name) - set(found_playbooks))
msg = "Validation {} not found in {}.".format(
unknown_validation, validations_dir)
msg = (
"Following validations were not found in '{}': {}"
).format(validations_dir, ', '.join(unknown_validations))
raise RuntimeError(msg)
else:
raise RuntimeError("No validations found")
if log_path:
self.log.warning((
"The 'log_path' argument is deprecated and"
" will be removed in the next release. "
"Use the 'log_path' argument of the init method."))
log_path = v_utils.create_log_dir(log_path)
else:
log_path = v_utils.create_log_dir(self.log_path)
self.log.debug((
'Running the validations with Ansible.\n'
'Gathered playbooks:\n -{}').format(
'\n -'.join(playbooks)))
log_path = v_utils.create_log_dir(log_path)
self.log.debug('Running the validations with Ansible')
results = []
for playbook in playbooks:
# Check if playbook should be skipped and on which hosts
@ -485,17 +550,6 @@ class ValidationActions(object):
'validations': _playbook.split('.')[0],
'UUID': validation_uuid,
})
# Print hosts which has been skipped:
if _hosts:
skipped_hosts = [h.replace('!', '')
for h in _hosts.split(',') if '!' in h]
if skipped_hosts:
msg = ("Validation {} has been skipped "
"on hosts: {}").format(_play,
','.join(skipped_hosts))
self.log.info(msg)
else:
self.log.info('Skipping Validations: {}'.format(playbook))
if run_async:
return results
@ -504,7 +558,7 @@ class ValidationActions(object):
vlog = ValidationLogs(log_path)
return vlog.get_results(uuid)
def group_information(self, groups, validation_config=None):
def group_information(self, groups=None, validation_config=None):
"""Get Information about Validation Groups
This is used to print table from python ``Tuple`` with ``PrettyTable``.
@ -519,7 +573,10 @@ class ValidationActions(object):
| group3 | Description of group3 | 1 |
+----------+--------------------------+-----------------------+
:param groups: The absolute path of the groups.yaml file
:param groups: The absolute path of the groups.yaml file.
The argument is deprecated and will be removed
in the next release.
Use the 'groups_path' argument of the init method.
:type groups: ``string``
:param validation_config: A dictionary of configuration for Validation
loaded from an validation.cfg file.
@ -529,19 +586,27 @@ class ValidationActions(object):
the numbers of validation belonging to them.
:rtype: ``tuple``
:Example:
:example:
>>> groups = "/foo/bar/groups.yaml"
>>> actions = ValidationActions(constants.ANSIBLE_VALIDATION_DIR)
>>> group_info = actions.group_information(groups)
>>> actions = ValidationActions(constants.ANSIBLE_VALIDATION_DIR, groups)
>>> group_info = actions.group_information()
>>> print(group_info)
(('Groups', 'Desciption', 'Number of Validations'),
[('group1', 'Description of group1', 3),
('group2', 'Description of group2', 12),
('group3', 'Description of group3', 1)])
"""
val_gp = Group(groups)
group_definitions = val_gp.get_formated_group
if groups:
self.log.warning((
"The 'groups' argument is deprecated and"
" will be removed in the next release. "
"Use the 'groups_path' argument of the init method."))
val_group = Group(groups)
else:
val_group = Group(self.groups_path)
group_definitions = val_group.get_formated_groups
group_info = []
@ -599,7 +664,7 @@ class ValidationActions(object):
:return: A JSON or a YAML dump (By default, JSON).
if `download_file` is used, a file containing only the
parameters will be created in the file system.
:exemple:
:example:
>>> validations = ['check-cpu', 'check-ram']
>>> groups = None
@ -620,26 +685,8 @@ class ValidationActions(object):
}
}
}
"""
if not validations:
validations = []
elif not isinstance(validations, list):
raise TypeError("The 'validations' argument must be a List")
if not groups:
groups = []
elif not isinstance(groups, list):
raise TypeError("The 'groups' argument must be a List")
if not categories:
categories = []
elif not isinstance(categories, list):
raise TypeError("The 'categories' argument must be a List")
if not products:
products = []
elif not isinstance(products, list):
raise TypeError("The 'products' argument must be a List")
supported_format = ['json', 'yaml']
@ -667,7 +714,7 @@ class ValidationActions(object):
params_only = {}
try:
with open(download_file, 'w') as parameters_file:
for val_name in params.keys():
for val_name in params:
params_only.update(params[val_name].get('parameters'))
if output_format == 'json':
@ -698,7 +745,7 @@ class ValidationActions(object):
return params
def show_history(self, validation_ids=None, extension='json',
log_path=constants.VALIDATIONS_LOG_BASEDIR,
log_path=None,
history_limit=None):
"""Return validation executions history
@ -706,7 +753,10 @@ class ValidationActions(object):
:type validation_ids: a list of strings
:param extension: The log file extension (Defaults to ``json``)
:type extension: ``string``
:param log_path: The absolute path of the validations logs directory
:param log_path: The absolute path of the validations logs directory.
The 'log_path' argument is deprecated and will
be removed in the next release.
Use the 'log_path' argument of the init method.
:type log_path: ``string``
:param history_limit: The number of most recent history logs
to be displayed.
@ -716,7 +766,7 @@ class ValidationActions(object):
history
:rtype: ``tuple``
:Example:
:example:
>>> actions = ValidationActions(constants.ANSIBLE_VALIDATION_DIR)
>>> print(actions.show_history())
@ -754,8 +804,17 @@ class ValidationActions(object):
'PASSED',
'2020-11-13T11:47:50.279662Z',
'0:00:02.237')])
"""
vlogs = ValidationLogs(log_path)
if log_path:
self.log.warning((
"The 'log_path' argument is deprecated and"
" will be removed in the next release. "
"Use the 'log_path' argument of the init method."))
vlogs = ValidationLogs(log_path)
else:
vlogs = ValidationLogs(self.log_path)
if validation_ids:
if not isinstance(validation_ids, list):
validation_ids = [validation_ids]
@ -794,13 +853,16 @@ class ValidationActions(object):
:type uuid: ``string``
:param status: The status of the execution (Defaults to FAILED)
:type status: ``string``
:param log_path: The absolute path of the validations logs directory
:param log_path: The absolute path of the validations logs directory.
The 'log_path' argument is deprecated and will
be removed in the next release.
Use the 'log_path' argument of the init method.
:type log_path: ``string``
:return: A list of validations execution with details and by status
:rtype: ``tuple``
:Example:
:example:
>>> actions = ValidationActions(validation_path='/foo/bar')
>>> status = actions.get_status(validation_id='foo'))
@ -831,7 +893,15 @@ class ValidationActions(object):
'failed': True,
'msg': 'Debug mode is not disabled.'})])
"""
vlogs = ValidationLogs(log_path)
if log_path:
self.log.warning((
"The 'log_path' argument is deprecated and"
" will be removed in the next release. "
"Use the 'log_path' argument of the init method."))
vlogs = ValidationLogs(log_path)
else:
vlogs = ValidationLogs(self.log_path)
if validation_id:
logs = vlogs.get_logfile_by_validation(validation_id)
elif uuid: